use std::sync::OnceLock;

use serde::Deserialize;

#[derive(Debug, Clone, Deserialize)]
pub(super) struct CheckedInSelectedYearBucketLadderArtifact {
    direct_lane_multipliers: Vec<f32>,
    complement_formula: CheckedInSelectedYearBucketComplementFormula,
    scaled_companion_formula: CheckedInSelectedYearBucketScaledCompanionFormula,
    entries: Vec<CheckedInSelectedYearBucketLadderEntry>,
}

#[derive(Debug, Clone, Deserialize)]
pub(super) struct CheckedInSelectedYearBucketLadderEntry {
    year: u32,
    value: f32,
}

#[derive(Debug, Clone, Deserialize)]
pub(super) struct CheckedInSelectedYearBucketComplementFormula {
    divisor: f32,
    multiplier: f32,
    bias: f32,
    scale: f32,
    floor: f32,
    build_106_multiplier: f32,
    cap: f32,
}

#[derive(Debug, Clone, Deserialize)]
pub(super) struct CheckedInSelectedYearBucketScaledCompanionFormula {
    numerator: f32,
    multiplier: f32,
    bias: f32,
    scale: f32,
}

#[derive(Debug, Clone)]
pub(crate) struct RuntimeSelectedYearBucketBands {
    pub(crate) direct: [f32; 3],
    pub(crate) complement: [f32; 3],
    pub(crate) scaled_companion: [f32; 3],
}

pub(super) fn checked_in_selected_year_bucket_ladder()
-> &'static CheckedInSelectedYearBucketLadderArtifact {
    static LADDER: OnceLock<CheckedInSelectedYearBucketLadderArtifact> = OnceLock::new();
    LADDER.get_or_init(|| {
        serde_json::from_str::<CheckedInSelectedYearBucketLadderArtifact>(include_str!(
            "../../../../../artifacts/exports/rt3-1.06/selected-year-bucket-ladder.json"
        ))
        .expect("checked-in selected-year bucket ladder should parse")
    })
}

pub fn runtime_world_selected_year_bucket_scalar_from_year_word(year_word: u32) -> Option<f32> {
    let ladder = &checked_in_selected_year_bucket_ladder().entries;
    if ladder.is_empty() {
        return None;
    }
    if year_word <= ladder[0].year {
        return Some(ladder[0].value);
    }
    for window in ladder.windows(2) {
        let start = &window[0];
        let end = &window[1];
        if year_word <= end.year {
            if year_word <= start.year || end.year == start.year {
                return Some(start.value);
            }
            let span = (end.year - start.year) as f32;
            let progress = (year_word - start.year) as f32 / span;
            return Some(start.value + (end.value - start.value) * progress);
        }
    }
    ladder.last().map(|entry| entry.value)
}

pub(crate) fn runtime_selected_year_bucket_bands_from_scalar(
    scalar: f32,
) -> Option<RuntimeSelectedYearBucketBands> {
    let artifact = checked_in_selected_year_bucket_ladder();
    if artifact.direct_lane_multipliers.len() != 3 {
        return None;
    }
    let direct = [
        scalar * artifact.direct_lane_multipliers[0],
        scalar * artifact.direct_lane_multipliers[1],
        scalar * artifact.direct_lane_multipliers[2],
    ];
    let mut complement = [0.0f32; 3];
    let mut scaled_companion = [0.0f32; 3];
    for (index, direct_value) in direct.iter().copied().enumerate() {
        let mut x = (((direct_value / artifact.complement_formula.divisor)
            * artifact.complement_formula.multiplier)
            + artifact.complement_formula.bias)
            * artifact.complement_formula.scale;
        x = x.max(artifact.complement_formula.floor);
        x *= artifact.complement_formula.build_106_multiplier;
        x = x.min(artifact.complement_formula.cap);
        complement[index] = 1.0 - x;
        scaled_companion[index] = (((artifact.scaled_companion_formula.numerator / direct_value)
            * artifact.scaled_companion_formula.multiplier)
            + artifact.scaled_companion_formula.bias)
            * artifact.scaled_companion_formula.scale;
    }
    Some(RuntimeSelectedYearBucketBands {
        direct,
        complement,
        scaled_companion,
    })
}

pub fn runtime_world_selected_year_gap_scalar_from_year_word(year_word: u32) -> Option<f32> {
    let normalized = (year_word as f64 - 1850.0) / 150.0;
    if !normalized.is_finite() {
        return None;
    }
    Some(normalized.clamp(1.0 / 3.0, 1.0) as f32)
}
