#!/usr/bin/env julia
#############################################################################
## 02_data_loading.jl
## Load and preprocess data for latent trait model
## Loads three datasets: text_data (manifesto + PolDem), expert dimension-specific, expert general L-R
##
## Supports both:
##   - 4D model (V10): type_high/type_low columns for bipolar bridges
##   - 2D model (V1): dim_idx + direction columns for direct estimation
#############################################################################

using DataFrames, CSV, CategoricalArrays, Statistics

#############################################################################
## UNION MAPPING: Individual party estimates via mean-constituent model
## Loads data/union_mapping.csv and builds lookup structures
#############################################################################

"""
    load_union_mapping(project_root::String)

Load union_mapping.csv and build lookup dictionaries.
Returns (union_to_constituents, constituent_to_union) dicts.
If file is missing or empty, returns empty dicts (backwards compatible).
"""
function load_union_mapping(project_root::String=".")
    mapping_file = joinpath(project_root, "data", "union_mapping.csv")

    union_to_constituents = Dict{Int, Vector{Int}}()
    constituent_to_union = Dict{Int, Int}()

    if !isfile(mapping_file)
        println("  No union_mapping.csv found - running without union decomposition")
        return union_to_constituents, constituent_to_union
    end

    df = CSV.read(mapping_file, DataFrame)
    if nrow(df) == 0
        println("  union_mapping.csv is empty - running without union decomposition")
        return union_to_constituents, constituent_to_union
    end

    for row in eachrow(df)
        union_id = row.manifesto_pf_id
        expert_id = row.expert_pf_id

        if !haskey(union_to_constituents, union_id)
            union_to_constituents[union_id] = Int[]
        end
        if !(expert_id in union_to_constituents[union_id])
            push!(union_to_constituents[union_id], expert_id)
        end
        constituent_to_union[expert_id] = union_id
    end

    println("  Union mapping loaded: $(length(union_to_constituents)) unions, $(length(constituent_to_union)) constituents")
    return union_to_constituents, constituent_to_union
end

#############################################################################
## SEGMENT-BASED INDEXING CONFIGURATION
## Split parties at gaps > MAX_GAP years to avoid flat posteriors
#############################################################################
const MAX_GAP = 7      # Maximum years between observations within a segment
const MIN_OBS = 2      # Minimum observations per segment (drop segments with fewer)

#############################################################################
## 2D MODEL MAPPING CONFIGURATION
## Maps type_high/type_low pairs to dim_idx + direction
#############################################################################
const TYPE_TO_DIM_DIRECTION = Dict(
    # Economic dimension: pro_market = right (+1), pro_welfare = left (-1)
    ("pro_market", "pro_welfare") => (dim_idx=1, direction=1),   # Right
    ("pro_welfare", "pro_market") => (dim_idx=1, direction=-1),  # Left

    # Cultural dimension: traditional = TAN (+1), cosmopolitan = GAL (-1)
    ("traditional", "cosmopolitan") => (dim_idx=2, direction=1), # TAN
    ("cosmopolitan", "traditional") => (dim_idx=2, direction=-1) # GAL
)

# Expert dimension mapping (lrecon -> economic, galtan/cultural -> galtan)
const EXPERT_VAR_TO_DIM = Dict(
    "lrecon_ches" => 1,
    "lrecon_vparty" => 1,
    "welf_vparty" => 1,
    "lrecon_gps" => 1,
    "lrecon_poppa" => 1,
    "galtan_ches" => 2,
    "libcon_gps" => 2,
    "immig_vparty" => 2,
    "lgbt_vparty" => 2,
    "culsup_vparty" => 2,
    "relig_vparty" => 2,
    "gender_vparty" => 2
)

function load_and_preprocess_4dim_data(start_year=1950; data_dir::String=".")
    println("Loading 4D latent trait data files...")
    println("Start year filter: $start_year")
    data_dir != "." && println("Data directory: $data_dir")

    # Load union mapping (check data_dir first, fall back to project root)
    println("\nLoading union mapping...")
    union_mapping_dir = isfile(joinpath(data_dir, "data", "union_mapping.csv")) ? data_dir : "."
    union_to_constituents, constituent_to_union = load_union_mapping(union_mapping_dir)

    # Load the three datasets
    text_data_raw = CSV.read(joinpath(data_dir, "text_data.csv"), DataFrame)
    expert_raw = CSV.read(joinpath(data_dir, "expert.csv"), DataFrame)
    lr_data_raw = CSV.read(joinpath(data_dir, "lr_data.csv"), DataFrame)

    # Filter to start year BEFORE calculating year0
    text_data_raw = text_data_raw[text_data_raw.year .>= start_year, :]
    expert_raw = expert_raw[expert_raw.year .>= start_year, :]
    lr_data_raw = lr_data_raw[lr_data_raw.year .>= start_year, :]

    println("Data filtered to $start_year onwards:")
    println("  Text data (manifesto + PolDem): $(nrow(text_data_raw)) observations")
    println("  Expert: $(nrow(expert_raw)) observations")
    println("  L-R data: $(nrow(lr_data_raw)) observations")

    # Define base year for relative time indexing
    year0 = Int(minimum([minimum(text_data_raw.year), minimum(expert_raw.year), minimum(lr_data_raw.year)])) - 1
    println("Base year set to: $year0")

    # Create type mapping for the four dimensions
    type_map = Dict(
        "pro_market" => 1,
        "pro_welfare" => 2,
        "cosmopolitan" => 3,
        "traditional" => 4
    )

    println("Type mapping: pro_market=1, pro_welfare=2, cosmopolitan=3, traditional=4")

    # Process text data (manifesto + PolDem media)
    text_data = copy(text_data_raw)
    text_data = text_data[text_data.year .> year0, :]

    # Add type indices for text items (V4/V10: bipolar bridge structure)
    if !("type_high" in names(text_data)) || !("type_low" in names(text_data))
        error("Text data must contain 'type_high' and 'type_low' columns with values: pro_market, pro_welfare, cosmopolitan, traditional")
    end

    text_data.type_high_idx = [type_map[t] for t in text_data.type_high]
    text_data.type_low_idx = [type_map[t] for t in text_data.type_low]

    # V1 (2D model): Add dim_idx and direction columns
    # Maps type_high/type_low to single dimension + direction
    dim_idx_man = Int[]
    direction_man = Int[]
    for row in eachrow(text_data)
        key = (row.type_high, row.type_low)
        if haskey(TYPE_TO_DIM_DIRECTION, key)
            mapping = TYPE_TO_DIM_DIRECTION[key]
            push!(dim_idx_man, mapping.dim_idx)
            push!(direction_man, mapping.direction)
        else
            # Unknown mapping - this should not happen with valid data
            error("Unknown type_high/type_low pair: $(row.type_high) / $(row.type_low)")
        end
    end
    text_data.dim_idx_man = dim_idx_man
    text_data.direction_man = direction_man

    # Standard processing
    text_data.country = categorical(text_data.country)
    text_data.party = categorical(text_data.party)
    text_data.var = categorical(text_data.var)
    text_data.Year = Int.(text_data.year) .- year0
    sort!(text_data, [:country, :party, :year, :var])
    println("Text data processed: $(nrow(text_data)) observations with bipolar bridge structure")

    # Process expert dimension-specific data (bipolar items like lrecon_ches, galtan_ches)
    expert_dim_vars = ["lrecon_ches", "galtan_ches", "lrecon_vparty", "welf_vparty",
                       "lrecon_gps", "libcon_gps", "lrecon_poppa",
                       "immig_vparty", "lgbt_vparty", "culsup_vparty", "relig_vparty", "gender_vparty"]
    expert_dim = expert_raw[in.(expert_raw.var, Ref(expert_dim_vars)), :]
    expert_dim = expert_dim[(expert_dim.year .> year0) .& (expert_dim.val .>= 0) .& (expert_dim.val .<= 1), :]

    # V5: Load integer observations, scale sizes, and expert counts for beta-binomial likelihood
    expert_dim.val_int = Int.(expert_dim.val_int)
    expert_dim.n_scale = Int.(expert_dim.n_scale)
    expert_dim.n_experts = Int.(expert_dim.n_experts)

    # Add type mappings for dimension-specific expert data
    if !("type_low" in names(expert_dim)) || !("type_high" in names(expert_dim))
        error("Expert data must contain 'type_low' and 'type_high' columns")
    end

    expert_dim.type_high_idx = [type_map[t] for t in expert_dim.type_high]
    expert_dim.type_low_idx = [type_map[t] for t in expert_dim.type_low]

    # V1 (2D model): Add dim_idx for expert dimension data
    dim_idx_exp = Int[]
    for row in eachrow(expert_dim)
        var_name = string(row.var)
        if haskey(EXPERT_VAR_TO_DIM, var_name)
            push!(dim_idx_exp, EXPERT_VAR_TO_DIM[var_name])
        else
            # Fallback: infer from type_high/type_low
            key = (row.type_high, row.type_low)
            if haskey(TYPE_TO_DIM_DIRECTION, key)
                push!(dim_idx_exp, TYPE_TO_DIM_DIRECTION[key].dim_idx)
            else
                error("Unknown expert variable: $var_name with type pair $(row.type_high) / $(row.type_low)")
            end
        end
    end
    expert_dim.dim_idx_exp = dim_idx_exp

    expert_dim.country = categorical(expert_dim.country)
    expert_dim.party = categorical(expert_dim.party)
    expert_dim.var = categorical(expert_dim.var)
    expert_dim.Year = Int.(expert_dim.year) .- year0
    sort!(expert_dim, [:country, :party, :year, :var])
    println("Expert dimension-specific data processed: $(nrow(expert_dim)) observations")

    # Process expert general L-R data (cross-dimensional anchoring)
    lr_vars = ["lr_ches", "lr_poppa", "lr_morgan"]  # General left-right items
    expert_lr = lr_data_raw[in.(lr_data_raw.var, Ref(lr_vars)), :]
    expert_lr = expert_lr[(expert_lr.year .> year0) .& (expert_lr.val .>= 0) .& (expert_lr.val .<= 1), :]

    # V5: Load integer observations, scale sizes, and expert counts for beta-binomial likelihood
    expert_lr.val_int = Int.(expert_lr.val_int)
    expert_lr.n_scale = Int.(expert_lr.n_scale)
    expert_lr.n_experts = Int.(expert_lr.n_experts)

    expert_lr.country = categorical(expert_lr.country)
    expert_lr.party = categorical(expert_lr.party)
    expert_lr.var = categorical(expert_lr.var)
    expert_lr.Year = Int.(expert_lr.year) .- year0
    sort!(expert_lr, [:country, :party, :year, :var])
    println("Expert general L-R data processed: $(nrow(expert_lr)) observations")

    # Validate data integrity
    println("\nData validation:")

    # Check text data dimension pair distribution (V4: bipolar bridges)
    type_pair_counts = combine(groupby(text_data, [:type_high, :type_low]), nrow => :count)
    for row in eachrow(type_pair_counts)
        println("  $(row.type_high) ↔ $(row.type_low): $(row.count) text data observations")
    end

    # Check expert dimension-specific type pairs
    type_pair_counts = combine(groupby(expert_dim, [:type_high, :type_low]), nrow => :count)
    for row in eachrow(type_pair_counts)
        println("  $(row.type_high) - $(row.type_low): $(row.count) expert dimension-specific observations")
    end

    # Check general L-R items
    lr_var_counts = combine(groupby(expert_lr, :var), nrow => :count)
    for row in eachrow(lr_var_counts)
        println("  $(row.var): $(row.count) general L-R observations")
    end

    # Check overlapping parties across datasets
    text_data_parties = Set(text_data.party)
    expert_dim_parties = Set(expert_dim.party)
    expert_lr_parties = Set(expert_lr.party)

    all_parties = union(text_data_parties, expert_dim_parties, expert_lr_parties)
    println("\nParty coverage:")
    println("  Total unique parties: $(length(all_parties))")
    println("  In text data: $(length(text_data_parties))")
    println("  In expert dimension-specific: $(length(expert_dim_parties))")
    println("  In expert general L-R: $(length(expert_lr_parties))")
    println("  In all three datasets: $(length(intersect(text_data_parties, expert_dim_parties, expert_lr_parties)))")

    return text_data, expert_dim, expert_lr, year0, union_to_constituents, constituent_to_union
end

# Execute if run directly
if abspath(PROGRAM_FILE) == @__FILE__
    text_data, expert_dim, expert_lr, year0, u2c, c2u = load_and_preprocess_4dim_data()
    println("4D data loading test completed successfully")
end