# ---------------------------
#
# Script name: aggregate_server.R
# data4health Shiny app - aggregate server
#
# Purpose of script: creates the server module for the
# aggregation tab of the shiny app
# Author: Daniela Lührsen
# Date Created: 2026-02
# Email: daniela.luhrsen@bsc.es
#
# ---------------------------

filter_server <- function(id, data_store, app_state) {

  moduleServer(id, function(input, output, session) {

    ns <- NS(id)

    # load data --------------------------------------------------------------
    # UI - for loading data
    output$filter_load <- renderUI({
      # if user is coming from the cleaning tab,
      # show message that data is loaded and give option to upload new data
      if (length(data_store$get()) != 0) {
        tagList(
          column(
            width = 6,
            helpText(paste0("The pre-processed data from the previous ",
                            "tab is automatically loaded."))
          ),
          column(
            width = 6,
            helpText(paste0("If you wish to use a different dataset, you can ",
                            "upload it here. Each row should be a case, and ",
                            "the columns (with headers) are information ",
                            "about it.")),
            fileInput(ns("filter_file_input"), "",
                      accept = c(".csv", ".dbf", ".dbc", ".rds",
                                 ".xlsx", ".xls", ".json"),
                      multiple = TRUE)
          )
        )
      # if user is coming directly to aggregation tab, show only the file input
      } else {
        tagList(
          helpText(paste0("Each row should be a case, and the columns (with ",
                          "headers) are information about it.")),
          fileInput(ns("filter_file_input"), "",
                    accept = c(".csv", ".dbf", ".dbc", ".rds",
                               ".xlsx", ".xls", ".json"),
                    multiple = TRUE)
        )
      }
    })

    # load dataset for filter
    observeEvent(input$filter_file_input, {
      infile <- input$filter_file_input
      data_store$set(data4health::d4h_load(infile$datapath))
    })

    # filter data ------------------------------------------------------------

    # Counter to keep track of filter blocks
    filter_count <- reactiveVal(0)

    observeEvent(input$add_filter, {
      filter_count(filter_count() + 1)
    })

    output$filters_ui <- renderUI({
      req(filter_count())

      tagList(
        lapply(seq_len(filter_count()), function(i) {

          fluidRow(
            column(
              width = 3,
              selectInput(
                ns(paste0("filter_column_", i)),
                "Column",
                choices = names(data_store$get()),
                selected = input[[paste0("filter_column_", i)]]
              )
            ),
            column(
              width = 3,
              uiOutput(ns(paste0("filter_col2_", i)))
            ),
            column(
              width = 3,
              uiOutput(ns(paste0("filter_col3_", i)))
            ),
            column(
              width = 3,
              uiOutput(ns(paste0("filter_col4_", i)))
            )
          )
        })
      )
    })

    observe({
      lapply(seq_len(filter_count()), function(i) {

        local({
          idx <- i

          output[[paste0("filter_col2_", idx)]] <- renderUI({
            req(input[[paste0("filter_column_", idx)]])

            x <- data_store$get()[[input[[paste0("filter_column_", idx)]]]]

            column_type <- if (is.numeric(x)) {
              "numeric"
            } else if (inherits(x, "Date")) {
              "Date"
            } else {
              "character"
            }
            prev_format <- input[[paste0("filter_format_", idx)]]
            prev_column <- input[[paste0("filter_column_prev_", idx)]]

            if (is.null(prev_column) || prev_column != column) {
              selected_val <- column_type
            } else {
              selected_val <- prev_format
            }


            selectInput(
              ns(paste0("filter_format_", idx)),
              "Format:",
              choices = c("numeric", "Date", "character"),
              selected = selected_val
            )
          })

          output[[paste0("filter_col3_", idx)]] <- renderUI({
            req(input[[paste0("filter_format_", idx)]])

            type <- input[[paste0("filter_format_", idx)]]

            conds <- switch(
              type,
              numeric = c("over", "under", "between"),
              Date = c("after", "before", "during"),
              character = c("include", "exclude"),
              c()
            )

            selectInput(
              ns(paste0("filter_cond_", idx)),
              "Condition",
              choices = conds,
              selected = input[[paste0("filter_cond_", idx)]]
            )
          })

          output[[paste0("filter_col4_", idx)]] <- renderUI({
            req(input[[paste0("filter_cond_", idx)]])

            column <- input[[paste0("filter_column_", idx)]]
            format <- input[[paste0("filter_format_", idx)]]
            cond <- input[[paste0("filter_cond_", idx)]]

            if (format == "Date") {

              if (cond == "during") {

                dateRangeInput(
                  ns(paste0("filter_val_", idx)),
                  label = "Select Date Range",
                  start = min(data_store$get()[[column]]),
                  end = max(data_store$get()[[column]])
                )

              } else {

                dateInput(
                  ns(paste0("filter_val_", idx)),
                  label = "Select Date",
                  value = input[[paste0("filter_val_", idx)]]
                )

              }

            } else if (format == "character") {

              choices <- unique(data_store$get()[[column]])

              if (length(choices) < 20) {

                selectInput(
                  ns(paste0("filter_val_", idx)),
                  "Select category",
                  choices = choices,
                  selected = input[[paste0("filter_val_", idx)]],
                  multiple = TRUE
                )

              } else {

                textInput(
                  ns(paste0("filter_val_", idx)),
                  "Value(s) (comma-separated)",
                  value = input[[paste0("filter_val_", idx)]]
                )

              }

            } else if (format == "numeric") {

              textInput(
                ns(paste0("filter_val_", idx)),
                "Value(s) (comma-separated)",
                value = input[[paste0("filter_val_", idx)]]
              )
            }
          })
        })
      })
    })


    # CODE - store code in a reactive value,
    # so it can be used both by the ui and the server
    code_filter <- reactiveVal()
    observe({
      req(data_store$get())
      all <- list()
      if (filter_count() > 0) {

        for (i in seq_len(filter_count())) {
          req(input[[paste0("filter_format_", i)]])
          col <- input[[paste0("filter_column_", i)]]
          cond <- input[[paste0("filter_cond_", i)]]
          val  <- input[[paste0("filter_val_", i)]]
          format <- input[[paste0("filter_format_", i)]]

          # if it's a single string with commas, split it
          if (is.character(val) && length(val) == 1 &&
                grepl(",", val, fixed = TRUE)) {
            val <- unlist(strsplit(val, ",", fixed = TRUE))
            val <- trimws(val)  # remove spaces
          }

          # build string
          if (length(val) > 1) {
            val_str <- paste0("c(",
                              paste(if (is.numeric(val)) val else shQuote(val),
                                    collapse = ", "),
                              ")")
          } else {
            if (format == "numeric") {
              val_str <- as.character(val)
            } else {
              val_str <- paste0("'", val, "'")
            }
          }

          # Try to convert character/factor to Date if possible
          if (format == "Date") {
            all[[i]] <- paste0("\n                  ",
                               col,
                               " = list(", cond, " = as.Date(", val_str, "))")
          } else {
            all[[i]] <- paste0("\n                  ",
                               col,
                               " = list(", cond, " = ", val_str, ")")
          }
        }
      }
      code_filter(paste0("df <- d4h_filter(data = df, ",
                         paste0(all, collapse = ", "), ")"))
    })

    # UI - code to filter
    output$filter_codeui <- renderUI({
      if (hide7_count() %% 2 == 0) {
        tagList(
          actionLink(ns("hide7"), "Click here to show the code",
                     icon("angle-down"),
                     style = "color: #bfbfbf; text-decoration: none")
        )
      } else {
        tagList(
          actionLink(ns("hide7"), "Click here to hide the code",
                     icon("angle-up"),
                     style = "color: #bfbfbf; text-decoration: none"),
          tags$br(),
          shinyAce::aceEditor(ns("code_filter"), mode = "r",
                              theme = "tomorrow", height = "50px",
                              value = code_filter())
        )
      }
    })
    hide7_count <- reactiveVal(0)
    observeEvent(input$hide7, {
      hide7_count(hide7_count() + 1)
    })

    # EXECUTE - run code to delete empty columns
    observeEvent(input$filter_button, {
      req(data_store$get())
      code <- code_filter()

      env <- new.env(parent = asNamespace("data4health"))
      env$input <- input
      env$df <- data_store$get()
      tryCatch({
        withCallingHandlers({
          out <- eval(parse(text = code), envir = env)

          # Now update the reactiveVal with the new data
          data_store$set(out)
        },
        # show messages in green
        message = function(m) {
          showNotification(conditionMessage(m), type = "message")
          invokeRestart("muffleMessage")
        })

      }, error = function(e) {
        showNotification(paste("Error:", conditionMessage(e)), type = "error")
      })
    })

    # download the cleaned data
    output$download_filter <- downloadHandler(
      filename = function() {
        paste0("d4h_filter_", format(Sys.time(), "%Y%m%d_%H%M"), ".csv")
      },
      content = function(file) {
        write.csv(data_store$get(), file)
      }
    )

    # if proceed to Visualisation button is clicked,
    # set go_visualisation to TRUE, app.R will use it to updateTabsetPanel
    observeEvent(input$go_filter_aggregation, {
      req(data_store$get())
      app_state$go_aggregation <- TRUE
    })
  })
}

### END
