# --- SAW-SIMODI-SURAD (SAW 3S) Shiny Application ---

# --- 0. Setup: Load Libraries ---
library(shiny)
library(bs4Dash)
library(tidyverse)
library(readr)
library(sf)
library(spdep)
library(maps)
library(rnaturalearth)
library(ggplot2)
library(RColorBrewer)
library(stringr)
library(classInt)
library(cowplot)
library(forecast) # Time Series Models
library(Metrics)  # sMAPE
library(nnfor)    # MLP
library(trend)    # Mann-Kendall, Theil-Sen
library(tseries)  # Stationarity Tests (ADF, PP, KPSS)
library(patchwork) # Combining plots
library(DT) # DataTables for results
library(zoo) # For na.approx

# Disable S2 for spatial operations (critical for poly2nb)
sf_use_s2(FALSE)
options(shiny.maxRequestSize = 900 * 1024^2)

# --- Configuration (Fixed Parameters for Forecasting) ---
forecast_end_year <- 2030
h_validation_years <- 2 # Length of the validation period (e.g., 2022-2023)

# --- Global Country Name Standardization Map ---
# Maps names found in the input CSV files (Data_Name) to the standardized 
# names used by rnaturalearth (Standard_Name).
country_name_standardization_map <- tribble(
  ~Data_Name, ~Standard_Name,
  # Regional/Aggregate Names (to be excluded)
  "Africa (WHO)", NA,
  "Africa Eastern and Southern", NA,
  "Africa Western and Central", NA,
  "Americas (WHO)", NA,
  "Arab World", NA,
  "Australia and New Zealand (UN)", NA,
  "Central Asia (UN)", NA,
  "Central and Southern Asia (UN)", NA,
  "Eastern Asia (UN)", NA,
  "Eastern Asia and South-Eastern Asia (UN)", NA,
  "Eastern Mediterranean (WHO)", NA,
  "Europe (WHO)", NA,
  "Euro area", NA,
  "European Union", NA,
  "Fragile and conflict affected situations", NA,
  "Heavily indebted poor countries (HIPC)", NA,
  "High income", NA,
  "IBRD only", NA,
  "IDA & IBRD total", NA,
  "IDA total", NA,
  "IDA blend", NA,
  "IDA only", NA,
  "Not classified", NA,
  "Latin America and the Caribbean (UN)", NA,
  "Latin America & Caribbean (excluding high income)", NA,
  "Latin America & the Caribbean (IDA & IBRD countries)", NA,
  "Least developed countries: UN classification", NA,
  "Low income", NA,
  "Lower middle income", NA,
  "Low & middle income", NA,
  "Middle East, North Africa, Afghanistan & Pakistan", NA,
  "Middle East, North Africa, Afghanistan & Pakistan (excluding high income)", NA,
  "Middle East, North Africa, Afghanistan & Pakistan (IDA & IBRD)", NA,
  "Middle income", NA,
  "Northern Africa (UN)", NA,
  "Northern Africa and Western Asia (UN)", NA,
  "Northern America and Europe (UN)", NA,
  "Oceania (exc. Australia and New Zealand) (UN)", NA,
  "OECD members", NA,
  "Other small states", NA,
  "Pacific island small states", NA,
  "Pre-demographic dividend", NA,
  "Post-demographic dividend", NA,
  "South Asia", NA,
  "South-East Asia (WHO)", NA,
  "South-Eastern Asia (UN)", NA,
  "Southern Asia (UN)", NA,
  "South Asia (IDA & IBRD)", NA,
  "Sub-Saharan Africa (UN)", NA,
  "Sub-Saharan Africa (excluding high income)", NA,
  "Sub-Saharan Africa (IDA & IBRD countries)", NA,
  "Sub-Saharan Africa", NA,
  "Small states", NA,
  "East Asia & Pacific (IDA & IBRD countries)", NA,
  "Europe & Central Asia (IDA & IBRD countries)", NA,
  "Upper middle income", NA,
  "Western Asia (UN)", NA,
  "Western Pacific (WHO)", NA,
  "World", NA,
  
  # --- Standardizations (Data Name -> Standard Name) ---
  # Matches for World Bank (WDI), Our World in Data, and GDL
  "Afghanistan", "Afghanistan",
  "Albania", "Albania",
  "Algeria", "Algeria",
  "Angola", "Angola",
  "Argentina", "Argentina",
  "Armenia", "Armenia",
  "Australia", "Australia",
  "Austria", "Austria",
  "Azerbaijan", "Azerbaijan",
  "Bahamas", "Bahamas",
  "Bahamas, The", "Bahamas",
  "Bahrain", "Bahrain",
  "Bangladesh", "Bangladesh",
  "Barbados", "Barbados",
  "Belarus", "Belarus",
  "Belgium", "Belgium",
  "Belize", "Belize",
  "Benin", "Benin",
  "Bhutan", "Bhutan",
  "Bolivia", "Bolivia",
  "Bosnia and Herzegovina", "Bosnia and Herz.",
  "Botswana", "Botswana",
  "Brazil", "Brazil",
  "Brunei", "Brunei",
  "Brunei Darussalam", "Brunei",
  "Bulgaria", "Bulgaria",
  "Burkina Faso", "Burkina Faso",
  "Burundi", "Burundi",
  "Cabo Verde", "Cabo Verde",
  "Cape Verde", "Cabo Verde",
  "Cambodia", "Cambodia",
  "Cameroon", "Cameroon",
  "Canada", "Canada",
  "Central African Republic", "Central African Rep.",
  "Central African Republic CAR", "Central African Rep.",
  "Chad", "Chad",
  "Chile", "Chile",
  "China", "China",
  "Colombia", "Colombia",
  "Comoros", "Comoros",
  "Congo", "Congo",
  "Congo, Rep.", "Congo",
  "Republic of Congo", "Congo",
  "Congo Brazzaville", "Congo",
  "Congo, Dem. Rep.", "Dem. Rep. Congo",
  "Democratic Republic of Congo", "Dem. Rep. Congo",
  "Democratic Republic of the Congo", "Dem. Rep. Congo",
  "Congo Democratic Republic", "Dem. Rep. Congo",
  "Costa Rica", "Costa Rica",
  "Cote d'Ivoire", "Côte d'Ivoire",
  "Côte d'Ivoire", "Côte d'Ivoire",
  "Ivory Coast", "Côte d'Ivoire",
  "Croatia", "Croatia",
  "Cuba", "Cuba",
  "Cyprus", "Cyprus",
  "Czechia", "Czechia",
  "Czech Republic", "Czechia",
  "Denmark", "Denmark",
  "Djibouti", "Djibouti",
  "Dominica", "Dominica",
  "Dominican Republic", "Dominican Rep.",
  "Ecuador", "Ecuador",
  "Egypt", "Egypt",
  "Egypt, Arab Rep.", "Egypt",
  "El Salvador", "El Salvador",
  "Equatorial Guinea", "Eq. Guinea",
  "Eritrea", "Eritrea",
  "Estonia", "Estonia",
  "Eswatini", "eSwatini",
  "Swaziland", "eSwatini",
  "Ethiopia", "Ethiopia",
  "Fiji", "Fiji",
  "Finland", "Finland",
  "France", "France",
  "Gabon", "Gabon",
  "Gambia", "Gambia",
  "Gambia, The", "Gambia",
  "Georgia", "Georgia",
  "Germany", "Germany",
  "Ghana", "Ghana",
  "Greece", "Greece",
  "Grenada", "Grenada",
  "Guatemala", "Guatemala",
  "Guinea", "Guinea",
  "Guinea-Bissau", "Guinea-Bissau",
  "Guinea Bissau", "Guinea-Bissau",
  "Guyana", "Guyana",
  "Haiti", "Haiti",
  "Honduras", "Honduras",
  "Hungary", "Hungary",
  "Iceland", "Iceland",
  "India", "India",
  "Indonesia", "Indonesia",
  "Iran", "Iran",
  "Iran, Islamic Rep.", "Iran",
  "Iraq", "Iraq",
  "Ireland", "Ireland",
  "Israel", "Israel",
  "Italy", "Italy",
  "Jamaica", "Jamaica",
  "Japan", "Japan",
  "Jordan", "Jordan",
  "Kazakhstan", "Kazakhstan",
  "Kenya", "Kenya",
  "Kiribati", "Kiribati",
  "Korea, Rep.", "South Korea",
  "South Korea", "South Korea",
  "Korea, Dem. People's Rep.", "North Korea",
  "North Korea", "North Korea",
  "Kuwait", "Kuwait",
  "Kyrgyzstan", "Kyrgyzstan",
  "Kyrgyz Republic", "Kyrgyzstan",
  "Laos", "Laos",
  "Lao PDR", "Laos",
  "Latvia", "Latvia",
  "Lebanon", "Lebanon",
  "Lesotho", "Lesotho",
  "Liberia", "Liberia",
  "Libya", "Libya",
  "Lithuania", "Lithuania",
  "Luxembourg", "Luxembourg",
  "Madagascar", "Madagascar",
  "Malawi", "Malawi",
  "Malaysia", "Malaysia",
  "Maldives", "Maldives",
  "Mali", "Mali",
  "Malta", "Malta",
  "Marshall Islands", "Marshall Islands",
  "Mauritania", "Mauritania",
  "Mauritius", "Mauritius",
  "Mexico", "Mexico",
  "Micronesia (country)", "Micronesia",
  "Micronesia, Fed. Sts.", "Micronesia",
  "Moldova", "Moldova",
  "Mongolia", "Mongolia",
  "Montenegro", "Montenegro",
  "Morocco", "Morocco",
  "Mozambique", "Mozambique",
  "Myanmar", "Myanmar",
  "Namibia", "Namibia",
  "Nauru", "Nauru",
  "Nepal", "Nepal",
  "Netherlands", "Netherlands",
  "New Zealand", "New Zealand",
  "Nicaragua", "Nicaragua",
  "Niger", "Niger",
  "Nigeria", "Nigeria",
  "North Macedonia", "North Macedonia",
  "Norway", "Norway",
  "Oman", "Oman",
  "Pakistan", "Pakistan",
  "Palau", "Palau",
  "Panama", "Panama",
  "Papua New Guinea", "Papua New Guinea",
  "Paraguay", "Paraguay",
  "Peru", "Peru",
  "Philippines", "Philippines",
  "Poland", "Poland",
  "Portugal", "Portugal",
  "Qatar", "Qatar",
  "Romania", "Romania",
  "Russia", "Russia",
  "Russian Federation", "Russia",
  "Rwanda", "Rwanda",
  "Saint Kitts and Nevis", "St. Kitts and Nevis",
  "St. Kitts and Nevis", "St. Kitts and Nevis",
  "Saint Lucia", "St. Lucia",
  "St. Lucia", "St. Lucia",
  "Saint Vincent and the Grenadines", "St. Vincent and the Grenadines",
  "St. Vincent and the Grenadines", "St. Vincent and the Grenadines",
  "Samoa", "Samoa",
  "San Marino", "San Marino",
  "Sao Tome and Principe", "São Tomé and Principe",
  "Sao Tome & Principe", "São Tomé and Principe",
  "Saudi Arabia", "Saudi Arabia",
  "Senegal", "Senegal",
  "Serbia", "Serbia",
  "Seychelles", "Seychelles",
  "Sierra Leone", "Sierra Leone",
  "Singapore", "Singapore",
  "Slovakia", "Slovakia",
  "Slovak Republic", "Slovakia",
  "Slovenia", "Slovenia",
  "Solomon Islands", "Solomon Is.",
  "Somalia", "Somalia",
  "Somalia, Fed. Rep.", "Somalia",
  "South Africa", "South Africa",
  "South Sudan", "S. Sudan",
  "Spain", "Spain",
  "Sri Lanka", "Sri Lanka",
  "Sudan", "Sudan",
  "Suriname", "Suriname",
  "Sweden", "Sweden",
  "Switzerland", "Switzerland",
  "Syria", "Syria",
  "Syrian Arab Republic", "Syria",
  "Tajikistan", "Tajikistan",
  "Tanzania", "Tanzania",
  "Thailand", "Thailand",
  "Timor-Leste", "Timor-Leste",
  "East Timor", "Timor-Leste",
  "Togo", "Togo",
  "Tonga", "Tonga",
  "Trinidad and Tobago", "Trinidad and Tobago",
  "Tunisia", "Tunisia",
  "Turkey", "Turkey",
  "Turkiye", "Turkey",
  "Turkmenistan", "Turkmenistan",
  "Tuvalu", "Tuvalu",
  "Uganda", "Uganda",
  "Ukraine", "Ukraine",
  "United Arab Emirates", "United Arab Emirates",
  "United Kingdom", "United Kingdom",
  "UK", "United Kingdom",
  "United States", "United States of America",
  "USA", "United States of America",
  "Uruguay", "Uruguay",
  "Uzbekistan", "Uzbekistan",
  "Vanuatu", "Vanuatu",
  "Venezuela", "Venezuela",
  "Venezuela, RB", "Venezuela",
  "Vietnam", "Vietnam",
  "Viet Nam", "Vietnam",
  "Yemen", "Yemen",
  "Yemen, Rep.", "Yemen",
  "Zambia", "Zambia",
  "Zimbabwe", "Zimbabwe",
  "Hong Kong SAR, China", "Hong Kong",
  "Macao SAR, China", "Macao",
  "West Bank and Gaza", "Palestine",
  "Palestine", "Palestine",
  "Curacao", "Curaçao"
)

# --- Maps Package Name Lookup (Standard Name -> Maps Name) ---
# Used when the user selects the 'maps' package as the source.
maps_name_lookup <- tribble(
  ~Standard_Name, ~Maps_Name,
  "United States of America", "USA",
  "Dem. Rep. Congo", "Democratic Republic of the Congo",
  "Congo", "Republic of Congo",
  "Côte d'Ivoire", "Ivory Coast",
  "eSwatini", "Swaziland",
  "São Tomé and Principe", "Sao Tome and Principe",
  "St. Kitts and Nevis", "Saint Kitts", # Maps splits this
  "St. Lucia", "Saint Lucia",
  "St. Vincent and the Grenadines", "Saint Vincent", # Maps splits this
  "Trinidad and Tobago", "Trinidad", # Maps splits this
  "Trinidad and Tobago", "Tobago",
  "Timor-Leste", "Timor-Leste",
  "United Kingdom", "UK",
  "North Macedonia", "North Macedonia",
  "Bosnia and Herz.", "Bosnia and Herzegovina",
  "Czechia", "Czech Republic",
  "Eq. Guinea", "Equatorial Guinea",
  "S. Sudan", "South Sudan",
  "Central African Rep.", "Central African Republic",
  "Micronesia", "Micronesia",
  "North Korea", "North Korea",
  "South Korea", "South Korea",
  "Yemen", "Yemen",
  "British Virgin Is.", "Virgin Islands, British",
  "U.S. Virgin Is.", "Virgin Islands, US",
  "Curaçao", "Curacao",
  "Hong Kong", "Hong Kong",
  "Macao", "Macao",
  "Palestine", "Palestine",
  "Taiwan", "Taiwan",
  "Vanuatu", "Vanuatu",
  "Samoa", "Samoa",
  "Solomon Is.", "Solomon Islands",
  "Kiribati", "Kiribati",
  "Tonga", "Tonga",
  "Tuvalu", "Tuvalu",
  "Nauru", "Nauru",
  "New Caledonia", "New Caledonia",
  "French Polynesia", "French Polynesia",
  "Cayman Islands", "Cayman Islands",
  "Turks and Caicos Is.", "Turks and Caicos Islands",
  "Gibraltar", "Gibraltar",
  "Isle of Man", "Isle of Man",
  "Jersey", "Jersey",
  "Guernsey", "Guernsey",
  "Greenland", "Greenland",
  "Faroe Islands", "Faroe Islands",
  "Puerto Rico", "Puerto Rico",
  "American Samoa", "American Samoa",
  "Guam", "Guam",
  "Northern Mariana Is.", "Northern Mariana Islands",
  "Niue", "Niue",
  "Wallis and Futuna", "Wallis and Futuna",
  "Saint Martin", "Saint Martin",
  "Sint Maarten", "Sint Maarten",
  "Bonaire", "Bonaire",
  "Heard I. and McDonald Is.", "Heard Island",
  "Cocos (Keeling) Is.", "Cocos Islands",
  "Christmas Island", "Christmas Island",
  "Chagos Archipelago", "Chagos Archipelago",
  "Ascension Island", "Ascension Island",
  "Saint Helena", "Saint Helena",
  "South Georgia and the South Sandwich Islands", "South Georgia",
  "Falkland Is.", "Falkland Islands",
  "Reunion", "Reunion",
  "Mayotte", "Mayotte",
  "French Guiana", "French Guiana",
  "Martinique", "Martinique",
  "Guadeloupe", "Guadeloupe",
  "Western Sahara", "Western Sahara",
  "Vatican", "Vatican"
)


# Custom CSS for Amoud University Branding
custom_theme <- "
  .main-header .logo { background-color: #006400 !important; color: #FFD700 !important; font-weight: bold; }
  .main-header .navbar { background-color: #006400 !important; }
  .sidebar-dark-primary { background-color: #004d00 !important; }
  .btn-success { background-color: #006400 !important; border-color: #FFD700 !important; color: #FFD700 !important; }
  .btn-info { background-color: #004d00 !important; border-color: #FFD700 !important; }
  .nav-pills .nav-link.active, .nav-pills .show > .nav-link { background-color: #006400 !important; color: #FFD700 !important; }
"

# --- UI Definition ---
ui <- dashboardPage(
  dark = NULL,
  header = dashboardHeader(title = "Saylac"),
  sidebar = dashboardSidebar(
    sidebarMenu(
      menuItem("1. Introduction (SAW)", tabName = "intro", icon = icon("info-circle")),
      menuItem("2. Data Setup & Snapshot", tabName = "setup", icon = icon("database")),
      menuItem("3. Spatial Diagnostics (ESDA)", tabName = "esda", icon = icon("map-marked-alt")),
      menuItem("4. Time Series Diagnostics (SIMODI)", tabName = "simodi", icon = icon("chart-line")),
      menuItem("5. Forecast & Spatial Results (SURAD)", tabName = "surad", icon = icon("globe-americas"))
    )
  ),
  body = dashboardBody(
    tags$head(tags$style(HTML(custom_theme))),
    tabItems(
      # --- 1. Introduction Tab (SAW) ---
      tabItem(tabName = "intro",
              jumbotron(
                title = "Saylac: Spatial Analysis of Yearly, Longitudinal, and Areal Change",
                lead = "Multidisciplinary spatial, longitudinal, time-series, forecasting, and reporting dashboard",
                status = "success",
                "Saylac is a multidisciplinary platform for global, national, and regional indicators across fields such as health, education, poverty, economy, environment, demography, infrastructure, and governance. SAYLAC stands for Spatial Analysis of Yearly, Longitudinal, and Areal Change."
              ),
              fluidRow(
                box(title = "Core Functionality", width = 12, status = "primary", solidHeader = TRUE,
                    tags$ul(
                      tags$li(strong("SAW (Spatial and Areal Weighted):"), "Exploratory Spatial Data Analysis (ESDA) for global, national, and regional indicators."),
                      tags$li(strong("SIMODI (Single Model Diagnostics):"), "Comparative forecasting using 8 advanced time series models (ARIMA, ETS, NNAR, MLP, etc.) for country-specific optimal model selection."),
                      tags$li(strong("SURAD (Spatial Uncertainty and Reporting Analysis Dashboard):"), "Visualization of final forecasts, spatial clustering, uncertainty-aware interpretation, and report generation.")
                    )
                )
              )
      ),
      # --- 2. Data Setup Tab ---
      tabItem(tabName = "setup",
              fluidRow(
                box(title = "1. Data Ingestion", width = 4, status = "primary",
                    fileInput("data_file", "Upload Country-Level Data (.csv)", accept = ".csv"),
                    
                    radioButtons("map_source", "Select Map Source:", 
                                 choices = c("rnaturalearth (Standardized)" = "rnaturalearth", 
                                             "maps (Legacy/More Regions)" = "maps"),
                                 selected = "rnaturalearth"),
                    
                    selectInput("region_filter", "Filter Analysis Scope:", 
                                choices = c("Global", 
                                            "Africa (All)" = "Africa",
                                            "Africa - Eastern" = "Eastern Africa",
                                            "Africa - Western" = "Western Africa",
                                            "Africa - Middle" = "Middle Africa",
                                            "Africa - Northern" = "Northern Africa",
                                            "Africa - Southern" = "Southern Africa",
                                            "Asia", "Europe", "Americas", "Oceania"), 
                                selected = "Africa"),
                    actionButton("process_data", "Load & Prepare Snapshot", class = "btn-success btn-block")
                ),
                box(title = "2. Core Variables & Snapshot Year", width = 8, status = "primary",
                    fluidRow(
                      column(6, selectInput("v_indicator", "Indicator Column (Y):", choices = NULL)),
                      column(6, selectInput("v_year", "Snapshot Year:", choices = NULL))
                    ),
                    hr(),
                    h5("Data Preview:"),
                    tableOutput("data_preview")
                )
              )
      ),
      # --- 3. Spatial Diagnostics Tab (ESDA) ---
      tabItem(tabName = "esda",
              fluidRow(
                box(title = "ESDA Diagnostic Selector", width = 3, status = "success",
                    selectInput("esda_method", "Select Method:", 
                                choices = c("1. Basic Choropleth", "2. Global Moran's I", "3. Moran Scatterplot", 
                                            "4. LISA Cluster Map", "5. Global Geary's C Map", "6. Spatial Correlogram", 
                                            "7. Connectivity Graph", 
                                            "8. Relative Risk (RR) Map", 
                                            "9. Spatiotemporal Persistence Map")) # <--- Added these two)
                ),
                box(title = "Spatial Diagnostic Output", width = 9, plotOutput("diag_plot", height = "600px"))
              )
      ),
      # --- 4. Time Series Diagnostics Tab (SIMODI) ---
      tabItem(tabName = "simodi",
              fluidRow(
                box(title = "Time Series Configuration", width = 4, status = "info",
                    uiOutput("simodi_config_info"), 
                    hr(),
                    actionButton("run_forecasting", "Run 8-Model Comparative Forecast", class = "btn-info btn-block"),
                    hr(),
                    selectInput("country_select_diag", "Select Country for Diagnostics:", choices = NULL)
                ),
                box(title = "Stationarity & Trend Tests", width = 8, 
                    tabsetPanel(
                      tabPanel("Stationarity (ADF/PP/KPSS)", verbatimTextOutput("stationarity_output")),
                      tabPanel("Trend (Mann-Kendall/Theil-Sen)", verbatimTextOutput("trend_output"))
                    )
                ),
                # ... existing Stationarity box ...
                box(title = "Comparative Trend Analysis (Theil-Sen Slope)", width = 12, status = "warning", solidHeader = TRUE,
                    plotOutput("trend_comparison_plot", height = "600px")
                ),
                # ... existing Model Performance table ...
              ),
              fluidRow(
                box(title = "Model Performance (sMAPE)", width = 12, 
                    DTOutput("performance_table"))
              )
      ),
      # --- 5. Forecast & Spatial Results Tab (SURAD) ---
      tabItem(tabName = "surad",
              fluidRow(
                box(title = "Forecast Visualization (Best Model)", width = 12, status = "primary",
                    plotOutput("combined_forecast_plot", height = "500px"))
              ),
              
              # --- Global Spatial Statistics Section ---
              fluidRow(
                column(width = 12, h4("Global Spatial Statistics (Forecasted Year)")),
                box(title = "Global Moran's I (Autocorrelation)", width = 6, status = "info",
                    plotOutput("surad_moran_curve", height = "300px")),
                box(title = "Global Geary's C (Similarity)", width = 6, status = "info",
                    plotOutput("surad_geary_curve", height = "300px"))
              ),
              
              # --- Local Spatial Maps Section ---
              fluidRow(
                column(width = 12, h4("Local Spatial Clustering & Dependency Maps")),
                box(title = "LISA Cluster Map (Local Moran)", width = 4, status = "success",
                    plotOutput("lisa_2030_plot", height = "350px")),
                box(title = "Getis-Ord Gi* (Hotspot Analysis)", width = 4, status = "success",
                    plotOutput("gi_star_plot", height = "350px")),
                box(title = "Spatial Dependency (Lag) Map", width = 4, status = "success",
                    plotOutput("spatial_lag_plot", height = "350px"))
              ),
              
              # --- Tables and Reporting ---
              fluidRow(
                box(title = "Forecasted Risk Analysis", width = 8, 
                    DTOutput("gap_table")),
                box(title = "Export Publication Report", width = 4, status = "warning",
                    textInput("rep_title", "Report Title:", "Saylac Analysis"),
                    radioButtons("format", "Format:", choices = c("HTML" = "html", "PDF" = "pdf", "Word" = "word")),
                    downloadButton("dl_report", "Download Full Report", class = "btn-warning btn-block")
                )
              )
      )
    )
  )
)
# --- Server Definition ---
server <- function(input, output, session) {
  
  # Reactive values to store dynamic configuration
  rv <- reactiveValues(
    historical_end_year = NULL,
    forecast_start_year = NULL
  )
  
  # --- Data Loading and Setup (SAW Logic) ---
  raw_data <- reactive({
    req(input$data_file)
    df <- read_csv(input$data_file$datapath, show_col_types = FALSE)
    
    # --- AUTO-DETECT WIDE FORMAT AND PIVOT ---
    # Check if we have a "Year" column. If not, assume it's Wide (like WDI data)
    cols <- names(df)
    has_year_col <- any(str_detect(cols, regex("^year$", ignore_case = TRUE)))
    
    if (!has_year_col) {
      # Try to identify columns that are 4-digit years (e.g., "1960", "2020")
      year_cols <- cols[str_detect(cols, "^\\d{4}$")]
      
      if (length(year_cols) > 0) {
        # We found year columns, so it's likely Wide format.
        # Pivot longer. We assume non-year columns are identifiers.
        # Typical WDI identifiers: Country Name, Country Code, Indicator Name...
        
        # We need to preserve the Country column name. 
        # Find column with "Country" or "Entity" in name
        id_cols <- cols[!cols %in% year_cols]
        
        df <- df %>%
          pivot_longer(cols = all_of(year_cols), names_to = "Year", values_to = "Value") %>%
          mutate(Year = as.numeric(Year), Value = as.numeric(Value)) %>%
          filter(!is.na(Value)) # Remove empty values common in WDI data
      }
    }
    
    return(df)
  })
  
  observe({
    df <- raw_data()
    vars <- names(df)
    
    # Try to find the Country column
    country_col <- vars[str_detect(vars, regex("country|entity", ignore_case = TRUE))][1]
    
    # Try to find the Year column
    year_col <- vars[str_detect(vars, regex("year", ignore_case = TRUE))][1]
    
    # Try to find the Indicator column (Value)
    # If we pivoted, it's "Value". If original Long, it's the numeric column that isn't Year/Code.
    if ("Value" %in% vars) {
      indicator_vars <- "Value"
    } else {
      # Exclude Year, Code, Country columns
      indicator_vars <- vars[!str_detect(vars, regex("year|code|entity|country", ignore_case = TRUE))]
      # Further filter to keep only numeric columns
      numeric_cols <- vars[sapply(df, is.numeric)]
      indicator_vars <- intersect(indicator_vars, numeric_cols)
    }
    
    updateSelectInput(session, "v_indicator", choices = indicator_vars)
    if (!is.na(year_col)) {
      updateSelectInput(session, "v_year", choices = sort(unique(df[[year_col]]), decreasing = TRUE))
    }
  })
  
  output$data_preview <- renderTable({
    req(raw_data())
    head(raw_data(), 5)
  })
  
  # --- Spatial Snapshot (SAW Logic) ---
  spatial_snapshot <- eventReactive(input$process_data, {
    req(input$v_indicator, input$v_year, input$map_source)
    
    df <- raw_data()
    country_col <- names(df)[str_detect(names(df), regex("country|entity", ignore_case = TRUE))][1]
    year_col <- names(df)[str_detect(names(df), regex("year", ignore_case = TRUE))][1]
    
    # 1. Prepare input data snapshot
    df_snapshot <- df %>%
      filter(!!sym(year_col) == input$v_year) %>%
      select(Data_Name = !!sym(country_col), Value = !!sym(input$v_indicator)) %>%
      mutate(Data_Name = str_trim(Data_Name))
    
    # 2. Harmonize input data names
    df_harmonized <- df_snapshot %>%
      left_join(country_name_standardization_map, by = "Data_Name") %>%
      mutate(Country_Standard = coalesce(Standard_Name, Data_Name)) %>% 
      filter(!is.na(Value))
    
    # 3. Load spatial data
    if (input$map_source == "rnaturalearth") {
      world_map_data <- ne_countries(scale = "small", returnclass = "sf") %>%
        st_make_valid() %>%
        rename(Country_Map = name) %>%
        select(Country_Map, continent, geometry)
      
      joined_sf <- world_map_data %>%
        left_join(df_harmonized, by = c("Country_Map" = "Country_Standard")) %>%
        rename(Country_Standard = Country_Map) 
      
    } else { 
      world_map_data_raw <- map("world", fill = TRUE, plot = FALSE)
      world_map_data <- st_as_sf(world_map_data_raw) %>%
        st_make_valid() %>%
        rename(Country_Map = ID) %>%
        mutate(Country_Map = str_trim(Country_Map))
      
      geom_col_name <- attr(world_map_data, "sf_column")
      
      df_mapped_names <- df_harmonized %>%
        left_join(maps_name_lookup, by = c("Country_Standard" = "Standard_Name"), relationship = "many-to-many") %>%
        mutate(Maps_Name = coalesce(Maps_Name, Country_Standard)) %>%
        select(Country_Standard, Value, Maps_Name)
      
      joined_sf <- world_map_data %>%
        left_join(df_mapped_names, by = c("Country_Map" = "Maps_Name"), relationship = "many-to-many") %>%
        group_by(Country_Map) %>%
        arrange(desc(!is.na(Value))) %>%
        slice(1) %>%
        ungroup() %>%
        rename(geometry = !!sym(geom_col_name))
    }
    
    # 4. Dynamic Region Filtering
    if (input$region_filter != "Global") {
      ne_ref <- ne_countries(scale = "small", returnclass = "sf") 
      
      # Check if the selection is a specific subregion (contains "Africa" but isn't just "Africa")
      if (input$region_filter %in% c("Eastern Africa", "Western Africa", "Middle Africa", "Northern Africa", "Southern Africa")) {
        
        countries_in_region <- ne_ref %>% 
          filter(subregion == input$region_filter) %>% 
          pull(name)
        
      } else {
        # Standard Continent Filtering
        target_continent <- switch(input$region_filter,
                                   "Africa" = "Africa",
                                   "Asia" = "Asia",
                                   "Europe" = "Europe",
                                   "Americas" = c("North America", "South America"),
                                   "Oceania" = "Oceania")
        
        countries_in_region <- ne_ref %>% 
          filter(continent %in% target_continent) %>% 
          pull(name)
      }
      
      # Add manual islands/exceptions if selecting whole Africa
      if(input$region_filter == "Africa") {
        countries_in_region <- c(countries_in_region, "Cabo Verde", "São Tomé and Principe", "eSwatini", "Côte d'Ivoire")
      }
      
      joined_sf <- joined_sf %>%
        filter(Country_Standard %in% countries_in_region)
    }
    
    joined_sf_clean <- joined_sf %>% filter(!is.na(Value))
    
    # --- Dynamic Year Calculation ---
    max_year <- max(df[[year_col]], na.rm = TRUE)
    rv$historical_end_year <- max_year
    rv$forecast_start_year <- max_year + 1
    
    validate(need(nrow(joined_sf_clean) > 3, paste("Error: Only", nrow(joined_sf_clean), "countries found for this region/year.")))
    
    return(joined_sf_clean)
  })
  
  # --- Persistence Logic (Separate Reactive) ---
  persistence_data <- reactive({
    req(input$process_data)
    df <- raw_data()
    country_col <- names(df)[str_detect(names(df), regex("country|entity", ignore_case = TRUE))][1]
    year_col <- names(df)[str_detect(names(df), regex("year", ignore_case = TRUE))][1]
    
    years <- sort(unique(df[[year_col]]))
    persistence_list <- list()
    
    # Get base geometry from the snapshot
    geo_base <- spatial_snapshot() %>% select(Country_Standard, geometry)
    
    for(y in years) {
      df_y <- df %>% filter(!!sym(year_col) == y) %>% 
        select(Data_Name = !!sym(country_col), Value = !!sym(input$v_indicator)) %>%
        left_join(country_name_standardization_map, by = "Data_Name") %>%
        mutate(Country_Standard = coalesce(Standard_Name, Data_Name))
      
      joined <- geo_base %>% 
        left_join(df_y, by = "Country_Standard") %>% 
        filter(!is.na(Value))
      
      if(nrow(joined) > 5) {
        nb <- poly2nb(joined, queen=TRUE)
        if(any(card(nb) == 0)) {
          coords <- st_coordinates(st_centroid(joined))
          nb_knn <- knn2nb(knearneigh(coords, k = 1))
          nb <- union.nb(nb, nb_knn)
        }
        
        lw <- nb2listw(nb, style="W", zero.policy=T)
        loc_m <- localmoran(joined$Value, lw, zero.policy=T)
        m_val <- mean(joined$Value, na.rm=T)
        lag_val <- lag.listw(lw, joined$Value, zero.policy=T)
        
        joined$is_hh <- ifelse(loc_m[,5] <= 0.05 & joined$Value > m_val & lag_val > m_val, 1, 0)
        persistence_list[[as.character(y)]] <- st_drop_geometry(joined) %>% select(Country_Standard, is_hh)
      }
    }
    
    bind_rows(persistence_list) %>% 
      group_by(Country_Standard) %>% 
      summarize(Years_as_Hotspot = sum(is_hh, na.rm=T))
  })
  
  
  # Server output for SIMODI configuration info
  output$simodi_config_info <- renderUI({
    req(rv$historical_end_year)
    
    historical_end_year_dynamic <- rv$historical_end_year
    forecast_start_year_dynamic <- rv$forecast_start_year
    
    tagList(
      p(strong("Historical Period:")),
      p(paste0("Data used for training: Start - ", historical_end_year_dynamic - h_validation_years)),
      p(paste0("Validation Period: ", historical_end_year_dynamic - h_validation_years + 1, " - ", historical_end_year_dynamic)),
      p(paste0("Forecast Horizon: ", forecast_start_year_dynamic, " - ", forecast_end_year))
    )
  })
  
  # --- Time Series Data Preparation (SIMODI Logic) ---
  time_series_data <- reactive({
    req(input$process_data, rv$historical_end_year)
    df <- raw_data()
    country_col <- names(df)[str_detect(names(df), regex("country|entity", ignore_case = TRUE))][1]
    year_col <- names(df)[str_detect(names(df), regex("year", ignore_case = TRUE))][1]
    
    # 1. Get the list of standardized countries that successfully joined the map
    valid_countries_sf <- spatial_snapshot()
    valid_countries <- unique(valid_countries_sf$Country_Standard)
    
    # 2. Harmonize raw data names to Standard Names for filtering
    df_harmonized_raw <- df %>%
      rename(Data_Name = !!sym(country_col), Year = !!sym(year_col), Value = !!sym(input$v_indicator)) %>%
      left_join(country_name_standardization_map, by = "Data_Name") %>%
      # Important: If not in map, try keeping Data_Name, but valid_countries filter will likely drop it
      mutate(Country = coalesce(Standard_Name, Data_Name)) %>%
      filter(Country %in% valid_countries) %>%
      select(Country, Year, Value)
    
    # 3. Prepare time series data (Relaxing the start year requirement)
    ts_data_raw <- df_harmonized_raw %>%
      filter(Year <= rv$historical_end_year) %>%
      arrange(Country, Year) %>%
      group_by(Country) %>%
      mutate(N_data = sum(!is.na(Value))) %>%
      ungroup()
    
    # Filter out countries with insufficient data (e.g., less than 5 years)
    ts_data_filtered <- ts_data_raw %>%
      filter(N_data >= 5) %>%
      select(Country, Year, Value)
    
    # 4. Interpolate missing values and standardize the time series structure
    ts_data <- ts_data_filtered %>%
      group_by(Country) %>%
      # Find the actual start year for this country's data
      mutate(Min_Year = min(Year)) %>%
      ungroup() %>%
      # Create a complete sequence for each country from its Min_Year to historical_end_year
      group_by(Country) %>%
      complete(Year = seq(Min_Year[1], rv$historical_end_year, by = 1)) %>%
      # Interpolate missing values (NA) within the series
      mutate(Value = zoo::na.approx(Value, na.rm = FALSE, rule = 2)) %>%
      # Drop rows where interpolation failed
      filter(!is.na(Value)) %>%
      ungroup()
    
    updateSelectInput(session, "country_select_diag", choices = sort(unique(ts_data$Country)))
    
    return(ts_data)
  })
  
  # Reactive for selected country's time series
  selected_ts <- reactive({
    req(input$country_select_diag)
    ts_data <- time_series_data()
    country_data <- ts_data %>% filter(Country == input$country_select_diag)
    
    if (nrow(country_data) == 0) return(NULL)
    
    start_year_ts <- min(country_data$Year)
    ts(country_data$Value, frequency = 1, start = start_year_ts)
  })
  
  output$stationarity_output <- renderPrint({
    ts_data <- selected_ts()
    req(ts_data)
    
    cat("--- ADF Test (Augmented Dickey-Fuller) ---\n")
    tryCatch(print(tseries::adf.test(ts_data)), error = function(e) print("Error: Data insufficient for ADF test"))
    
    cat("\n--- PP Test (Phillips-Perron) ---\n")
    tryCatch(print(tseries::pp.test(ts_data)), error = function(e) print("Error: Data insufficient for PP test"))
    
    cat("\n--- KPSS Test ---\n")
    tryCatch(print(tseries::kpss.test(ts_data)), error = function(e) print("Error: Data insufficient for KPSS test"))
  })
  
  output$trend_output <- renderPrint({
    ts_data <- selected_ts()
    req(ts_data)
    
    cat("--- Mann-Kendall Trend Test ---\n")
    tryCatch(print(trend::mk.test(ts_data)), error = function(e) print("Error: Data insufficient for MK test"))
    
    cat("\n--- Theil-Sen Slope Estimator ---\n")
    tryCatch(print(trend::sens.slope(ts_data)), error = function(e) print("Error: Data insufficient for Theil-Sen"))
  })
  # --- New Reactive: Calculate Trends for All Countries ---
  trend_analysis_results <- reactive({
    req(time_series_data())
    ts_df <- time_series_data()
    
    # Calculate Theil-Sen and Mann-Kendall for every country
    results <- ts_df %>%
      group_by(Country) %>%
      summarise(
        mk_p_value = tryCatch(trend::mk.test(Value)$p.value, error = function(e) NA),
        sen_slope = tryCatch(trend::sens.slope(Value)$estimates, error = function(e) NA),
        .groups = 'drop'
      ) %>%
      filter(!is.na(sen_slope)) %>%
      mutate(
        significance = ifelse(mk_p_value < 0.05, "Significant (p < 0.05)", "Non-significant"),
        Country = fct_reorder(Country, sen_slope)
      )
    
    return(results)
  })
  
  # --- New Output: Trend Comparison Plot ---
  output$trend_comparison_plot <- renderPlot({
    req(trend_analysis_results())
    df <- trend_analysis_results()
    
    ggplot(df, aes(x = Country, y = sen_slope, fill = significance)) +
      geom_col() +
      coord_flip() +
      scale_fill_manual(values = c("Significant (p < 0.05)" = "#006400", "Non-significant" = "grey70")) +
      labs(
        title = paste("Rate of Change in", input$v_indicator, "per Year"),
        subtitle = "Calculated using Theil-Sen Slope Estimator & Mann-Kendall Test",
        x = "Country",
        y = paste("Annual Change in", input$v_indicator),
        fill = "Trend Significance"
      ) +
      theme_minimal() +
      theme(legend.position = "bottom", 
            axis.text.y = element_text(size = 10))
  })
  
  # --- Updated Full Forecasting Engine (8 Models + CI) ---
  forecasting_results <- eventReactive(input$run_forecasting, {
    req(rv$historical_end_year, rv$forecast_start_year)
    
    historical_end_year_dynamic <- rv$historical_end_year
    forecast_start_year_dynamic <- rv$forecast_start_year
    h_future_years <- forecast_end_year - forecast_start_year_dynamic + 1
    
    ts_data <- time_series_data()
    unique_countries <- unique(ts_data$Country)
    
    all_accuracy_metrics <- list()
    all_future_forecasts <- list()
    
    withProgress(message = 'Running 8-Model Comparative Forecast...', value = 0, {
      for (i in seq_along(unique_countries)) {
        current_country <- unique_countries[i]
        incProgress(1/length(unique_countries), detail = paste("Country:", current_country))
        
        country_data <- ts_data %>% filter(Country == current_country)
        country_ts <- ts(country_data$Value, frequency = 1, start = min(country_data$Year))
        
        if (length(country_ts) <= h_validation_years + 2) next 
        
        train_val <- subset(country_ts, end = length(country_ts) - h_validation_years)
        test_val <- subset(country_ts, start = length(country_ts) - h_validation_years + 1)
        
        country_metrics_df <- data.frame()
        country_future_forecasts_list <- list()
        
        # Helper to run models and extract CI
        run_model <- function(model_name, fit_func, is_forecast_func = FALSE) {
          tryCatch({
            # 1. Validation phase
            if(is_forecast_func) {
              f_val <- fit_func(train_val, h_validation_years)
            } else {
              m_val <- fit_func(train_val); f_val <- forecast::forecast(m_val, h = h_validation_years)
            }
            
            acc <- forecast::accuracy(f_val, test_val)
            smap_val <- Metrics::smape(as.numeric(test_val), as.numeric(f_val$mean))
            
            # 2. Final Forecast phase
            if(is_forecast_func) {
              f_final <- fit_func(country_ts, h_future_years)
            } else {
              m_final <- fit_func(country_ts)
              # NNAR needs PI=TRUE for intervals
              if(model_name == "NNAR") {
                f_final <- forecast::forecast(m_final, h = h_future_years, PI = TRUE, npaths = 100)
              } else {
                f_final <- forecast::forecast(m_final, h = h_future_years)
              }
            }
            
            country_metrics_df <<- rbind(country_metrics_df, data.frame(
              Country = current_country, Model = model_name, 
              RMSE = acc[2, "RMSE"], MAE = acc[2, "MAE"], sMAPE = smap_val
            ))
            
            country_future_forecasts_list[[model_name]] <<- tibble(
              Country = current_country, Model = model_name, 
              Year = seq(from = forecast_start_year_dynamic, to = forecast_end_year),
              Forecast = as.numeric(f_final$mean),
              Lo80 = if(!is.null(f_final$lower)) as.numeric(f_final$lower[,1]) else as.numeric(f_final$mean),
              Hi80 = if(!is.null(f_final$upper)) as.numeric(f_final$upper[,1]) else as.numeric(f_final$mean),
              Lo95 = if(!is.null(f_final$lower)) as.numeric(f_final$lower[,2]) else as.numeric(f_final$mean),
              Hi95 = if(!is.null(f_final$upper)) as.numeric(f_final$upper[,2]) else as.numeric(f_final$mean)
            )
          }, error = function(e) NULL)
        }
        
        # The 8 Models
        run_model("ARIMA", function(d) auto.arima(d, stepwise=F, approximation=F))
        run_model("ETS", ets)
        run_model("BATS", bats)
        run_model("TBATS", tbats)
        run_model("Theta", thetaf, is_forecast_func = TRUE)
        run_model("NNAR", nnetar)
        run_model("MLP", function(d) mlp(d))
        run_model("RW_Drift", function(d, h) rwf(d, h=h, drift=TRUE), is_forecast_func = TRUE)
        
        all_accuracy_metrics[[current_country]] <- country_metrics_df
        all_future_forecasts[[current_country]] <- bind_rows(country_future_forecasts_list)
      }
    })
    
    acc_final <- bind_rows(all_accuracy_metrics)
    return(list(
      accuracy = acc_final,
      forecasts = bind_rows(all_future_forecasts),
      best_models = acc_final %>% group_by(Country) %>% slice_min(sMAPE, n = 1, with_ties = FALSE) %>% ungroup()
    ))
  })
  
  
  
  output$performance_table <- renderDT({
    req(forecasting_results())
    
    df <- forecasting_results()$accuracy %>%
      mutate(across(c(RMSE, MAE, sMAPE), ~round(., 4))) %>%
      arrange(Country, sMAPE)
    
    best_models <- forecasting_results()$best_models
    
    df_dt <- datatable(df, options = list(pageLength = 10))
    
    for (country in unique(df$Country)) {
      best_model_name <- best_models %>% filter(Country == country) %>% pull(Model)
      if (length(best_model_name) > 0) {
        row_indices <- which(df$Country == country & df$Model == best_model_name)
        df_dt <- df_dt %>% formatStyle(
          'Model', target = 'row',
          backgroundColor = styleEqual(country, 'lightgreen'),
          rows = row_indices
        )
      }
    }
    return(df_dt)
  })
  
  output$combined_forecast_plot <- renderPlot({
    req(forecasting_results())
    results <- forecasting_results()
    
    # Filter only the best model's forecast for each country
    plot_data <- results$forecasts %>%
      inner_join(results$best_models %>% select(Country, Model), by = c("Country", "Model"))
    
    hist_data <- time_series_data()
    
    ggplot() +
      geom_ribbon(data = plot_data, aes(x = Year, ymin = Lo95, ymax = Hi95), fill = "red", alpha = 0.1) +
      geom_ribbon(data = plot_data, aes(x = Year, ymin = Lo80, ymax = Hi80), fill = "red", alpha = 0.2) +
      geom_line(data = hist_data, aes(x = Year, y = Value), color = "black", linewidth = 0.7) +
      geom_line(data = plot_data, aes(x = Year, y = Forecast), color = "red", linewidth = 0.8) +
      facet_wrap(~Country, scales = "free_y", ncol = 5) +
      labs(title = "Optimal Model Forecasts with 80% and 95% Confidence Intervals",
           subtitle = "Black: Historical | Red: Forecast & Uncertainty Ribbons",
           y = input$v_indicator, x = "Year") +
      theme_minimal()
  })
  
  # --- SURAD: Comprehensive Spatial Calculations for Forecast Year ---
  surad_spatial_analysis <- reactive({
    req(forecasting_results())
    
    # 1. Prepare Data
    results <- forecasting_results()
    target_year <- 2030 # Fixed to forecast end or make dynamic if needed
    
    year_data <- results$forecasts %>%
      filter(Year == target_year) %>%
      inner_join(results$best_models, by = c("Country", "Model")) %>%
      select(Country_Standard = Country, Forecast_Value = Forecast)
    
    base_sf <- spatial_snapshot() %>% 
      select(Country_Map, Country_Standard, geometry) %>% 
      distinct(Country_Standard, .keep_all = TRUE)
    
    # Join Forecast Data to Geometry
    sf_obj <- base_sf %>%
      left_join(year_data, by = "Country_Standard") %>% 
      filter(!is.na(Forecast_Value))
    
    # 2. Create Spatial Weights (Queen Contiguity)
    nb <- poly2nb(sf_obj, queen = TRUE)
    
    # Handle Islands (Zero neighbors) using KNN k=1
    if(any(card(nb) == 0)) {
      coords <- st_coordinates(st_centroid(sf_obj))
      nb_knn <- knn2nb(knearneigh(coords, k = 1))
      nb <- union.nb(nb, nb_knn)
    }
    
    lw <- nb2listw(nb, style = "W", zero.policy = TRUE)
    
    # 3. Global Statistics
    # Global Moran's I
    moran_global <- moran.test(sf_obj$Forecast_Value, lw, zero.policy = TRUE, randomisation = TRUE)
    
    # Global Geary's C
    geary_global <- geary.test(sf_obj$Forecast_Value, lw, zero.policy = TRUE, randomisation = TRUE)
    
    # 4. Local Statistics
    # LISA (Local Moran)
    loc_m <- localmoran(sf_obj$Forecast_Value, lw, zero.policy = TRUE)
    sf_obj$lisa_pval <- loc_m[,5]
    
    # Calculate Lag for LISA categorization
    sf_obj$lag_value <- lag.listw(lw, sf_obj$Forecast_Value, zero.policy = TRUE)
    mean_val <- mean(sf_obj$Forecast_Value, na.rm = TRUE)
    
    sf_obj$lisa_cat <- case_when(
      sf_obj$lisa_pval <= 0.05 & sf_obj$Forecast_Value >= mean_val & sf_obj$lag_value >= mean_val ~ "High-High",
      sf_obj$lisa_pval <= 0.05 & sf_obj$Forecast_Value <= mean_val & sf_obj$lag_value <= mean_val ~ "Low-Low",
      sf_obj$lisa_pval <= 0.05 & sf_obj$Forecast_Value >= mean_val & sf_obj$lag_value <= mean_val ~ "High-Low",
      sf_obj$lisa_pval <= 0.05 & sf_obj$Forecast_Value <= mean_val & sf_obj$lag_value >= mean_val ~ "Low-High",
      TRUE ~ "Not Significant"
    )
    
    # Getis-Ord Gi* (Hotspot Analysis)
    # localG returns a 'htest' like object, we cast to numeric for Z-score
    sf_obj$gi_star <- as.numeric(localG(sf_obj$Forecast_Value, lw, zero.policy = TRUE))
    
    sf_obj$gi_cat <- case_when(
      sf_obj$gi_star >= 1.96 ~ "Hotspot (95%)",
      sf_obj$gi_star >= 1.645 ~ "Hotspot (90%)",
      sf_obj$gi_star <= -1.96 ~ "Coldspot (95%)",
      sf_obj$gi_star <= -1.645 ~ "Coldspot (90%)",
      TRUE ~ "Not Significant"
    )
    
    return(list(
      sf_data = sf_obj,
      moran = moran_global,
      geary = geary_global
    ))
  })
  
  # --- Output: Global Moran's I Curve ---
  output$surad_moran_curve <- renderPlot({
    res <- surad_spatial_analysis()
    m <- res$moran
    
    stat <- m$statistic[[1]] # Z-score
    est  <- m$estimate[[1]]  # Moran's I
    pval <- m$p.value
    
    # Create Normal Distribution Data
    df_norm <- data.frame(x = seq(-4, 4, length.out = 1000)) %>% mutate(y = dnorm(x))
    
    ggplot(df_norm, aes(x, y)) + 
      geom_line(linewidth = 1) + 
      geom_area(data = subset(df_norm, x >= abs(stat) | x <= -abs(stat)), fill = "#006400", alpha = 0.4) +
      geom_vline(xintercept = stat, color = "red", linetype="dashed", linewidth = 1) +
      annotate("text", x = -3, y = 0.35, label = paste0("Moran's I: ", round(est, 3)), hjust=0, fontface="bold") +
      annotate("text", x = -3, y = 0.30, label = paste0("Z-Score: ", round(stat, 3)), hjust=0) +
      annotate("text", x = -3, y = 0.25, label = paste0("p-value: ", format.pval(pval, digits=3)), hjust=0) +
      labs(x = "Z-Score", y = "Density", title = "Global Moran's I Distribution") +
      theme_minimal()
  })
  
  # --- Output: Global Geary's C Curve ---
  output$surad_geary_curve <- renderPlot({
    res <- surad_spatial_analysis()
    g <- res$geary
    
    stat <- g$statistic[[1]] # Z-score
    est  <- g$estimate[[1]]  # Geary's C
    pval <- g$p.value
    
    # Create Normal Distribution Data
    df_norm <- data.frame(x = seq(-4, 4, length.out = 1000)) %>% mutate(y = dnorm(x))
    
    ggplot(df_norm, aes(x, y)) + 
      geom_line(linewidth = 1) + 
      geom_area(data = subset(df_norm, x >= abs(stat) | x <= -abs(stat)), fill = "orange", alpha = 0.4) +
      geom_vline(xintercept = stat, color = "blue", linetype="dashed", linewidth = 1) +
      annotate("text", x = -3, y = 0.35, label = paste0("Geary's C: ", round(est, 3)), hjust=0, fontface="bold") +
      annotate("text", x = -3, y = 0.30, label = paste0("Z-Score: ", round(stat, 3)), hjust=0) +
      annotate("text", x = -3, y = 0.25, label = paste0("p-value: ", format.pval(pval, digits=3)), hjust=0) +
      labs(x = "Z-Score", y = "Density", title = "Global Geary's C Distribution") +
      theme_minimal()
  })
  
  # --- Output: LISA Map ---
  output$lisa_2030_plot <- renderPlot({
    df <- surad_spatial_analysis()$sf_data
    
    ggplot(df) + 
      geom_sf(aes(fill=lisa_cat), color="black", linewidth=0.1) + 
      scale_fill_manual(values=c("High-High"="red", "Low-Low"="blue", 
                                 "High-Low"="pink", "Low-High"="lightblue",
                                 "Not Significant"="grey95"),
                        name = "Cluster Type") +
      labs(subtitle = "Local Moran's I Clusters") +
      theme_void() + theme(legend.position = "bottom")
  })
  
  # --- Output: Getis-Ord Gi* Map ---
  output$gi_star_plot <- renderPlot({
    df <- surad_spatial_analysis()$sf_data
    
    ggplot(df) + 
      geom_sf(aes(fill=gi_cat), color="black", linewidth=0.1) + 
      scale_fill_manual(values=c("Hotspot (95%)"="#b30000", "Hotspot (90%)"="#fd8d3c", 
                                 "Coldspot (95%)"="#045a8d", "Coldspot (90%)"="#74a9cf",
                                 "Not Significant"="grey95"),
                        name = "Gi* Significance") +
      labs(subtitle = "Getis-Ord Gi* Hotspots") +
      theme_void() + theme(legend.position = "bottom")
  })
  
  # --- Output: Spatial Dependency (Lag) Map ---
  output$spatial_lag_plot <- renderPlot({
    df <- surad_spatial_analysis()$sf_data
    
    ggplot(df) + 
      geom_sf(aes(fill=lag_value), color="black", linewidth=0.1) + 
      scale_fill_viridis_c(option = "plasma", name = "Lagged Value") +
      labs(subtitle = "Spatial Lag (Avg. of Neighbors)") +
      theme_void() + theme(legend.position = "bottom")
  })
  
  # --- DEFINING RISK/FORECAST REACTIVE ---
  gap_data_prep <- reactive({
    req(forecasting_results())
    
    # Get 2030 data and join with Best Model info
    # FIX: Join by BOTH Country and Model to preserve column name and filter correctly
    df <- forecasting_results()$forecasts %>% 
      filter(Year == 2030) %>%
      inner_join(forecasting_results()$best_models, by = c("Country", "Model"))
    
    # Calculate Global Mean for 2030 to derive Risk Ratio
    mean_val <- mean(df$Forecast, na.rm = TRUE)
    
    df %>% 
      mutate(
        `2030 Forecast` = Forecast,
        `Risk Ratio` = Forecast / mean_val, 
        `Best Model` = Model # This will now work because 'Model' exists
      )
  })
  
  #
  generate_esda_plot <- function(df, method) {
    # df is the sf object from spatial_snapshot() containing 'Value'
    
    # 1. Prepare spatial weights (nb and lw)
    nb <- poly2nb(df, queen = TRUE)
    
    # Handle islands/disconnected components by adding k-nearest neighbors (k=1)
    if(any(card(nb) == 0)) {
      nb_knn <- knn2nb(knearneigh(st_coordinates(st_centroid(df)), k = 1))
      nb <- union.nb(nb, nb_knn)
    }
    lw <- nb2listw(nb, style = "W", zero.policy = TRUE)
    
    # 2. Plotting based on method
    if(method == "1. Basic Choropleth") {
      ggplot(df) + geom_sf(aes(fill=Value), color="black", linewidth=0.2) + 
        scale_fill_viridis_c(option = "plasma", name = input$v_indicator) + 
        labs(title=paste("1. Choropleth Map:", input$v_indicator, "in", input$v_year)) + theme_void()
      # Add these two blocks inside generate_esda_plot
    }else if(method == "8. Relative Risk (RR) Map") {
      df$RR <- df$Value / mean(df$Value, na.rm = TRUE)
      ggplot(df) + geom_sf(aes(fill = RR), color = "black", linewidth = 0.2) + 
        scale_fill_distiller(palette = "Spectral", name = "RR Index") + 
        labs(title = "Relative Risk (RR) Map", subtitle = "Values > 1 indicate higher risk than regional average") + theme_void()
      
    } else if(method == "9. Spatiotemporal Persistence Map") {
      # Fix: Use 'method' argument, match UI string exactly, and join by Country_Standard
      p_data <- persistence_data()
      p_df <- df %>% left_join(p_data, by="Country_Standard")
      
      ggplot(p_df) + 
        geom_sf(aes(fill=Years_as_Hotspot), color="black", linewidth=0.1) + 
        scale_fill_viridis_c(option="magma", name="Years") +
        labs(title="Spatiotemporal Persistence", 
             subtitle="Number of years identified as a High-High Cluster") + 
        theme_void()
    } else if(method == "2. Global Moran's I") {
      gmoran <- moran.test(df$Value, lw, zero.policy = TRUE)
      z_score <- gmoran$statistic
      p_value <- gmoran$p.value
      moran_i <- gmoran$estimate[1]
      
      # Create density curve data
      df_norm <- data.frame(x = seq(-4, 4, length.out = 1000)) %>% mutate(y = dnorm(x))
      
      ggplot(df_norm, aes(x, y)) + geom_line(linewidth = 1) + 
        geom_area(data = subset(df_norm, x >= z_score), fill = "red", alpha = 0.5) +
        geom_vline(xintercept = z_score, color = "red", linetype="dashed") +
        annotate("text", x = 2.5, y = 0.3, label = paste("p-value:", format.pval(p_value)), color="red", fontface="bold") +
        labs(title = "2. Global Moran's I (Systemic Test)", 
             subtitle = paste("Moran's I:", round(moran_i, 3), "| Z-Score:", round(z_score, 2)), 
             x = "Z-Score", y = "Density") + theme_minimal()
      
    } else if(method == "3. Moran Scatterplot") {
      df$lag_value <- lag.listw(lw, df$Value, zero.policy = TRUE)
      m_prev <- mean(df$Value, na.rm = TRUE)
      
      df$quadrant <- case_when(
        df$Value >= m_prev & df$lag_value >= m_prev ~ "High-High (Hotspot)", 
        df$Value <= m_prev & df$lag_value <= m_prev ~ "Low-Low (Coldspot)", 
        df$Value >= m_prev & df$lag_value <= m_prev ~ "High-Low (Outlier)", 
        df$Value <= m_prev & df$lag_value >= m_prev ~ "Low-High (Outlier)"
      )
      
      ggplot(df, aes(x = Value, y = lag_value, color = quadrant)) + 
        geom_vline(xintercept = m_prev, linetype = "dashed") + 
        geom_hline(yintercept = mean(df$lag_value, na.rm = TRUE), linetype = "dashed") + 
        geom_point(size = 3) + 
        # --- ADDED: Country Labels ---
        geom_text(aes(label = Country_Standard), size = 3, vjust = -0.5, 
                  check_overlap = TRUE, show.legend = FALSE) +
        # -----------------------------
      geom_smooth(method = "lm", se = FALSE, color = "black") + 
        scale_color_manual(values = c("High-High (Hotspot)"="red", "Low-Low (Coldspot)"="blue", 
                                      "High-Low (Outlier)"="pink", "Low-High (Outlier)"="lightblue"), 
                           name = "Quadrant") + 
        labs(title = paste("3. Moran Scatterplot (Association) for", input$v_year), 
             x = input$v_indicator, y = paste("Spatially Lagged", input$v_indicator)) + 
        theme_minimal() + theme(legend.position = "bottom")
      
    } else if(method == "4. LISA Cluster Map") {
      loc_m <- localmoran(df$Value, lw, zero.policy = TRUE)
      df$Pr <- loc_m[,5]
      df$lag_value <- lag.listw(lw, df$Value, zero.policy = TRUE)
      m_prev <- mean(df$Value, na.rm = TRUE)
      
      df$lisa <- case_when(
        df$Pr <= 0.05 & df$Value >= m_prev & df$lag_value >= m_prev ~ "High-High (Hotspot)", 
        df$Pr <= 0.05 & df$Value <= m_prev & df$lag_value <= m_prev ~ "Low-Low (Coldspot)", 
        TRUE ~ "Not Significant"
      )
      
      ggplot(df) + geom_sf(aes(fill=lisa), color="black", linewidth=0.2) + 
        scale_fill_manual(values=c("High-High (Hotspot)"="red", "Low-Low (Coldspot)"="blue", 
                                   "Not Significant"="grey90"), name = "LISA Cluster Type") + 
        labs(title = paste("4. LISA Cluster Map (Significant Spatial Regimes) for", input$v_year)) + 
        theme_void() + theme(legend.position = "bottom")
      
    } else if(method == "5. Global Geary's C Map") {
      # Calculate Local Geary's C
      geary_loc <- localC(df$Value, lw)
      df$geary_c <- geary_loc
      
      ggplot(df) + geom_sf(aes(fill = geary_c), color = "black", linewidth = 0.2) + 
        scale_fill_viridis_c(option = "magma", direction = -1, name = "Local Geary's C") + 
        labs(title = "5. Local Geary's C (Dissimilarity/Edge Detection)", 
             subtitle = "Lower values (darker) indicate stronger local similarity.") + 
        theme_void() + theme(legend.position = "bottom")
      
    } else if(method == "6. Spatial Correlogram") {
      # Calculate Correlogram
      cor_res <- sp.correlogram(nb, df$Value, order = 4, method = "I", style = "W", zero.policy = TRUE)
      
      cor_df <- data.frame(
        Lag = 1:4, 
        Morans_I = cor_res$res[, 1], 
        P_Value = 2 * (1 - pnorm(abs(cor_res$res[, 1] - cor_res$res[, 2]) / cor_res$res[, 3]))
      ) %>% mutate(Significance = ifelse(P_Value < 0.05, "Significant", "Not Significant"))
      
      ggplot(cor_df, aes(x = Lag, y = Morans_I)) + 
        geom_hline(yintercept = 0, linetype = "dashed", color = "gray") + 
        geom_line(linewidth = 1) + 
        geom_point(aes(color = Significance), size = 4) + 
        scale_color_manual(values = c("Significant" = "red", "Not Significant" = "blue")) + 
        labs(title = "6. Spatial Correlogram (Moran's I by Distance Lag)", 
             y = "Moran's I", x = "Spatial Lag (Order)") + 
        theme_minimal() + theme(legend.position = "bottom")
      
    } else if(method == "7. Connectivity Graph") {
      # Helper function for plotting connectivity
      nb_to_segment <- function(nb, coords) {
        segments <- data.frame()
        for (i in 1:length(nb)) {
          for (j in nb[[i]]) {
            if (i < j) { # Avoid duplicate segments
              segments <- rbind(segments, data.frame(
                x = coords[i, 1], y = coords[i, 2],
                xend = coords[j, 1], yend = coords[j, 2]
              ))
            }
          }
        }
        return(segments)
      }
      
      coords <- st_coordinates(st_centroid(df))
      
      # Base map
      base_map <- ggplot(df) + geom_sf(fill="grey90", color="grey50") + theme_void()
      
      # Add segments
      base_map + 
        geom_segment(data = nb_to_segment(nb, coords), 
                     aes(x = x, y = y, xend = xend, yend = yend), 
                     color = "#006400", linewidth = 1, alpha = 0.7) +
        labs(title = "7. Spatial Connectivity Graph (Queen Contiguity)")
    }
  }
  # --- Render the Forecasted Risk Analysis Table in the UI ---
  output$gap_table <- renderDT({
    req(gap_data_prep())
    
    # Get the prepared data
    df <- gap_data_prep() %>%
      select(Country, `2030 Forecast`, `Risk Ratio`, `Best Model`) %>%
      mutate(across(where(is.numeric), ~round(., 3))) %>%
      arrange(desc(`Risk Ratio`))
    
    # Render as a DataTable
    datatable(df, 
              options = list(pageLength = 10, scrollX = TRUE),
              caption = "Table: Projected Forecast & Risk Ratio (Relative to Global Mean)") %>%
      formatStyle(
        'Risk Ratio',
        backgroundColor = styleInterval(c(0.8, 1.2), c('#d1e7dd', '#fff3cd', '#f8d7da'))
      )
  })
  output$diag_plot <- renderPlot({
    df <- spatial_snapshot()
    generate_esda_plot(df, input$esda_method)
  })
  # --- UPDATED: Comprehensive Reporting Logic with Labeled Global Stats ---
  output$dl_report <- downloadHandler(
    filename = function() { 
      paste0("Saylac_Report_", Sys.Date(), ".", switch(input$format, "html"="html", "pdf"="pdf", "word"="docx")) 
    },
    content = function(file) {
      # 1. Ensure all data is ready
      req(rv$historical_end_year, rv$forecast_start_year, forecasting_results(), surad_spatial_analysis(), trend_analysis_results())
      
      # Show a notification while generating
      id <- showNotification("Generating comprehensive report... please wait.", duration = NULL, closeButton = FALSE)
      on.exit(removeNotification(id), add = TRUE)
      
      # 2. Generate all 9 SAW Plots programmatically (Updated to include RR and Persistence)
      saw_methods <- c("1. Basic Choropleth", "2. Global Moran's I", "3. Moran Scatterplot", 
                       "4. LISA Cluster Map", "5. Global Geary's C Map", "6. Spatial Correlogram", 
                       "7. Connectivity Graph", "8. Relative Risk (RR) Map", "9. Spatiotemporal Persistence Map")
      
      df_saw <- spatial_snapshot()
      saw_plot_list <- lapply(saw_methods, function(m) {
        generate_esda_plot(df_saw, m)
      })
      names(saw_plot_list) <- saw_methods
      
      # --- NEW: Generate Trend Plot for Report ---
      trend_data <- trend_analysis_results()
      p_trend <- ggplot(trend_data, aes(x = Country, y = sen_slope, fill = significance)) +
        geom_col() +
        coord_flip() +
        scale_fill_manual(values = c("Significant (p < 0.05)" = "#006400", "Non-significant" = "grey70")) +
        labs(title = "Rate of Change per Year (Theil-Sen Slope)", y = "Annual Change", fill = "Significance") +
        theme_minimal() + theme(legend.position="bottom", axis.text.y = element_text(size=6))
      
      # 3. Create a temporary Rmd file
      tempReport <- file.path(tempdir(), "saw_comprehensive_report.Rmd")
      
      # Capture dynamic variables
      hist_end <- rv$historical_end_year
      fc_start <- rv$forecast_start_year
      fc_end <- forecast_end_year
      
      # 4. Define the R Markdown Template
      report_content <- c(
        "---",
        paste0("title: '", input$rep_title, "'"),
        "subtitle: 'Generated via Saylac Platform'",
        "date: '`r format(Sys.time(), \"%d %B, %Y\")`'",
        "output:",
        "  word_document:",
        "    toc: true",
        "    fig_caption: true",
        "    df_print: kable",
        "  pdf_document:",
        "    toc: true",
        "    number_sections: true",
        "  html_document:",
        "    theme: flatly",
        "    toc: true",
        "params:",
        "  res_obj: NA",
        "  surad_obj: NA",
        "  saw_plots: NA",
        "  trend_plot: NA",
        "  hist_data: NA",
        "  gap_data: NA",
        "  indicator: NA",
        "  region: NA",
        "  hist_end: NA",
        "  fc_start: NA",
        "  fc_end: NA",
        "---",
        "",
        "```{r setup, include=FALSE}",
        "knitr::opts_chunk$set(echo = FALSE, message = FALSE, warning = FALSE, dpi = 300, fig.width=8, fig.height=6)",
        "library(tidyverse)",
        "library(sf)",
        "library(ggplot2)",
        "library(knitr)",
        "library(forecast)",
        "library(spdep)",
        "```",
        "",
        "# 1. Introduction",
        "",
        "This report presents a comprehensive analysis of **`r params$indicator`** for the **`r params$region`** region. The analysis spans historical data (up to `r params$hist_end`) and provides forecasts through `r params$fc_end`.",
        "",
        "# 2. SAW: Historical Spatial Analysis (`r params$hist_end`)",
        "",
        "This section explores the spatial distribution, autocorrelation, and connectivity of the data in the snapshot year.",
        "",
        "## 2.1 Basic Distribution",
        "```{r}",
        "params$saw_plots[['1. Basic Choropleth']]",
        "```",
        "",
        "## 2.2 Global Spatial Autocorrelation",
        "Global Moran's I tests for systemic clustering, while Geary's C tests for local similarity.",
        "```{r}",
        "params$saw_plots[['2. Global Moran\\'s I']]",
        "```",
        "```{r}",
        "params$saw_plots[['5. Global Geary\\'s C Map']]",
        "```",
        "",
        "## 2.3 Local Clusters & Outliers",
        "The Scatterplot visualizes the relationship between a country and its neighbors. The LISA map identifies statistically significant clusters.",
        "```{r}",
        "params$saw_plots[['3. Moran Scatterplot']]",
        "```",
        "```{r}",
        "params$saw_plots[['4. LISA Cluster Map']]",
        "```",
        "",
        "## 2.4 Spatial Structure",
        "The Correlogram shows how correlation decays with distance. The Connectivity graph shows the neighbor structure used for analysis.",
        "```{r}",
        "params$saw_plots[['6. Spatial Correlogram']]",
        "```",
        "```{r}",
        "params$saw_plots[['7. Connectivity Graph']]",
        "```",
        "",
        "## 2.5 Advanced Spatiotemporal Risk",
        "Relative Risk shows intensity relative to the mean. Persistence shows how often a country is a hotspot.",
        "```{r}",
        "params$saw_plots[['8. Relative Risk (RR) Map']]",
        "```",
        "```{r}",
        "params$saw_plots[['9. Spatiotemporal Persistence Map']]",
        "```",
        "",
        "# 3. SIMODI: Time Series Diagnostics & Forecasts",
        "",
        "## 3.0 Comparative Trend Analysis",
        "The chart below displays the annual rate of change (Theil-Sen slope) for all selected countries. Green bars indicate a statistically significant trend (Mann-Kendall p < 0.05).",
        "```{r fig.height=8}",
        "params$trend_plot",
        "```",
        "",
        "## 3.1 Model Selection Performance",
        "The table below shows the optimal model selected for each country based on the lowest sMAPE (Symmetric Mean Absolute Percentage Error) during the validation phase.",
        "",
        "```{r}",
        "acc_df <- params$res_obj$best_models %>% ",
        "  arrange(sMAPE) %>% ",
        "  mutate(sMAPE = round(sMAPE, 4))",
        "knitr::kable(acc_df, caption = 'Table 1: Optimal Models per Country')",
        "```",
        "",
        "## 3.2 Forecast Trajectories with Uncertainty",
        "Visualizing historical trends (black) vs. future predictions (red) with 80% and 95% confidence intervals.",
        "",
        "```{r fig.height=12, fig.width=10}",
        "    results <- params$res_obj",
        "    best_models <- results$best_models",
        "    fc_data <- results$forecasts %>% inner_join(best_models %>% select(Country, Model), by = c('Country', 'Model'))",
        "    hist_df <- params$hist_data",
        "    ",
        "    ggplot() +",
        "      geom_ribbon(data = fc_data, aes(x = Year, ymin = Lo95, ymax = Hi95), fill = 'red', alpha = 0.1) +",
        "      geom_ribbon(data = fc_data, aes(x = Year, ymin = Lo80, ymax = Hi80), fill = 'red', alpha = 0.2) +",
        "      geom_line(data = hist_df, aes(x = Year, y = Value), color = 'black', size = 0.6) +",
        "      geom_line(data = fc_data, aes(x = Year, y = Forecast), color = 'red', size = 0.7) +",
        "      facet_wrap(~Country, scales = 'free_y', ncol = 5) +",
        "      labs(y = params$indicator, x = 'Year') +",
        "      theme_minimal() + ",
        "      theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 6))",
        "```",
        "",
        "# 4. SURAD: Forecasted Spatial Results (`r params$fc_end`)",
        "",
        "This section analyzes the spatial patterns of the *forecasted* values for the year `r params$fc_end`.",
        "",
        "## 4.1 Global Spatial Statistics (Forecasted)",
        "```{r fig.width=10, fig.height=5}",
        "    # Reconstruct Global Moran Plot with Labels",
        "    m <- params$surad_obj$moran",
        "    stat_m <- m$statistic[[1]]; est_m <- m$estimate[[1]]; pval_m <- m$p.value",
        "    df_norm_m <- data.frame(x = seq(-4, 4, length.out = 1000)) %>% mutate(y = dnorm(x))",
        "    p1 <- ggplot(df_norm_m, aes(x, y)) + geom_line() + ",
        "      geom_area(data = subset(df_norm_m, x >= abs(stat_m) | x <= -abs(stat_m)), fill = '#006400', alpha = 0.4) +",
        "      geom_vline(xintercept = stat_m, color = 'red', linetype='dashed') +",
        "      annotate('text', x = -3, y = 0.35, label = paste0('Moran\\'s I: ', round(est_m, 3)), hjust=0, fontface='bold') +",
        "      annotate('text', x = -3, y = 0.30, label = paste0('Z-Score: ', round(stat_m, 3)), hjust=0) +",
        "      annotate('text', x = -3, y = 0.25, label = paste0('p-value: ', format.pval(pval_m, digits=3)), hjust=0) +",
        "      labs(title = 'Global Moran\\'s I Distribution', x='Z-Score', y='Density') + theme_minimal()",
        "",
        "    # Reconstruct Global Geary Plot with Labels",
        "    g <- params$surad_obj$geary",
        "    stat_g <- g$statistic[[1]]; est_g <- g$estimate[[1]]; pval_g <- g$p.value",
        "    df_norm_g <- data.frame(x = seq(-4, 4, length.out = 1000)) %>% mutate(y = dnorm(x))",
        "    p2 <- ggplot(df_norm_g, aes(x, y)) + geom_line() + ",
        "      geom_area(data = subset(df_norm_g, x >= abs(stat_g) | x <= -abs(stat_g)), fill = 'orange', alpha = 0.4) +",
        "      geom_vline(xintercept = stat_g, color = 'blue', linetype='dashed') +",
        "      annotate('text', x = -3, y = 0.35, label = paste0('Geary\\'s C: ', round(est_g, 3)), hjust=0, fontface='bold') +",
        "      annotate('text', x = -3, y = 0.30, label = paste0('Z-Score: ', round(stat_g, 3)), hjust=0) +",
        "      annotate('text', x = -3, y = 0.25, label = paste0('p-value: ', format.pval(pval_g, digits=3)), hjust=0) +",
        "      labs(title = 'Global Geary\\'s C Distribution', x='Z-Score', y='Density') + theme_minimal()",
        "    ",
        "    library(patchwork)",
        "    p1 + p2",
        "```",
        "",
        "## 4.2 Local Spatial Clustering Maps (`r params$fc_end`)",
        "**LISA Map (Left):** Identifies local clusters (High-High, Low-Low). **Gi* Map (Right):** Identifies Hotspots and Coldspots with statistical significance.",
        "",
        "```{r fig.width=12, fig.height=6}",
        "    df_sf <- params$surad_obj$sf_data",
        "    ",
        "    p_lisa <- ggplot(df_sf) + ",
        "      geom_sf(aes(fill=lisa_cat), color='black', linewidth=0.1) + ",
        "      scale_fill_manual(values=c('High-High'='red', 'Low-Low'='blue', 'High-Low'='pink', 'Low-High'='lightblue', 'Not Significant'='grey95'), name='LISA') +",
        "      labs(title='LISA Clusters') + theme_void()",
        "      ",
        "    p_gi <- ggplot(df_sf) + ",
        "      geom_sf(aes(fill=gi_cat), color='black', linewidth=0.1) + ",
        "      scale_fill_manual(values=c('Hotspot (95%)'='#b30000', 'Hotspot (90%)'='#fd8d3c', 'Coldspot (95%)'='#045a8d', 'Coldspot (90%)'='#74a9cf', 'Not Significant'='grey95'), name='Gi*') +",
        "      labs(title='Gi* Hotspots') + theme_void()",
        "      ",
        "    p_lisa + p_gi",
        "```",
        "",
        "## 4.3 Spatial Dependency (Lag)",
        "This map shows the average value of each country's neighbors, highlighting regional context.",
        "```{r}",
        "    ggplot(df_sf) + ",
        "      geom_sf(aes(fill=lag_value), color='black', linewidth=0.1) + ",
        "      scale_fill_viridis_c(option = 'plasma', name = 'Lag Value') +",
        "      theme_void()",
        "```",
        "",
        "# 5. Forecasted Risk Analysis",
        "Ranking countries by their forecasted value relative to the global average (Risk Ratio) by `r params$fc_end`.",
        "",
        "```{r}",
        "    gap_df <- params$gap_data %>% ",
        "      select(Country, `2030 Forecast`, `Risk Ratio`, `Best Model`) %>%",
        "      arrange(desc(`Risk Ratio`))",
        "    ",
        "    knitr::kable(gap_df, digits = 3, caption = 'Table 2: Projected Forecast & Risk Ratio')",
        "```",
        "",
        "***",
        "*Report generated automatically by the Saylac platform.*"
      )
      
      writeLines(report_content, tempReport)
      
      # 5. Render the Report
      rmarkdown::render(tempReport, output_file = file,
                        params = list(
                          res_obj = forecasting_results(),
                          surad_obj = surad_spatial_analysis(),
                          saw_plots = saw_plot_list,
                          trend_plot = p_trend,
                          hist_data = time_series_data(),
                          gap_data = gap_data_prep(),
                          indicator = input$v_indicator,
                          region = input$region_filter,
                          hist_end = hist_end,
                          fc_start = fc_start,
                          fc_end = fc_end
                        ),
                        envir = new.env(parent = globalenv())
      )
    }
  )
}

shinyApp(ui, server)