# GSEAlens Interactive Analysis Workstation ---------------------------------------------------

#' @title GSEAlens Interactive Analysis Workstation

#' @description Modular Shiny application based on GseaRes object, supporting

#'   Limma and DESeq2 dual backends. Provides interactive visualization for

#'   GSEA pathway enrichment analysis including volcano plots, heatmaps,

#'   expression boxplots, and pathway relationship networks.

#' @param gsea_res GseaRes object (generated by batch_calc_gsea())

#' @param addition_data Optional. A data frame or file path containing

#'   pathway annotations to merge into the main table. Can also be:

#'   \itemize{

#'     \item A data.frame with 'ID' column as primary key

#'     \item A character vector: file path to .csv, .csv.gz, .rds, or .rds.gz

#'     \item NULL: automatically searches for "addition_data_gsealens.rds"

#'       or "addition_data_gsealens.csv" in the working directory

#'   }

#'   Additional columns will be merged to the main pathway table.

#' @return Shiny application object

#' @export

#' @examples

#' if(interactive()){

#' # Basic usage

#' gsea_res <- batch_calc_gsea(gsea_env)

#' launch_gsea_app(gsea_res)

#'

#' # With addition data from file path

#' launch_gsea_app(gsea_res, "pathway_annotations.csv")

#'

#' # With pre-loaded data frame

#' add_data <- read.csv("annotations.csv")

#' launch_gsea_app(gsea_res, add_data)

#' }

launch_gsea_app <- function(gsea_res, addition_data = NULL) {

    # Step 1: Validate GseaRes object ------------------------------------------------



    if (!inherits(gsea_res, "GseaRes")) {

    stop("Please provide a valid GseaRes object (generated by batch_calc_gsea())")

    }



    # Step 2: Handle addition_data ----------------------------------------------------



    if (!is.null(addition_data)) {

    addition_data <- .process_addition_data(addition_data)

    } else {

    # Auto-detect from working directory

    addition_data <- .auto_detect_addition_data()

    }



    # Step 3: Start Shiny application -------------------------------------------------



    .launch_gsea_shiny(gsea_res, addition_data)

}



# Process Addition Data -----------------------------------------------------------------------





#' @title Process Addition Data

#' @description Internal function to handle various addition_data input types.

#'   Supports data.frame objects or file paths (.csv, .rds formats).

#' @param addition_data A data.frame or character vector (file path)

#' @return A validated data.frame with addition data

#' @keywords internal

.process_addition_data <- function(addition_data) {

    # If already a data.frame, validate and return

    if (is.data.frame(addition_data)) {

    .validate_addition_data(addition_data)

    return(addition_data)

    }



    # If character, treat as file path

    if (is.character(addition_data)) {

    if (length(addition_data) != 1) {

            stop("When addition_data is a character, it must be a single file path")

    }

    if (!file.exists(addition_data)) {

            stop("Addition data file not found: ", addition_data)

    }



    # Detect format and read

    if (grepl("\\.rds", addition_data, ignore.case = TRUE)) {

            data <- readRDS(addition_data)

    } else if (grepl("\\.csv", addition_data, ignore.case = TRUE)) {

            data <- read.csv(addition_data,

        stringsAsFactors = FALSE,

        check.names = FALSE, encoding = "UTF-8"

            )

    } else {

            stop("Unsupported addition_data format. Use .csv or .rds")

    }



    .validate_addition_data(data)

    return(data)

    }



    stop("addition_data must be a data.frame or file path (character)")

}



# Validate Addition Data ----------------------------------------------------------------------





#' @title Validate Addition Data

#' @description Internal function to validate addition_data structure.

#'   Checks that input is a data.frame with required 'ID' column and

#'   removes duplicate entries if present.

#' @param data A data.frame to validate

#' @return Validated data.frame with character ID column

#' @keywords internal

.validate_addition_data <- function(data) {

    if (!is.data.frame(data)) {

    stop("addition_data must be a data.frame")

    }

    if (!"ID" %in% colnames(data)) {

    stop("addition_data must contain 'ID' column as primary key")

    }

    # Ensure ID is character

    data$ID <- as.character(data$ID)

    # Deduplicate

    if (any(duplicated(data$ID))) {

    n_dup <- sum(duplicated(data$ID))

    message("[GSEAlens] Removed ", n_dup, " duplicate ID(s) from addition_data")

    data <- data[!duplicated(data$ID), ]

    }

    message(

    "[GSEAlens] addition_data validated: ", nrow(data), " rows x ",

    ncol(data), " columns"

    )

    return(data)

}



# Auto-detect Addition Data -------------------------------------------------------------------





#' @title Auto-detect Addition Data

#' @description Search working directory for default addition_data files.

#'   Checks for 'addition_data_gsealens.rds' or 'addition_data_gsealens.csv'

#'   in the current working directory.

#' @return A validated data.frame or NULL if no file found

#' @keywords internal

.auto_detect_addition_data <- function() {

    wd <- getwd()



    # Priority: RDS > CSV > not found

    rds_path <- file.path(wd, "addition_data_gsealens.rds")

    csv_path <- file.path(wd, "addition_data_gsealens.csv")



    if (file.exists(rds_path)) {

    message("[GSEAlens] Auto-detected addition_data: ", basename(rds_path))

    data <- readRDS(rds_path)

    return(.validate_addition_data(data))

    }



    if (file.exists(csv_path)) {

    message("[GSEAlens] Auto-detected addition_data: ", basename(csv_path))

    message("[GSEAlens] Converting CSV to RDS for faster loading...")

    data <- read.csv(csv_path,

            stringsAsFactors = FALSE,

            check.names = FALSE, encoding = "UTF-8"

    )

    # Validate and save as RDS for future use

    data <- .validate_addition_data(data)

    saveRDS(data, rds_path, compress = "gzip")

    message("[GSEAlens] Saved RDS cache: ", basename(rds_path))

    return(data)

    }



    message("[GSEAlens] No addition_data found, using default table display")

    return(NULL)

}



# Launch GSEAlens Shiny App (Internal) --------------------------------------------------------





#' @title Launch GSEAlens Shiny App (Internal)

#' @description Core Shiny application launcher. Builds the UI layout and

#'   initializes all server modules for interactive GSEA analysis.

#' @param gsea_res A valid GseaRes object

#' @param addition_data Optional data.frame with pathway annotations

#' @return A Shiny app object

#' @keywords internal

.launch_gsea_shiny <- function(gsea_res, addition_data) {

    ui <- shiny::fluidPage(

    shiny::tags$head(

            shiny::tags$style(shiny::HTML("

        /* IRON FIX (2026-06-30): modal scroll-to-top bug.

                    * Previously only .modal-body had max-height: 85vh, but the dialog

                    * itself was unconstrained and vertically centered by Bootstrap, so

                    * when the Live Preview image grew taller than the viewport the

                    * browser pushed the modal header above the viewport and the user

                    * could not scroll back to it. Switching to a flex layout with a

                    * bounded .modal-dialog / .modal-content keeps header & footer

                    * pinned and only the .modal-body scrolls. */

        .modal {

                    display: flex !important;

                    align-items: flex-start !important;

                    overflow-y: auto !important;

        }

        .modal-dialog {

                    max-width: 90vw !important;

                    width: 90vw !important;

                    margin: 4vh auto !important;

        }

        .modal-content {

                    max-height: 92vh !important;

                    display: flex !important;

                    flex-direction: column !important;

        }

        .modal-header { flex: 0 0 auto !important; }

        .modal-body {

                    flex: 1 1 auto !important;

                    min-height: 0 !important;

                    overflow-y: auto !important;

                    background-color: #fafafa;

        }

        .modal-footer { flex: 0 0 auto !important; }

        .white-box {

                    background-color: white; padding: 15px; border-radius: 8px;

                    box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin-bottom: 15px;

        }

        .description-cell {

                    white-space: normal !important; word-wrap: break-word !important;

                    max-width: 300px; font-size: 12px; line-height: 1.4;

        }

        .dt-center { text-align: center !important; }

        .well { background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 15px; }

            "))

    ),

    shinyjs::useShinyjs(),

    shiny::titlePanel(shiny::HTML("GSEAlens: Seek Inside")),

    shiny::sidebarLayout(

            shiny::sidebarPanel(

        width = 3,

        mod_data_prep_ui("data_prep")

            ),

            shiny::mainPanel(

        width = 9,

        shiny::tabsetPanel(

                    id = "main_tabs",

                    shiny::tabPanel(

            title = "Main Workspace",

            shiny::br(),

            mod_master_table_ui("master_table"),

            shiny::hr(),

            mod_multi_plot_ui("multi_plot")

                    ),

                    shiny::tabPanel(

            title = "Holographic Quadruple Linkage",

            shiny::br(),

            mod_quadrant_ui("quadrant")

                    ),

                    shiny::tabPanel(

            title = "Pathway Relationship Exploration",

            shiny::br(),

            mod_pathway_relation_ui("pathway_relation")

                    ),

                    shiny::tabPanel(

            title = "HubGene Network",

            value = "hubgene_vis",

            shiny::br(),

            mod_hubgene_vis_ui("hubgene_vis")

                    ),

                    shiny::tabPanel(

            title = "AI Interpretation",

            value = "ai_interpretation",

            mod_ai_abs_page_ui("ai_abs")

                    ),

                    shiny::tabPanel(

            title = "Joint GSEA Canvas",

            shiny::br(),

            mod_joint_canvas_ui("joint_canvas")

                    )

        )

            )

    ),

    mod_pathway_modal_ui("pathway_modal")

    )



    # Server part -------------------------------------------------------------------------

    server <- function(input, output, session) {

    # Module 1: Data preprocessing module

    data_prep_list <- mod_data_prep_server("data_prep", gsea_res)



    # Module 2: Master table module

    table_result <- mod_master_table_server("master_table", data_prep_list$data, addition_data)



    # Module 3: Combined plotting module (gsea_res for cross-collection support)

    mod_multi_plot_server("multi_plot", data_prep_list$data, table_result, gsea_res)



    # Module 4: Quadrant linkage module (pass table_controller for bidirectional sync)

    mod_quadrant_server("quadrant", data_prep_list, gsea_res, table_controller = table_result)



    # Module 5: Detail modal module

    mod_pathway_modal_server(

            "pathway_modal",

            data_prep_list$data,

            table_result$show_modal,

            gsea_res,

            pending_genes_reactive = data_prep_list$get_pending_genes,

            update_pending_genes = data_prep_list$update_pending_genes

    )



    # Module 6: Pathway relation exploration module

    mod_pathway_relation_server("pathway_relation", data_prep_list, gsea_res, table_result)



    # Shared gsea_res reference (for downstream modules)

    shiny::shinyOptions(gsea_res_shared = gsea_res)



    # Module 7: HubGene Network module

    mod_hubgene_vis_server("hubgene_vis", data_prep_list, table_result, gsea_res)



    # Module 8: AI analysis module

    mod_ai_abs_page_server("ai_abs", gsea_res, data_prep_list, table_result)



    # Module 9: Joint GSEA canvas module

    mod_joint_canvas_server("joint_canvas", gsea_res, data_prep_list, table_result)

    }



    message("GSEAlens started")

    message(sprintf("   Backend type: %s", gsea_res$backend_info$backend))

    message(sprintf("   Number of comparison groups: %d", nrow(gsea_res$contrast_registry)))



    shiny::shinyApp(ui, server)

}

