options(
  shiny.sanitize.errors = TRUE,
  stringsAsFactors = FALSE
)

suppressPackageStartupMessages({
  library(bslib)
  library(plotly)
  library(shiny)
})

app_directory <- normalizePath(getwd(), winslash = "/", mustWork = TRUE)
source(file.path(app_directory, "R", "helpers.R"), local = TRUE, encoding = "UTF-8")
source(file.path(app_directory, "R", "plots.R"), local = TRUE, encoding = "UTF-8")
source(file.path(app_directory, "R", "query-state.R"), local = TRUE, encoding = "UTF-8")

data_path <- file.path(app_directory, "data", "cascade_results_explorer.rds")
if (!file.exists(data_path)) {
  stop("Prepared app data are missing. Run: Rscript prepare_app_data.R")
}

app_data <- readRDS(data_path)
required_objects <- c(
  "configuration", "cascade_summary", "table2", "table3",
  "density_change_summary", "target_position_gaps",
  "conditional_duration_density_comparison", "topology_summary",
  "target_summary", "target_agreement_summary", "edge_weight_summary",
  "topology_diagnostics_mc", "target_diagnostics_mc",
  "target_agreement_diagnostics_mc", "edge_weight_diagnostics_mc"
)
missing_objects <- setdiff(required_objects, names(app_data))
if (length(missing_objects) > 0L) {
  stop("Prepared app data are incomplete: ", paste(missing_objects, collapse = ", "))
}
if (nrow(app_data$cascade_summary) != 288L) stop("Unexpected cascade-summary row count.")
if (nrow(app_data$table2) != 96L) stop("Unexpected contrast-summary row count.")
if (nrow(app_data$table3) != 192L) stop("Unexpected total-failure-summary row count.")

cascade_summary <- app_data$cascade_summary
table2_summary <- app_data$table2

app_theme <- bs_theme(
  version = 5,
  bg = "#F5F7FA",
  fg = "#17202A",
  primary = "#164E63",
  secondary = "#64748B"
)

section_intro <- function(kicker, title, text) {
  div(
    class = "study-banner",
    span(class = "section-kicker", kicker),
    h2(title),
    p(text)
  )
}

question_card <- function(number, title, text, button_id, button_label) {
  div(
    class = "question-card",
    span(class = "question-number", paste("Question", number)),
    h3(title),
    p(text),
    actionButton(button_id, button_label, class = "btn-outline-primary btn-sm")
  )
}

guided_plot_card <- function(title, subtitle, plot_output) {
  card(
    class = "guided-plot-card",
    full_screen = TRUE,
    card_header(
      div(
        class = "plot-heading",
        h3(title),
        p(subtitle)
      )
    ),
    card_body(plot_output)
  )
}

filter_sidebar <- sidebar(
  width = 300,
  open = "desktop",
  title = "Choose a snapshot",
  div(
    class = "sidebar-intro",
    "All three graph families stay visible. The controls below set the remaining study conditions."
  ),
  conditionalPanel(
    condition = "['graph_family', 'centrality', 'thresholds', 'total_failure'].includes(input.view)",
    selectInput(
      "density", "Graph density",
      choices = named_choices(density_labels),
      selected = query_defaults$density
    )
  ),
  conditionalPanel(
    condition = "['graph_family', 'density', 'thresholds', 'total_failure'].includes(input.view)",
    selectInput(
      "metric", "Centrality metric",
      choices = named_choices(metric_labels),
      selected = query_defaults$metric
    )
  ),
  conditionalPanel(
    condition = "['graph_family', 'density', 'centrality', 'total_failure'].includes(input.view)",
    selectInput(
      "threshold", "Threshold regime",
      choices = named_choices(threshold_labels),
      selected = query_defaults$threshold
    )
  ),
  uiOutput("filter_summary"),
  div(
    class = "sidebar-actions",
    conditionalPanel(
      condition = "['graph_family', 'density', 'centrality', 'thresholds', 'total_failure'].includes(input.view)",
      actionButton("reset_filters", "Reset selections", class = "btn-outline-secondary"),
      downloadButton("download_snapshot", "Download this snapshot", class = "btn-outline-primary")
    ),
    actionButton("copy_view_link", "Copy view link", class = "btn-primary")
  )
)

ui <- page_sidebar(
  title = div(
    class = "app-brand",
    span("Cascade Simulation Results Explorer"),
    tags$small("Guided views of the verified primary study")
  ),
  window_title = "Cascade Simulation Results Explorer",
  theme = app_theme,
  fillable = FALSE,
  sidebar = filter_sidebar,
  tags$head(
    tags$link(rel = "stylesheet", type = "text/css", href = "styles.css?v=20260818-5"),
    tags$script(HTML(
      "Shiny.addCustomMessageHandler('copy-current-url', async function(message) {\n",
      "  let ok = false;\n",
      "  try {\n",
      "    const url = window.location.href;\n",
      "    if (navigator.clipboard && window.isSecureContext) {\n",
      "      await navigator.clipboard.writeText(url);\n",
      "      ok = true;\n",
      "    } else {\n",
      "      const input = document.createElement('textarea');\n",
      "      input.value = url; input.setAttribute('readonly', '');\n",
      "      input.style.position = 'fixed'; input.style.opacity = '0';\n",
      "      document.body.appendChild(input); input.select();\n",
      "      ok = document.execCommand('copy'); document.body.removeChild(input);\n",
      "    }\n",
      "  } catch (error) { ok = false; }\n",
      "  Shiny.setInputValue('copy_result', {ok: ok, nonce: Date.now()}, {priority: 'event'});\n",
      "});"
    ))
  ),
  navset_card_tab(
    id = "view",
    selected = "overview",
    nav_panel(
      title = "Start",
      value = "overview",
      section_intro(
        "Study guide",
        "Explore the study one question at a time",
        "Choose a research question below. Each page shows a bounded comparison and only the controls that apply to it."
      ),
      layout_columns(
        col_widths = c(4, 4, 4),
        question_card(
          1, "Graph family",
          "Where is the difference between high- and low-centrality targets largest?",
          "go_graph_family", "Compare graph families"
        ),
        question_card(
          2, "Density",
          "How do sparse and denser graphs change cascade extent and target separation?",
          "go_density", "Compare densities"
        ),
        question_card(
          3, "Centrality metric",
          "Does the high–low difference depend on the centrality metric?",
          "go_centrality", "Compare metrics"
        )
      ),
      layout_columns(
        col_widths = c(6, 6),
        question_card(
          4, "Threshold regime",
          "When does threshold sensitivity strengthen or suppress target differences?",
          "go_thresholds", "Compare thresholds"
        ),
        question_card(
          5, "Total failure",
          "When does the entire graph activate, and how many synchronous updates does that require?",
          "go_total_failure", "Explore total failure"
        )
      ),
      layout_columns(
        col_widths = c(7, 5),
        card(
          card_header("Representative graph realizations"),
          card_body(
            tags$img(
              src = "original-figures/figure04_representative_topologies.png",
              class = "topology-figure",
              alt = "Representative sparse and denser Erdős–Rényi, Barabási–Albert, and Watts–Strogatz graphs."
            )
          )
        ),
        card(
          card_header("How to read a snapshot"),
          card_body(
            div(
              class = "reading-steps",
              div(strong("1"), p("Choose the study conditions in the sidebar.")),
              div(strong("2"), p("Read the visible estimate and comparison.")),
              div(strong("3"), p("Hover only when you need the exact value."))
            )
          )
        )
      ),
      div(
        class = "interpretation-note",
        strong("Model scope. "),
        "These are Monte Carlo results from a weighted-threshold cascade model, not empirical treatment effects."
      )
    ),
    nav_panel(
      title = "Graph family",
      value = "graph_family",
      section_intro(
        "Research question 1",
        "Where does centrality matter most?",
        "Compare the high–low target difference across the three graph-generating models while holding density, centrality metric, and threshold regime fixed."
      ),
      guided_plot_card(
        "High- versus low-centrality targets",
        "The bar height is the paired difference in final activation. Positive values favor the high-centrality target.",
        plotlyOutput("rq1_plot", height = "440px")
      ),
      uiOutput("rq1_takeaway")
    ),
    nav_panel(
      title = "Density",
      value = "density",
      section_intro(
        "Research question 2",
        "What changes when graphs are denser?",
        "Compare 20- and 40-edge graphs across all three graph families. Switch between target separation and the absolute cascade outcome."
      ),
      div(
        class = "view-toggle",
        radioButtons(
          "density_view", "Display",
          choices = c("High–low difference" = "contrast", "Final extent" = "extent"),
          selected = "contrast",
          inline = TRUE
        )
      ),
      guided_plot_card(
        "Sparse versus denser graphs",
        "Color and marker identify graph family (ER, BA, WS). In Final extent, dashed lines are least central and solid lines are most central.",
        plotlyOutput("rq2_plot", height = "440px")
      ),
      uiOutput("rq2_takeaway")
    ),
    nav_panel(
      title = "Centrality metric",
      value = "centrality",
      section_intro(
        "Research question 3",
        "Does the centrality metric change the result?",
        "Compare degree, strength, harmonic closeness, and betweenness across all three graph families for the same density and threshold regime."
      ),
      guided_plot_card(
        "High–low difference by centrality metric",
        "Each line is one graph family; color and marker identify ER, BA, and WS.",
        plotlyOutput("rq3_plot", height = "440px")
      ),
      uiOutput("rq3_takeaway")
    ),
    nav_panel(
      title = "Thresholds",
      value = "thresholds",
      section_intro(
        "Research question 4",
        "When do target differences emerge?",
        "Compare all four threshold regimes across all three graph families for one density and centrality metric."
      ),
      div(
        class = "view-toggle",
        radioButtons(
          "threshold_view", "Display",
          choices = c("High–low difference" = "contrast", "Final extent" = "extent"),
          selected = "contrast",
          inline = TRUE
        )
      ),
      guided_plot_card(
        "Sensitive through resilient thresholds",
        "Color and marker identify graph family (ER, BA, WS). In Final extent, dashed lines are least central and solid lines are most central.",
        plotlyOutput("rq4_plot", height = "440px")
      ),
      uiOutput("rq4_takeaway")
    ),
    nav_panel(
      title = "Total failure",
      value = "total_failure",
      section_intro(
        "Research question 5",
        "When does the entire network activate?",
        "Compare high- and low-centrality targets across graph families. Conditional update counts are shown only for cascades that activated all 20 vertices."
      ),
      div(
        class = "view-toggle",
        radioButtons(
          "failure_view", "Display",
          choices = c("How often?" = "completion", "How many updates?" = "steps"),
          selected = "completion",
          inline = TRUE
        )
      ),
      guided_plot_card(
        "Complete network activation",
        "Completion percentages use all replications; update means condition on the completed subset.",
        plotlyOutput("rq5_plot", height = "460px")
      ),
      uiOutput("rq5_takeaway")
    ),
    nav_panel(
      title = "Study & downloads",
      value = "provenance",
      section_intro(
        "Data provenance",
        "Where these results came from",
        "The app reads verified, archived simulation outputs. It does not rerun the simulation or modify the results."
      ),
      layout_columns(
        col_widths = c(7, 5),
        card(
          card_header("From simulation to display"),
          card_body(
            div(
              class = "data-flow",
              div(strong("1"), h3("Simulate"), p("Generate weighted graphs and paired target cascades.")),
              div(class = "flow-arrow", "→"),
              div(strong("2"), h3("Verify"), p("Recompute summaries, tables, and figures from 288,000 records.")),
              div(class = "flow-arrow", "→"),
              div(strong("3"), h3("Explore"), p("Read the archived summaries through focused snapshots."))
            )
          )
        ),
        card(
          card_header("Download the research package"),
          card_body(
            div(
              class = "download-stack",
              downloadButton("download_reproduction_script", "Download reproduction script", class = "btn-primary"),
              downloadButton("download_verified_bundle", "Download verified outputs", class = "btn-outline-primary"),
              downloadButton("download_dictionary", "Download data dictionary", class = "btn-outline-secondary")
            )
          )
        )
      ),
      div(
        class = "verification-strip",
        div(strong("Exact replay"), span("The complete simulation reproduced the archived scientific objects.")),
        div(strong("Read-only app"), span("No user interaction changes the stored results.")),
        div(strong("Open provenance"), span("Code, outputs, dictionary, and verification record are downloadable."))
      ),
      tags$details(
        class = "details-panel",
        tags$summary("Study design in brief"),
        tags$ul(
          tags$li("20 vertices per realized graph."),
          tags$li("Sparse graphs contain 20 edges; denser graphs contain 40 edges."),
          tags$li("Degree, strength, harmonic closeness, and betweenness define target positions."),
          tags$li("Thresholds are sampled within sensitive, moderate, elevated, and resilient regimes."),
          tags$li("Each displayed condition summarizes 1,000 Monte Carlo replications.")
        )
      ),
      tags$details(
        class = "details-panel",
        tags$summary("Technical verification record"),
        tags$pre(class = "provenance-record", textOutput("provenance_record"))
      )
    )
  )
)

server <- function(input, output, session) {
  restoring_query <- reactiveVal(TRUE)

  observeEvent(session$clientData$url_search, {
    req(!is.null(session$clientData$url_search))
    for (input_id in c(
      "view", "density", "metric", "threshold",
      "density_view", "threshold_view", "failure_view"
    )) {
      freezeReactiveValue(input, input_id)
    }
    restore_query_state(session, session$clientData$url_search)
    session$onFlushed(function() {
      later::later(function() restoring_query(FALSE), delay = 0.35)
    }, once = TRUE)
  }, once = TRUE, ignoreInit = FALSE)

  observeEvent(input$reset_filters, {
    updateSelectInput(session, "density", selected = query_defaults$density)
    updateSelectInput(session, "metric", selected = query_defaults$metric)
    updateSelectInput(session, "threshold", selected = query_defaults$threshold)
    updateRadioButtons(session, "density_view", selected = query_defaults$density_view)
    updateRadioButtons(session, "threshold_view", selected = query_defaults$threshold_view)
    updateRadioButtons(session, "failure_view", selected = query_defaults$failure_view)
  })

  observeEvent(
    list(
      input$view, input$density, input$metric, input$threshold,
      input$density_view, input$threshold_view, input$failure_view
    ),
    {
      req(!restoring_query())
      req(
        !is.null(input$view),
        length(input$density) == 1L,
        length(input$metric) == 1L,
        length(input$threshold) == 1L,
        length(input$density_view) == 1L,
        length(input$threshold_view) == 1L,
        length(input$failure_view) == 1L
      )
      updateQueryString(build_app_query(input), mode = "replace", session = session)
    },
    ignoreInit = TRUE
  )

  observeEvent(input$copy_view_link, {
    session$sendCustomMessage("copy-current-url", list())
  })

  observeEvent(input$copy_result, {
    if (isTRUE(input$copy_result$ok)) {
      showNotification("View link copied.", type = "message", duration = 2)
    } else {
      showNotification("The browser blocked clipboard access. Copy the address from the location bar.", type = "warning", duration = 5)
    }
  })

  navigation_targets <- c(
    go_graph_family = "graph_family",
    go_density = "density",
    go_centrality = "centrality",
    go_thresholds = "thresholds",
    go_total_failure = "total_failure"
  )
  for (button_id in names(navigation_targets)) {
    local({
      id <- button_id
      target <- navigation_targets[[button_id]]
      observeEvent(input[[id]], bslib::nav_select("view", selected = target, session = session))
    })
  }

  output$filter_summary <- renderUI({
    req(input$view)
    if (input$view == "overview") {
      return(div(class = "snapshot-summary", "Choose a research question to begin."))
    }
    if (input$view == "provenance") {
      return(div(class = "snapshot-summary", "Downloads contain the verified code and source results."))
    }
    req(input$density, input$metric, input$threshold)
    parts <- character()
    if (input$view %in% c("graph_family", "centrality", "thresholds", "total_failure")) {
      parts <- c(parts, unname(density_short_labels[input$density]))
    }
    if (input$view %in% c("graph_family", "density", "thresholds", "total_failure")) {
      parts <- c(parts, unname(metric_labels[input$metric]))
    }
    if (input$view %in% c("graph_family", "density", "centrality", "total_failure")) {
      parts <- c(parts, unname(threshold_short_labels[input$threshold]))
    }
    div(
      class = "snapshot-summary",
      span("Current snapshot"),
      strong(paste(parts, collapse = " · "))
    )
  })

  rq1_data <- reactive({
    req(input$density, input$metric, input$threshold)
    table2_summary[
      table2_summary$density_condition == input$density &
        table2_summary$metric == input$metric &
        table2_summary$threshold == input$threshold,
      , drop = FALSE
    ]
  })

  rq2_contrast_data <- reactive({
    req(input$metric, input$threshold)
    table2_summary[
      table2_summary$metric == input$metric &
        table2_summary$threshold == input$threshold,
      , drop = FALSE
    ]
  })

  rq2_extent_data <- reactive({
    req(input$metric, input$threshold)
    cascade_summary[
      cascade_summary$metric == input$metric &
        cascade_summary$threshold == input$threshold,
      , drop = FALSE
    ]
  })

  rq3_data <- reactive({
    req(input$density, input$threshold)
    table2_summary[
      table2_summary$density_condition == input$density &
        table2_summary$threshold == input$threshold,
      , drop = FALSE
    ]
  })

  rq4_contrast_data <- reactive({
    req(input$density, input$metric)
    table2_summary[
      table2_summary$density_condition == input$density &
        table2_summary$metric == input$metric,
      , drop = FALSE
    ]
  })

  rq4_extent_data <- reactive({
    req(input$density, input$metric)
    cascade_summary[
      cascade_summary$density_condition == input$density &
        cascade_summary$metric == input$metric,
      , drop = FALSE
    ]
  })

  rq5_data <- reactive({
    req(input$density, input$metric, input$threshold)
    cascade_summary[
      cascade_summary$density_condition == input$density &
        cascade_summary$metric == input$metric &
        cascade_summary$threshold == input$threshold &
        cascade_summary$target %in% c("Low", "High"),
      , drop = FALSE
    ]
  })

  output$rq1_plot <- renderPlotly(plot_contrast_snapshot(rq1_data(), "network"))
  output$rq2_plot <- renderPlotly({
    if (identical(input$density_view, "extent")) {
      plot_multifamily_extent_snapshot(rq2_extent_data(), "density_condition")
    } else {
      plot_multifamily_contrast_snapshot(rq2_contrast_data(), "density_condition")
    }
  })
  output$rq3_plot <- renderPlotly(plot_multifamily_contrast_snapshot(rq3_data(), "metric"))
  output$rq4_plot <- renderPlotly({
    if (identical(input$threshold_view, "extent")) {
      plot_multifamily_extent_snapshot(rq4_extent_data(), "threshold")
    } else {
      plot_multifamily_contrast_snapshot(rq4_contrast_data(), "threshold")
    }
  })
  output$rq5_plot <- renderPlotly({
    plot_total_failure_snapshot(rq5_data(), input$failure_view %||% "completion")
  })

  takeaway <- function(label, text) {
    div(class = "takeaway", span(label), strong(text))
  }

  output$rq1_takeaway <- renderUI({
    data <- add_display_columns(rq1_data())
    best <- data[which.max(data$mean_high_low_difference), , drop = FALSE]
    takeaway(
      "Snapshot finding",
      paste0(
        best$network_display, " has the largest high–low difference: ",
        format_number(100 * best$mean_high_low_difference, 1, " percentage points"), "."
      )
    )
  })

  output$rq2_takeaway <- renderUI({
    if (identical(input$density_view, "extent")) {
      data <- rq2_extent_data()
      data <- data[data$target %in% c("Low", "High"), , drop = FALSE]
      sparse <- data[data$density_condition == "Sparse", c("network", "target", "mean_final_active")]
      denser <- data[data$density_condition == "Denser", c("network", "target", "mean_final_active")]
      names(sparse)[[3]] <- "sparse_mean"
      names(denser)[[3]] <- "denser_mean"
      changes <- merge(sparse, denser, by = c("network", "target"))
      changes$change_pp <- 100 * (changes$denser_mean - changes$sparse_mean)
      largest <- changes[which.max(abs(changes$change_pp)), , drop = FALSE]
      direction <- if (largest$change_pp >= 0) "higher" else "lower"
      return(takeaway(
        "Snapshot finding",
        paste0(
          "Final activation shifts most for ", unname(network_labels[largest$network]),
          " with the ", tolower(unname(target_labels[largest$target])), " target: ",
          format_number(abs(largest$change_pp), 1, " points"), " ", direction,
          " in denser graphs."
        )
      ))
    }
    data <- rq2_contrast_data()
    sparse <- data[data$density_condition == "Sparse", c("network", "mean_high_low_difference")]
    denser <- data[data$density_condition == "Denser", c("network", "mean_high_low_difference")]
    names(sparse)[[2]] <- "sparse_mean"
    names(denser)[[2]] <- "denser_mean"
    changes <- merge(sparse, denser, by = "network")
    changes$change_pp <- 100 * (changes$denser_mean - changes$sparse_mean)
    largest <- changes[which.max(abs(changes$change_pp)), , drop = FALSE]
    direction <- if (largest$change_pp >= 0) "larger" else "smaller"
    takeaway(
      "Snapshot finding",
      paste0(
        "The high–low difference changes most for ", unname(network_labels[largest$network]),
        ": ", format_number(abs(largest$change_pp), 1, " points"), " ",
        direction, " in denser graphs."
      )
    )
  })

  output$rq3_takeaway <- renderUI({
    data <- add_display_columns(rq3_data())
    best <- data[which.max(data$mean_high_low_difference), , drop = FALSE]
    takeaway(
      "Snapshot finding",
      paste0(
        "The largest high–low difference is ",
        format_number(100 * best$mean_high_low_difference, 1, " percentage points"),
        " for ", best$network_display, " using ", tolower(best$metric_display), "."
      )
    )
  })

  output$rq4_takeaway <- renderUI({
    if (identical(input$threshold_view, "extent")) {
      data <- rq4_extent_data()
      high <- data[data$target == "High", , drop = FALSE]
      sensitive <- high[high$threshold == "0.10-0.20", c("network", "mean_final_active")]
      resilient <- high[high$threshold == "0.60-0.80", c("network", "mean_final_active")]
      names(sensitive)[[2]] <- "sensitive_mean"
      names(resilient)[[2]] <- "resilient_mean"
      changes <- merge(sensitive, resilient, by = "network")
      changes$decline_pp <- 100 * (changes$sensitive_mean - changes$resilient_mean)
      largest <- changes[which.max(abs(changes$decline_pp)), , drop = FALSE]
      return(takeaway(
        "Snapshot finding",
        paste0(
          "For ", unname(network_labels[largest$network]),
          ", final activation from the high-centrality target falls from ",
          format_number(100 * largest$sensitive_mean, 1, "%"), " under sensitive thresholds to ",
          format_number(100 * largest$resilient_mean, 1, "%"), " under resilient thresholds."
        )
      ))
    }
    data <- add_display_columns(rq4_contrast_data())
    best <- data[which.max(data$mean_high_low_difference), , drop = FALSE]
    takeaway(
      "Snapshot finding",
      paste0(
        "The difference is largest for ", best$network_display, " under ",
        tolower(as.character(best$threshold_short)), " thresholds: ",
        format_number(100 * best$mean_high_low_difference, 1, " percentage points"), "."
      )
    )
  })

  output$rq5_takeaway <- renderUI({
    data <- add_display_columns(rq5_data())
    if (identical(input$failure_view, "steps")) {
      estimable <- data[!is.na(data$mean_steps_to_complete_failure), , drop = FALSE]
      if (nrow(estimable) == 0L) {
        return(takeaway("Snapshot finding", "No selected cascade reached total failure, so update counts are not estimable."))
      }
      fastest <- estimable[which.min(estimable$mean_steps_to_complete_failure), , drop = FALSE]
      return(takeaway(
        "Snapshot finding",
        paste0(
          fastest$network_display, " with the ", tolower(fastest$target_display),
          " target requires the fewest updates among completed cascades: ",
          format_number(fastest$mean_steps_to_complete_failure, 2), "."
        )
      ))
    }
    highest <- data[which.max(data$percent_complete_failures), , drop = FALSE]
    takeaway(
      "Snapshot finding",
      paste0(
        "The highest completion percentage is ", format_number(highest$percent_complete_failures, 1, "%"),
        " for ", highest$network_display, " with the ", tolower(highest$target_display), " target."
      )
    )
  })

  current_snapshot <- reactive({
    view <- input$view %||% "overview"
    if (view == "graph_family") return(rq1_data())
    if (view == "density") {
      if (identical(input$density_view, "extent")) return(rq2_extent_data())
      return(rq2_contrast_data())
    }
    if (view == "centrality") return(rq3_data())
    if (view == "thresholds") {
      if (identical(input$threshold_view, "extent")) return(rq4_extent_data())
      return(rq4_contrast_data())
    }
    if (view == "total_failure") return(rq5_data())
    cascade_summary[
      cascade_summary$density_condition == input$density &
        cascade_summary$metric == input$metric &
        cascade_summary$threshold == input$threshold,
      , drop = FALSE
    ]
  })

  output$download_snapshot <- downloadHandler(
    filename = function() paste0("cascade-", input$view %||% "snapshot", "-", Sys.Date(), ".csv"),
    content = function(file) write.csv(current_snapshot(), file, row.names = FALSE, na = "")
  )

  output$download_reproduction_script <- downloadHandler(
    filename = function() "Manuscript_Simulation_Reproduction_CompleteFailure_20260817.R",
    content = function(file) {
      file.copy(
        file.path(app_directory, "source-data", "Manuscript_Simulation_Reproduction_CompleteFailure_20260817.R"),
        file,
        overwrite = TRUE
      )
    }
  )

  output$download_verified_bundle <- downloadHandler(
    filename = function() "verified-cascade-results-and-code.zip",
    contentType = "application/zip",
    content = function(file) {
      source_directory <- file.path(app_directory, "source-data")
      files <- list.files(source_directory, full.names = FALSE, recursive = FALSE)
      zip::zipr(file, files = files, root = source_directory)
    }
  )

  output$download_dictionary <- downloadHandler(
    filename = function() "cascade-results-data-dictionary.csv",
    content = function(file) {
      file.copy(file.path(app_directory, "data", "data_dictionary.csv"), file, overwrite = TRUE)
    }
  )

  output$provenance_record <- renderText({
    paste(
      readLines(file.path(app_directory, "source-data", "reproducibility_record.txt"), warn = FALSE),
      collapse = "\n"
    )
  })
}

shinyApp(ui = ui, server = server)
