# ============================================================
# 02_build_model_inputs.R - Master Data Pipeline Orchestrator
# ============================================================
# Coordinates all data processing sub-scripts and produces
# final output files for the 4D latent trait model. By default this writes only
# to local-only directories under _local/ and never overwrites committed data/.
#
# Sub-scripts (run conditionally based on intermediate file existence):
#   process_manifesto.R  -> manifesto_data.csv
#   process_poldem.R     -> poldem_data.csv
#   process_expert.R     -> expert_raw.csv, lr_data_raw.csv
#   process_morgan.R     -> morgan_data.csv, morgan_lr.csv
#
# Final generated model inputs:
#   text_data.csv - Combined manifesto + PolDem
#   expert.csv    - Expert survey data (CHES, V-Party, POPPA, GPS)
#   lr_data.csv   - General left-right anchoring data
# ============================================================

library(tidyverse)
library(countrycode)

cmd_args <- commandArgs(trailingOnly = FALSE)
file_arg <- grep("^--file=", cmd_args, value = TRUE)
if (length(file_arg) > 0) {
  this_file <- normalizePath(sub("^--file=", "", file_arg[[1]]), mustWork = TRUE)
  repo_root <- normalizePath(file.path(dirname(this_file), "..", ".."), mustWork = TRUE)
} else {
  repo_root <- normalizePath(getwd(), mustWork = TRUE)
}
script_dir <- file.path(repo_root, "data-setup", "R")
build_dir <- normalizePath(
  Sys.getenv("PARTY2D_BUILD_DIR", file.path(repo_root, "_local", "build")),
  mustWork = FALSE
)
generated_input_dir <- normalizePath(
  Sys.getenv("PARTY2D_GENERATED_INPUT_DIR", file.path(repo_root, "_local", "generated-inputs")),
  mustWork = FALSE
)
dir.create(build_dir, recursive = TRUE, showWarnings = FALSE)
dir.create(generated_input_dir, recursive = TRUE, showWarnings = FALSE)

# The source-processing scripts use relative paths for intermediate files.
# Keep those intermediates in the ignored build directory, never committed data/.
setwd(build_dir)

# Static model support inputs are versioned in data/ and copied into the local
# generated-input set for comparison. They are not regenerated by raw-source setup.
for (support_file in c("union_mapping.csv", "party_families.csv")) {
  src <- file.path(repo_root, "data", support_file)
  if (!file.exists(src)) {
    stop("Required committed support input not found: ", src)
  }
  file.copy(src, file.path(build_dir, support_file), overwrite = TRUE)
}

cat("============================================================\n")
cat("Data Management Pipeline\n")
cat("============================================================\n\n")
cat("Build directory: ", build_dir, "\n", sep = "")
cat("Generated input directory: ", generated_input_dir, "\n\n", sep = "")

# ============================================================
# Configuration: Set to TRUE to force re-run of sub-scripts
# ============================================================

FORCE_RERUN_MANIFESTO <- FALSE
FORCE_RERUN_POLDEM <- FALSE
FORCE_RERUN_EXPERT <- FALSE
FORCE_RERUN_MORGAN <- FALSE

# ============================================================
# Step 1: Manifesto Data
# ============================================================

cat("Step 1: Manifesto data\n")
if (!file.exists("manifesto_data.csv") || !file.exists("election_data.csv") || FORCE_RERUN_MANIFESTO) {
  cat("  Running process_manifesto.R...\n")
  source(file.path(script_dir, "process_manifesto.R"))
} else {
  cat("  Loading cached manifesto_data.csv and election_data.csv...\n")
}
manifesto <- read_csv("manifesto_data.csv", show_col_types = FALSE)
election_data <- read_csv("election_data.csv", show_col_types = FALSE)
cat(sprintf("  Loaded manifesto: %d rows, %d parties\n", nrow(manifesto), n_distinct(manifesto$party)))
cat(sprintf("  Loaded election: %d rows, %d parties\n\n", nrow(election_data), n_distinct(election_data$party)))

# ============================================================
# Step 2: PolDem Media Data
# ============================================================

cat("Step 2: PolDem media data\n")
if (!file.exists("poldem_data.csv") || FORCE_RERUN_POLDEM) {
  cat("  Running process_poldem.R...\n")
  source(file.path(script_dir, "process_poldem.R"))
} else {
  cat("  Loading cached poldem_data.csv...\n")
}
poldem_data <- read_csv("poldem_data.csv", show_col_types = FALSE)
cat(sprintf("  Loaded: %d rows, %d parties\n\n", nrow(poldem_data), n_distinct(poldem_data$party)))

# ============================================================
# Step 4: Expert Survey Data
# ============================================================

cat("Step 3: Expert survey data\n")
if (!file.exists("expert_raw.csv") || !file.exists("lr_data_raw.csv") || FORCE_RERUN_EXPERT) {
  cat("  Running process_expert.R...\n")
  source(file.path(script_dir, "process_expert.R"))
} else {
  cat("  Loading cached expert_raw.csv and lr_data_raw.csv...\n")
}
expert_raw <- read_csv("expert_raw.csv", show_col_types = FALSE)
lr_data_raw <- read_csv("lr_data_raw.csv", show_col_types = FALSE)
cat(sprintf("  Expert: %d rows, LR: %d rows\n\n", nrow(expert_raw), nrow(lr_data_raw)))

# ============================================================
# Step 3b: Morgan (1976) Historical Expert Data
# ============================================================

cat("Step 3b: Morgan (1976) historical L-R data\n")

# First run to generate morgan_data.csv if needed
if (!file.exists("morgan_data.csv") || FORCE_RERUN_MORGAN) {
  cat("  Running process_morgan.R (initial processing)...\n")
  source(file.path(script_dir, "process_morgan.R"))
}

# morgan_lr.csv depends on text_data.csv, so we need to check if it needs regeneration
# It will be generated/regenerated below after text_data is created

# ============================================================
# Step 4: Combine Text Data Sources
# ============================================================

cat("Step 4: Combining text data sources\n")
text_data <- bind_rows(manifesto, poldem_data)
cat(sprintf("  Combined text_data: %d rows\n", nrow(text_data)))

# Save unfiltered text_data for reproducible mismatch diagnosis
write_csv(text_data, "text_data_unfiltered.csv")
cat(sprintf("  Saved unfiltered text_data: %d rows, %d parties\n", nrow(text_data), n_distinct(text_data$party)))

# ============================================================
# Step 4b: Party Renames (applied before filtering)
# ============================================================
# Renames must happen BEFORE the relevance filter so that party IDs
# match across text_data and expert_raw when computing expert coverage.

# Simple renames only (organizational continuity: same leadership/members)
simple_renames <- c(
  `10` = 1816L,    # DE: Greens -> Bündnis90/Grüne
  `276` = 120L,    # RO: FDSN/PDSR -> PSD (renamed 2001)
  `8054` = 878L,   # IT: PDS -> DS (renamed 1998)
  `1696` = 813L,   # IT: MSI -> AN (refounded 1995)
  `553` = 1968L,   # BE: Vlaams Blok -> Vlaams Belang (refounded 2004)
  `8058` = 1626L   # IT: Forza Italia (refounded 2013) -> Forza Italia (same party, Berlusconi)
)

apply_simple_renames <- function(df) {
  for (old_id in names(simple_renames)) {
    df <- df %>%
      mutate(party = ifelse(party == as.integer(old_id), simple_renames[[old_id]], party))
  }
  df
}

cat("\nStep 4b: Party renames\n")
text_data <- apply_simple_renames(text_data)
cat(sprintf("  Applied %d renames to text_data\n", length(simple_renames)))

# ============================================================
# Step 4c: Relevance Filter
# ============================================================
# Design: R pipeline filters for RELEVANCE (is this party worth modeling?).
# Julia pipeline handles INTERPOLATION QUALITY (MAX_GAP=7 segment splitting, MIN_OBS=2).
# Expert survey coverage is a relevance signal: CHES only covers parties with >1% vote share.

cat("\nStep 4c: Relevance filter\n")
parties_before <- n_distinct(text_data$party)

# Compute expert coverage per party (with renames applied for consistent matching)
expert_year_counts <- bind_rows(
  expert_raw %>% select(party, year),
  lr_data_raw %>% select(party, year)
) %>% distinct() %>%
  apply_simple_renames() %>%
  distinct() %>%
  count(party, name = "expert_years")

expert_party_ids <- unique(expert_year_counts$party)
cat(sprintf("  Parties with expert data: %d\n", length(expert_party_ids)))

# Three-tier relevance filter:
#   Tier 1: 3+ text data years (always include, regardless of expert data)
#   Tier 2: 2 text years + any expert data (major newer parties like M5S, ANO, LREM)
#   Tier 3: 1 text year + 3+ expert survey years (parties with rich expert coverage)
text_data <- text_data %>%
  group_by(country, party) %>%
  mutate(n_years = n_distinct(year)) %>%
  ungroup() %>%
  left_join(expert_year_counts, by = "party") %>%
  mutate(expert_years = replace_na(expert_years, 0L)) %>%
  mutate(
    tier = case_when(
      n_years >= 3 ~ 1L,
      n_years >= 2 & party %in% expert_party_ids ~ 2L,
      n_years >= 1 & expert_years >= 3 ~ 3L,
      TRUE ~ 0L
    )
  ) %>%
  filter(tier > 0) %>%
  select(-n_years, -expert_years, -tier)

parties_after <- n_distinct(text_data$party)
cat(sprintf("  Parties before filter: %d\n", parties_before))
cat(sprintf("  Parties after filter: %d\n", parties_after))
cat(sprintf("  Parties removed: %d\n\n", parties_before - parties_after))

# ============================================================
# Step 5: Party Harmonization
# ============================================================

cat("Step 5: Party harmonization (union-aware)\n")

# Load union mapping to identify constituent parties
union_map <- read_csv("union_mapping.csv", show_col_types = FALSE)

# Build set of constituent parties whose union is in text_data
constituent_parties <- union_map %>%
  filter(manifesto_pf_id %in% unique(text_data$party)) %>%
  pull(expert_pf_id)

cat(sprintf("  Union mappings loaded: %d rows covering %d unions\n",
            nrow(union_map), n_distinct(union_map$manifesto_pf_id)))
cat(sprintf("  Constituent parties with unions in text_data: %d\n",
            length(unique(constituent_parties))))

# Deduplicate union manifesto rows: where multiple CMP codes map to the same
# union PF ID with identical content, keep only one set per (party, year, var)
text_data_before_dedup <- nrow(text_data)
text_data <- text_data %>%
  distinct(country, party, year, var, .keep_all = TRUE)
cat(sprintf("  Text data: %d unique parties after harmonization\n", n_distinct(text_data$party)))
cat(sprintf("  Text data: deduplicated %d -> %d rows\n", text_data_before_dedup, nrow(text_data)))

# Filter expert data: keep parties in text_data OR constituent parties of unions in text_data
expert <- expert_raw %>%
  apply_simple_renames() %>%
  group_by(country, party, var, year) %>%
  summarise(
    val = mean(val, na.rm = TRUE),
    val_int = first(val_int),
    n_scale = first(n_scale),
    n_experts = first(n_experts),
    project = first(project),
    type_low = first(type_low),
    type_high = first(type_high),
    .groups = "drop"
  ) %>%
  filter(party %in% unique(text_data$party) | party %in% constituent_parties)

lr_data <- lr_data_raw %>%
  apply_simple_renames() %>%
  group_by(country, party, var, year) %>%
  summarise(
    val = mean(val, na.rm = TRUE),
    val_int = first(val_int),
    n_scale = first(n_scale),
    n_experts = first(n_experts),
    project = first(project),
    .groups = "drop"
  ) %>%
  filter(party %in% unique(text_data$party) | party %in% constituent_parties)

cat(sprintf("  Expert data: %d rows (filtered to text_data parties)\n", nrow(expert)))
cat(sprintf("  LR data (CHES/POPPA): %d rows (filtered to text_data parties)\n", nrow(lr_data)))

# ============================================================
# Step 5b: Integrate Morgan L-R Data
# ============================================================

cat("\nStep 5b: Morgan L-R data integration\n")

# Generate morgan_lr.csv (requires text_data.csv to exist)
# We need to regenerate it if text_data changed or if forced
if (!file.exists("morgan_lr.csv") || FORCE_RERUN_MORGAN) {
  cat("  Generating morgan_lr.csv...\n")
  # Write text_data first so morgan script can use it
  write_csv(text_data, "text_data.csv")
  source(file.path(script_dir, "process_morgan.R"))
}

# Load and integrate Morgan L-R data
if (file.exists("morgan_lr.csv")) {
  morgan_lr <- read_csv("morgan_lr.csv", show_col_types = FALSE) %>%
    apply_simple_renames() %>%
    filter(party %in% unique(text_data$party) | party %in% constituent_parties)

  cat(sprintf("  Morgan L-R: %d rows (filtered to text_data parties)\n", nrow(morgan_lr)))
  cat(sprintf("  Morgan parties: %d\n", n_distinct(morgan_lr$party)))
  cat(sprintf("  Morgan year range: %d-%d\n", min(morgan_lr$year), max(morgan_lr$year)))

  # Combine with existing lr_data
  lr_data_before <- nrow(lr_data)
  lr_data <- bind_rows(lr_data, morgan_lr) %>%
    arrange(country, party, year, var)

  cat(sprintf("  Combined LR data: %d rows (+%d from Morgan)\n",
              nrow(lr_data), nrow(lr_data) - lr_data_before))
} else {
  cat("  Warning: morgan_lr.csv not found, skipping Morgan integration\n")
}

cat("\n")

# ============================================================
# Step 6: Write Final Outputs
# ============================================================

cat("Step 6: Writing final outputs\n")

write_csv(text_data, "text_data.csv")
write_csv(expert, "expert.csv")
write_csv(lr_data, "lr_data.csv")

for (final_file in c("text_data.csv", "expert.csv", "lr_data.csv", "union_mapping.csv", "party_families.csv")) {
  file.copy(file.path(build_dir, final_file), file.path(generated_input_dir, final_file), overwrite = TRUE)
}

cat("\n============================================================\n")
cat("Pipeline Complete!\n")
cat("============================================================\n\n")

cat("Output files written:\n")
cat(sprintf("  local generated input dir: %s\n", generated_input_dir))
cat(sprintf("  text_data.csv: %d rows\n", nrow(text_data)))
cat(sprintf("    - Manifesto: %d rows\n", sum(grepl("_manifesto", text_data$var))))
cat(sprintf("    - PolDem:    %d rows\n", sum(grepl("_poldem", text_data$var))))
cat(sprintf("  expert.csv: %d rows\n", nrow(expert)))
cat(sprintf("  lr_data.csv: %d rows\n", nrow(lr_data)))
cat(sprintf("    - CHES:   %d rows\n", sum(lr_data$var == "lr_ches")))
cat(sprintf("    - POPPA:  %d rows\n", sum(lr_data$var == "lr_poppa")))
cat(sprintf("    - Morgan: %d rows\n", sum(lr_data$var == "lr_morgan")))

cat("\nUnique parties in text_data:", n_distinct(text_data$party), "\n")
cat("Countries:", paste(sort(unique(text_data$country)), collapse = ", "), "\n")
cat("Year range:", min(text_data$year, na.rm = TRUE), "-", max(text_data$year, na.rm = TRUE), "\n")
