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

clean_server <- function(id, data_store, app_state) {
  moduleServer(id, function(input, output, session) {

    ns <- NS(id)

    ## Load dataset ------------------------------------------------------------

    # load dataset for pre processing
    observeEvent(input$pre_file_input, {
      inFile <- input$pre_file_input
      data_store$set(data4health::d4h_load(inFile$datapath))
      columns_renamed(FALSE)
    })

    # load default dataset when "load test dataset" button is clicked
    observeEvent(input$pre_default_input, {
      file_path <- system.file("shiny/data",
                               "small_test_dataset.csv",
                               package = "data4health")
      data_store$set(read.csv(file_path))
      columns_renamed(FALSE)
    })

    # load dictionary
    dictionary <- read.csv("data/Dictionary_all.csv")

    ## Filter selected column _clean(cols_to_include)---------------------------

    # UI - list of colnames with explanation
    output$pre_colnames <- renderUI({
      req(data_store$get())
      tagList(
        tags$table(
          width = "100%",
          tags$tr(
            width = "100%",
            if (columns_renamed()) { tags$th("Original name")},
            tags$th("Column name"),
            tags$th("Meaning")
          ),
          lapply(colnames(data_store$get()), function(i) {

            # get name, or renamed name of column
            display_name <- ifelse(i %in% names(col_changes()),
                                   col_changes()[[i]],
                                   i)

            desc <- dictionary[dictionary$Var == display_name, "Description"]
            desc <- if (length(desc) == 0 || is.na(desc[1])) "" else desc[1]

            # create table with (renamed) names and description
            tags$tr(
              width = "100%",
              # if columns were renamed, add a column with the original name
              if (columns_renamed()) { tags$td(display_name)},
              tags$td(i),
              tags$td(desc)
            )
          })
        )
      )
    })

    # UI - checkboxes to select columns
    output$pre_include_columns_ui <- renderUI({
      req(data_store$get())
      checkboxGroupInput(
        ns("pre_include_columns"),
        "Select columns",
        choices = colnames(data_store$get())
      )
    })

    # CODE - stored in a reactive value, now its accesible by ui and the server
    code_pre_include_columns <- reactiveVal()
    observe({
      req(input$pre_include_columns)
      code_pre_include_columns(paste0(
        ", cols_to_include = c(",
        paste(sprintf('\"%s\"', input$pre_include_columns), collapse = ", "),
        ")"
      ))
    })

    # CODE - stored in a reactive value, now its accesible by ui and the server
    code_pre_include_columns <- reactiveVal()
    observe({
      req(pre_include_columns$clicked)
      code_pre_include_columns(paste0(
        ", cols_to_include = c(",
        paste(sprintf('\"%s\"', pre_include_columns$clicked), collapse = ", "),
        ")"
      ))
    })

    # UI - code to only keep selected variables
    pre_include_columns <- reactiveValues(clicked = list())
    output$pre_include_columns_uicode <- renderUI({
      if (hide3_count() %% 2 == 0) {
        tagList(
          actionLink(ns("hide3"),
                     "Click here to show the code",
                     icon("angle-down"),
                     style = "color: #bfbfbf; text-decoration: none")
        )
      } else {
        tagList(
          actionLink(ns("hide3"),
                     "Click here to hide the code",
                     icon("angle-up"),
                     style = "color: #bfbfbf; text-decoration: none"),
          tags$br(),
          shinyAce::aceEditor(
            ns("pre_include_columns_code"),
            mode = "r", theme = "tomorrow",
            height = "40px",
            value = make_clean_code(code_pre_include_columns())
          )
        )
      }
    })

    hide3_count <- reactiveVal(0)
    observeEvent(input[["hide3"]], {
      hide3_count(hide3_count() + 1)
    })

    # execute - keep only selected variables
    observeEvent(input$pre_include_columns_button, {
      req(data_store$get())
      code <- make_clean_code(code_pre_include_columns())

      env <- environment()
      env$input <- input
      env$df <- data_store$get()

      # Explicitly load the namespace so eval can find it
      ns <- getNamespace("data4health")

      tryCatch({
        withCallingHandlers({
          out <- eval(parse(text = code),
                      envir = list2env(as.list(env), parent = ns))
          # update the df reactive Value
          data_store$set(out)
        },
        # show messages in green
        message = function(m) {
          showNotification(conditionMessage(m), type = "message")
          invokeRestart("muffleMessage")
        })
        # show error messages
      }, error = function(e) {
        showNotification(
          paste("Error in code execution:", e$message),
          type = "error",
          duration = 10
        )
      })
    })

    ## rename columns _clean(rename_columns)------------------------------------

    # UI - rename columns (table)
    columns_renamed <- reactiveVal(FALSE)
    output$pre_rename_ui <- renderUI({
      req(data_store$get())
      colnames <- colnames(data_store$get())
      table <- tags$table(
        width="100%",
        tags$tr(
          width="100%",
          tags$th("Current Value", width = "30%"),
          tags$th("New value", width = "70%")
        ),
        lapply(seq_along(colnames), function(i) {
          val <- colnames[i]
          new_name <- dictionary$Translation[dictionary$Var == val]
          new_name <- if (length(new_name) == 0 || is.na(new_name[1])) "" else new_name[1]
          tags$tr(
            width="100%",
            tags$td(width = "30%", val),
            tags$td(width = "70%",
                    textInput(inputId = ns(paste0("input_col_", val)),
                              label = NULL,
                              value = new_name))
          )
        })
      )
      table
    })

    # stored in a reactive value, now its accesible by ui and the server
    code_pre_rename <- reactiveVal()
    observe({
      req(data_store$get())

format_name <- function(x) {
  ifelse(grepl("\\s", x),
         paste0("`", x, "`"),
         x)
}

      colnames <- colnames(data_store$get())
      new_names <- character()
      for (val in colnames) {
        new_name <- input[[paste0("input_col_", val)]]
        if (!is.null(new_name) && length(new_name) != 0 && new_name != "") {
          new_names[val] <- new_name
        }
      }

      code_pre_rename(paste0(
        ", rename_columns = c(",
        paste(sprintf('%s = "%s"', format_name(names(new_names)), new_names),
              collapse = ", "),
        ")"
      ))
    })

    # UI - rename columns (code block and button)
    output$pre_rename_uicode <- renderUI({

      output <- tagList()

      if (hide4_count() %% 2 == 0){
        tagList(
          actionLink(ns("hide4"),
                     "Click here to show the code",
                     icon("angle-down"),
                     style = "color: #bfbfbf; text-decoration: none"),
          tags$br(),
          actionButton(ns("run_code_colnames_rename"),
                       label = "Rename columns")
        )
      } else {
        tagList(
          actionLink(ns("hide4"),
                     "Click here to hide the code",
                     icon("angle-up"),
                     style = "color: #bfbfbf; text-decoration: none"),
          tags$br(),
          shinyAce::aceEditor(ns("code_colnames_rename"),
                              mode = "r", theme = "tomorrow",
                              height = "100px",
                              value = make_clean_code(code_pre_rename())),
          actionButton(ns("run_code_colnames_rename"),
                       label = "Rename columns")
        )
      }
    })

    hide4_count <- reactiveVal(0)
    observeEvent(input[["hide4"]],{
      hide4_count(hide4_count() + 1)
    })

    # execute_ rename columns
    col_changes <- reactiveVal()
    observeEvent(input$run_code_colnames_rename, {
      columns_renamed(TRUE)
      req(data_store$get())

      # preparing data for the table with names and definition
      colnames <- colnames(data_store$get())
      new_names <- character()
      for (val in colnames) {
        new_name <- input[[paste0("input_col_", val)]]
        if (!is.null(new_name) && length(new_name) != 0 && new_name != "") {
          new_names[new_name] <- val
        }
      }
      col_changes(new_names)

      # executing code when pressing button
      code <- make_clean_code(code_pre_rename())

      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
        message = function(m) {
          showNotification(conditionMessage(m), type = "message")
          invokeRestart("muffleMessage")
        })
      }, error = function(e) {
        showNotification(paste("Error:", conditionMessage(e)), type = "error")
      })
    })

    ## show completeness of columns  -------------------------------------------

    # CODE - stored in a reactive value, now its accesible by ui and the server
    code_pre_plot_missing <- reactiveVal()
    observe({
      req(data_store$get()) #req(data_store$get())
      code_pre_plot_missing(paste0(
        "missing_df <- data.frame(
    column = names(df),
    missing_pct = colSums(is.na(df)) / nrow(df) * 100
  )

  # Create interactive plot
  plot_ly(
    data = missing_df,
    x = ~column,
    y = ~missing_pct,
    type = 'bar',
    marker = list(color = 'skyblue')
  ) %>%
    layout(
      title = 'Percentage of Missing Values per Column',
      xaxis = list(title = 'Column', tickangle = -45),
      yaxis = list(title = 'Missing Values (%)')
    )"))
    })

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

    # reactive plot object containing the plot from code_pre_plot_missing
    plot_obj <- reactive({
      req(data_store$get())

      code <- code_pre_plot_missing()
      tryCatch({
        env <- new.env(parent = asNamespace("data4health"))
        env$input <- input
        env$df <- data_store$get()
        with(env, {
          library(plotly)
        })

        eval(parse(text = code), envir = env)

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

    # Assign the result to output
    output$NAplot <- renderPlotly({
      plot_obj()
    })

    ## remove columns with missing values --------------------------------------

    # CODE - stored in a reactive value, now its accesible by ui and the server
    code_pre_remove_missing <- reactiveVal()
    observe({
      req(data_store$get()) #req(data_store$get())
      code_pre_remove_missing(paste0(
        ", threshold_remove = ",
        input$pre_remove_threshold))
    })

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

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

      code <- make_clean_code(code_pre_remove_missing())

      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")
      })
    })
    
    ## rename categories -------------------------------------------------------
      
    # update column in dropdown
    observe({
        req(data_store$get())
      updateSelectInput(session,
                        "pre_rename_categories",
                        choices = names(data_store$get()),
                        selected = "-")
    })

    # UI - table to rename categories
    output$pre_rename_categories_ui <- renderUI({
      req(input$pre_rename_categories)

      column <- input$pre_rename_categories
      unique <- unique(data_store$get()[[column]])

      if (length(unique) < 20) {
        table <- tags$table(
          width = "100%",
          tags$tr(
            width = "100%",
            tags$th("Current Category", width = "30%"),
            tags$th("New Cateogry name", width = "70%")
          ),
          lapply(seq_along(unique), function(i) {
            val <- unique[i]
            tags$tr(
              width = "100%",
              tags$td(width = "30%", val),
              tags$td(width = "70%",
                      textInput(inputId = ns(paste0("input_cat_", val)),
                                label = NULL,
                                value = dictionary[[ifelse(is.numeric(val), paste0("X", val), val)]][dictionary$Var == column]))
            )
          })
        )
        table
      } else {
        print("Too many categories, please select a different column.")
      }
    })

    # CODE - stored in a reactive value, now its accesible by ui and the server
    code_pre_rename_categories <- reactiveVal()
    observe({
      req(data_store$get())
      column <- input$pre_rename_categories
       # use as.character to avoid errors with category names that are numbers
      categories <- as.character(unique(data_store$get()[[column]]))

      new_cats <- character()
      for (cat in categories) {
        new_cat <- input[[paste0("input_cat_", cat)]]
        if (!is.null(new_cat) && length(new_cat) != 0 && new_cat != "") {
          new_cats[cat] <- new_cat
        }
      }
      code_pre_rename_categories(paste0(
        ", rename_categories = list(", column, " = c(",
        paste(sprintf('"%s" = "%s"', names(new_cats), new_cats),
              collapse = ", "),
        "))"))
    })

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

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

      code <- make_clean_code(code_pre_rename_categories())

      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
        message = function(m) {
          showNotification(conditionMessage(m), type = "message")
          invokeRestart("muffleMessage")
        })
      }, error = function(e) {
        showNotification(paste("Error:", conditionMessage(e)), type = "error")
      })
    })

    ## convert temporal resolutions --------------------------------------------

    # update column in dropdown
    observe({
      x <- names(data_store$get())
      updateSelectInput(session, "pre_temporal_date",
                        choices = x, selected = "-")
    })

    # CODE - stored in a reactive value, now its accesible by ui and the server
    code_pre_temporal <- reactiveVal()
    observe({
      req(data_store$get())
      code_pre_temporal(paste0(
        ifelse("weekdate" %in% input$pre_temporal_choices,
              paste0(", date_to_weekdate = '", input$pre_temporal_date, "'"),
              ""),
        ifelse("weeknumber" %in% input$pre_temporal_choices,
              paste0(", date_to_weeknumber = '", input$pre_temporal_date, "'"),
              ""),
        ifelse("monthdate" %in% input$pre_temporal_choices,
              paste0(", date_to_monthdate = '", input$pre_temporal_date, "'"),
              ""),
        ifelse("monthnumber" %in% input$pre_temporal_choices,
              paste0(", date_to_monthnumber = '", input$pre_temporal_date, "'"),
              ""),
        ifelse("yearnumber" %in% input$pre_temporal_choices,
              paste0(", date_to_yearnumber = '", input$pre_temporal_date, "'"),
              ""),
        ", week_start = '", input$pre_week_start, "'"))
    })

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

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

      code <- make_clean_code(code_pre_temporal())

      env <- new.env(parent = asNamespace("data4health"))
      env$input <- input
      env$df <- data_store$get()

      tryCatch({
        withCallingHandlers({
          out <- eval(parse(text = code), envir = env)

          # 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")
      })
    })

    ## Finishing up: download, continue ----------------------------------------

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

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

### END
