test_that("gtregression app is bundled with the package", {
  app_file <- system.file("shiny", "app.R", package = "gtregression")

  expect_true(nzchar(app_file))
  expect_true(file.exists(app_file))
  expect_true("gtregression_app" %in% getNamespaceExports("gtregression"))
})

test_that("app organises visualisation tools and available combinations", {
  app_file <- system.file("shiny", "app.R", package = "gtregression")
  app_code <- paste(readLines(app_file, warn = FALSE), collapse = "\n")

  expect_match(app_code, '"visual_tool"', fixed = TRUE)
  expect_match(app_code, '"merge_table_choices"', fixed = TRUE)
  expect_match(app_code, '"plot_content_controls"', fixed = TRUE)
  expect_match(app_code, '"forest_content_controls"', fixed = TRUE)
  expect_match(app_code, '"Descriptive + regression crude + adjusted"', fixed = TRUE)
  expect_match(app_code, "gtregression::plot_reg_combine(pair[[1]], pair[[2]]", fixed = TRUE)
  expect_match(app_code, "gtregression::forest_df(pair[[1]], pair[[2]], desc = desc)", fixed = TRUE)
  expect_match(app_code, "Run both the exposure models and multivariable model", fixed = TRUE)
})

test_that("app treats stratified plots and forests as single sources", {
  app_file <- system.file("shiny", "app.R", package = "gtregression")
  app_code <- paste(readLines(app_file, warn = FALSE), collapse = "\n")

  expect_match(app_code, "is_stratified_result <- function", fixed = TRUE)
  expect_match(app_code, "pair_can_combine <- function", fixed = TRUE)
  expect_match(app_code, "Cox exposure models (stratified)", fixed = TRUE)
  expect_match(app_code, "plot_reg() creates strata-specific panels", fixed = TRUE)
  expect_match(app_code, "forest_reg() places strata side by side", fixed = TRUE)
  expect_match(app_code, "combined_ready <- pair_can_combine(rv$uni, rv$multi)", fixed = TRUE)
  expect_match(app_code, "cox_combined_ready <- pair_can_combine(rv$cox_exposure, rv$cox_multi)", fixed = TRUE)
  expect_match(app_code, "survreg_combined_ready <- pair_can_combine(rv$survreg_exposure, rv$survreg_multi)", fixed = TRUE)
})

test_that("app exposes broad imports and essential forest controls", {
  app_file <- system.file("shiny", "app.R", package = "gtregression")
  app_code <- paste(readLines(app_file, warn = FALSE), collapse = "\n")

  expect_match(app_code, '".rds"', fixed = TRUE)
  expect_match(app_code, '".dta"', fixed = TRUE)
  expect_match(app_code, '".sav"', fixed = TRUE)
  expect_match(app_code, '".sas7bdat"', fixed = TRUE)
  expect_match(app_code, 'readRDS(path)', fixed = TRUE)
  expect_match(app_code, 'requireNamespace("haven"', fixed = TRUE)
  expect_match(app_code, 'numericInput("forest_ci_width"', fixed = TRUE)
  expect_match(app_code, 'textInput("forest_xlim"', fixed = TRUE)
  expect_match(app_code, 'textInput("forest_ticks"', fixed = TRUE)
  expect_match(app_code, 'downloadButton(paste0(id, "_html"), "HTML")', fixed = TRUE)
})

test_that("survival workspace separates Cox and parametric workflows", {
  app_file <- system.file("shiny", "app.R", package = "gtregression")
  app_code <- paste(readLines(app_file, warn = FALSE), collapse = "\n")

  expect_match(app_code, '"surv_mode"', fixed = TRUE)
  expect_match(app_code, '"Cox regression" = "cox"', fixed = TRUE)
  expect_match(app_code, '"Parametric survival" = "survreg"', fixed = TRUE)
  expect_match(app_code, "input.surv_mode == 'cox'", fixed = TRUE)
  expect_match(app_code, "input.surv_mode == 'survreg'", fixed = TRUE)
  expect_match(app_code, 'id = "surv_output_tab"', fixed = TRUE)
  expect_match(app_code, '"Model Result",', fixed = TRUE)
  expect_match(app_code, 'actionButton("run_cox", "Run Cox regression"', fixed = TRUE)
  expect_match(app_code, 'actionButton("run_survreg", "Run parametric survival regression"', fixed = TRUE)
  expect_match(app_code, 'id = "cox_result_tab"', fixed = TRUE)
  expect_match(app_code, 'id = "survreg_result_tab"', fixed = TRUE)
  expect_match(app_code, 'gtx_table_output("cox_exposure_table")', fixed = TRUE)
  expect_match(app_code, 'gtx_table_output("cox_multi_table")', fixed = TRUE)
  expect_match(app_code, 'gtx_table_output("survreg_exposure_table")', fixed = TRUE)
  expect_match(app_code, 'gtx_table_output("survreg_multi_table")', fixed = TRUE)
  expect_match(app_code, 'uiOutput("km_group_input")', fixed = TRUE)
  expect_match(app_code, 'identical(input$surv_mode, "survreg")', fixed = TRUE)
})

test_that("survival results are retained by model role", {
  app_file <- system.file("shiny", "app.R", package = "gtregression")
  app_code <- paste(readLines(app_file, warn = FALSE), collapse = "\n")

  expect_match(app_code, "rv$cox_exposure <- result", fixed = TRUE)
  expect_match(app_code, "rv$cox_multi <- result", fixed = TRUE)
  expect_match(app_code, "rv$survreg_exposure <- result", fixed = TRUE)
  expect_match(app_code, "rv$survreg_multi <- result", fixed = TRUE)
  expect_match(app_code, '"Cox exposure models" = rv$cox_exposure', fixed = TRUE)
  expect_match(app_code, '"Cox multivariable model" = rv$cox_multi', fixed = TRUE)
  expect_match(app_code, '"Parametric exposure models" = rv$survreg_exposure', fixed = TRUE)
  expect_match(app_code, '"Parametric multivariable model" = rv$survreg_multi', fixed = TRUE)
  expect_match(app_code, '"cox_multi_result" else "cox_exposure_result"', fixed = TRUE)
  expect_match(app_code, '"survreg_multi_result" else "survreg_exposure_result"', fixed = TRUE)
  expect_match(app_code, '"Cox exposure + multivariable" = "cox_combined"', fixed = TRUE)
  expect_match(app_code, '"Parametric exposure + multivariable" = "survreg_combined"', fixed = TRUE)
  expect_match(app_code, "cox_combined = list(rv$cox_exposure, rv$cox_multi)", fixed = TRUE)
  expect_match(app_code, "survreg_combined = list(rv$survreg_exposure, rv$survreg_multi)", fixed = TRUE)
  expect_match(app_code, "forest_data <- forest_df(cox_exposure_result, cox_multi_result", fixed = TRUE)
  expect_match(app_code, "forest_data <- forest_df(survreg_exposure_result, survreg_multi_result", fixed = TRUE)
})

test_that("advanced tools use a guided contextual workflow", {
  app_file <- system.file("shiny", "app.R", package = "gtregression")
  app_code <- paste(readLines(app_file, warn = FALSE), collapse = "\n")

  expect_match(app_code, '"advanced_tool"', fixed = TRUE)
  expect_match(app_code, '"Select models" = "selection"', fixed = TRUE)
  expect_match(app_code, '"Compare models" = "comparison"', fixed = TRUE)
  expect_match(app_code, '"Confounder" = "confounder"', fixed = TRUE)
  expect_match(app_code, '"Interaction" = "interaction"', fixed = TRUE)
  expect_match(app_code, '"Convergence" = "convergence"', fixed = TRUE)
  expect_match(app_code, '"Collinearity" = "collinearity"', fixed = TRUE)
  expect_match(app_code, '"compare_approach"', fixed = TRUE)
  expect_match(app_code, '"compare_outcome"', fixed = TRUE)
  expect_match(app_code, '"compare_exposures_"', fixed = TRUE)
  expect_match(app_code, '"compare_adjust_"', fixed = TRUE)
  expect_match(app_code, '"compare_interaction_a_"', fixed = TRUE)
  expect_match(app_code, '"compare_name_"', fixed = TRUE)
  expect_match(app_code, '"compare_add_model"', fixed = TRUE)
  expect_match(app_code, '"adv_collinearity_model"', fixed = TRUE)
  expect_match(app_code, 'uiOutput("advanced_downloads")', fixed = TRUE)
  expect_match(app_code, 'current_approach <- input$adv_approach %||% "logit"', fixed = TRUE)
  expect_match(app_code, 'isTRUE(current_approach %in% c("cox", "survreg"))', fixed = TRUE)
  expect_match(app_code, 'observeEvent(input$advanced_tool', fixed = TRUE)
  expect_match(app_code, 'Fit and compare models', fixed = TRUE)
  expect_match(app_code, 'fit_one <- function(specification)', fixed = TRUE)
  expect_match(app_code, '"multi_reg", data = data', fixed = TRUE)
  expect_match(app_code, '"cox_reg", data = data', fixed = TRUE)
  expect_match(app_code, '"surv_reg", data = data', fixed = TRUE)
  expect_match(app_code, 'gtx_table_downloads(output, "advanced"', fixed = TRUE)
  expect_match(app_code, 'updateTabsetPanel(session, "advanced_output_tab"', fixed = TRUE)
})

test_that("app errors are contained without pausing the Shiny session", {
  app_file <- system.file("shiny", "app.R", package = "gtregression")
  app_code <- paste(readLines(app_file, warn = FALSE), collapse = "\n")

  expect_match(app_code, "run_safely <- function", fixed = TRUE)
  expect_match(app_code, "shiny.silent.error = function(e) NULL", fixed = TRUE)
  expect_match(app_code, 'type = "error"', fixed = TRUE)
  expect_match(app_code, "clear_advanced_error", fixed = TRUE)
  expect_match(app_code, 'run_safely("Model selection"', fixed = TRUE)
  expect_match(app_code, 'run_safely("Confounder assessment"', fixed = TRUE)
  expect_match(app_code, 'run_safely("Interaction assessment"', fixed = TRUE)
  expect_match(app_code, '"Convergence diagnostics",\n      gtx_capture', fixed = TRUE)
  expect_match(app_code, '"Collinearity diagnostics",\n      gtx_capture', fixed = TRUE)
  expect_match(app_code, "This result could not be displayed", fixed = TRUE)
})

test_that("the complete app workflow can be downloaded as an R script", {
  app_file <- system.file("shiny", "app.R", package = "gtregression")
  app_code <- paste(readLines(app_file, warn = FALSE), collapse = "\n")

  expect_match(app_code, 'tabPanel(\n    "Session Code"', fixed = TRUE)
  expect_match(app_code, 'downloadButton("download_session_script", "Download complete R script"', fixed = TRUE)
  expect_match(app_code, 'complete_workflow_code <- reactive({', fixed = TRUE)
  expect_match(app_code, '"# Generated by the gtregression app"', fixed = TRUE)
  expect_match(app_code, '"library(gtregression)"', fixed = TRUE)
  expect_match(app_code, 'output$full_workflow_code <- renderText(complete_workflow_code())', fixed = TRUE)
  expect_match(app_code, 'output$download_session_script <- downloadHandler(', fixed = TRUE)
  expect_match(app_code, 'filename = function() "gtregression-session.R"', fixed = TRUE)
  expect_match(app_code, 'writeLines(complete_workflow_code(), file, useBytes = TRUE)', fixed = TRUE)
})

test_that("app keeps table modification and display-label controls discoverable", {
  app_file <- system.file("shiny", "app.R", package = "gtregression")
  app_code <- paste(readLines(app_file, warn = FALSE), collapse = "\n")

  expect_match(app_code, '"Modify table" = "modify"', fixed = TRUE)
  expect_match(app_code, 'actionButton("run_modify_table", "Apply table changes"', fixed = TRUE)
  expect_match(app_code, 'gtregression::modify_table(', fixed = TRUE)
  expect_match(app_code, 'remove_adjustment_note = isTRUE(input$modify_remove_adjustment_note)', fixed = TRUE)
  expect_match(app_code, 'tabPanel(\n            "Modified Table"', fixed = TRUE)
  expect_match(app_code, "Level-label format:", fixed = TRUE)
})

test_that("app defaults regression plots to a linear axis and supports advanced screening roles", {
  app_file <- system.file("shiny", "app.R", package = "gtregression")
  app_code <- paste(readLines(app_file, warn = FALSE), collapse = "\n")

  expect_match(app_code, 'checkboxInput("plot_log_x", "Use a log x-axis", FALSE)', fixed = TRUE)
  expect_match(app_code, 'checkboxInput("reg_model_stats", "Store model statistics", value = TRUE)', fixed = TRUE)
  expect_match(app_code, 'model_stats = input$reg_model_stats', fixed = TRUE)
  expect_match(app_code, 'checkboxGroupInput("conf_exposures", "Exposures of interest"', fixed = TRUE)
  expect_match(app_code, 'covariate_label = "Covariates included in both interaction models"', fixed = TRUE)
  expect_match(app_code, 'exposure = exposures,', fixed = TRUE)
})

test_that("app exposes the complete plot_model_fit API and records it in session code", {
  app_file <- system.file("shiny", "app.R", package = "gtregression")
  app_code <- paste(readLines(app_file, warn = FALSE), collapse = "\n")

  expect_match(app_code, '"Auto (recommended)" = "auto"', fixed = TRUE)
  expect_match(app_code, '"Normal Q-Q" = "qq"', fixed = TRUE)
  expect_match(app_code, '"Scale-location" = "scale_location"', fixed = TRUE)
  expect_match(app_code, '"Observed vs predicted" = "observed_predicted"', fixed = TRUE)
  expect_match(app_code, 'numericInput("fit_base_size", "Base font size"', fixed = TRUE)
  expect_match(app_code, 'base_size = input$fit_base_size', fixed = TRUE)
  expect_match(app_code, '"# 7a. Model-fit diagnostic"', fixed = TRUE)
})

test_that("app keeps data preview compact and descriptive statistics visible", {
  app_file <- system.file("shiny", "app.R", package = "gtregression")
  app_code <- paste(readLines(app_file, warn = FALSE), collapse = "\n")

  expect_match(app_code, "utils::head(rv$data, 10)", fixed = TRUE)
  expect_match(app_code, "First 10 rows only.", fixed = TRUE)
  expect_match(app_code, 'textInput("desc_statistic", "Numeric statistic override"', fixed = TRUE)
  expect_match(app_code, "If x-axis labels overlap, enter fewer ticks", fixed = TRUE)
})
