use std::env;
use std::fmt::Write as _;
use std::fs;
use std::path::Path;

const LANGS: &[(&str, &str)] = &[
    ("de", "De"),
    ("en", "En"),
    ("tr", "Tr"),
    ("ru", "Ru"),
    ("fr", "Fr"),
    ("es", "Es"),
    ("it", "It"),
    ("nl", "Nl"),
    ("pt", "Pt"),
    ("pl", "Pl"),
];

fn main() {
    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR set by cargo");
    let out_dir = Path::new(&out_dir);

    let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR set");
    let data_root = Path::new(&manifest_dir).parent().unwrap().join("data");

    let dict_size = resolve_dict_size();

    emit_dict(&data_root, out_dir, dict_size);
    emit_ngram(&data_root, out_dir);

    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-env-changed=PAPAGAN_DICT_SIZE");
}

// Priority: env var > dict-10k > dict-5k > default 3000. Env var is the
// power-user escape hatch; features are the declarative knob for most users.
// 3k is the empirical knee on a 5000-sentence Tatoeba evaluation — below it
// accuracy climbs fast, above it the curve is flat until 7k+.
fn resolve_dict_size() -> usize {
    if let Ok(s) = env::var("PAPAGAN_DICT_SIZE") {
        if let Ok(n) = s.parse() {
            return n;
        }
    }
    if env::var("CARGO_FEATURE_DICT_10K").is_ok() {
        return 10_000;
    }
    if env::var("CARGO_FEATURE_DICT_5K").is_ok() {
        return 5_000;
    }
    3_000
}

fn emit_dict(data_root: &Path, out_dir: &Path, dict_size: usize) {
    let mut entries: Vec<(String, &'static str, u16)> = Vec::new();

    for (feat, variant) in LANGS {
        if env::var(format!("CARGO_FEATURE_{}", feat.to_uppercase())).is_err() {
            continue;
        }
        let words_path = data_root.join(feat).join("words.txt");
        println!("cargo:rerun-if-changed={}", words_path.display());

        let Ok(content) = fs::read_to_string(&words_path) else {
            println!(
                "cargo:warning=data/{feat}/words.txt missing — run `cargo xtask build-data --langs {feat}`",
            );
            continue;
        };

        for (rank, word) in content.lines().take(dict_size).enumerate() {
            let word = word.trim();
            if word.is_empty() {
                continue;
            }
            entries.push((word.to_string(), variant, rank as u16));
        }
    }

    entries.sort_by(|a, b| a.0.cmp(&b.0));

    let mut out = String::from("// Generated by build.rs — do not edit.\n\nuse crate::lang::Lang;\n\npub(crate) static DICT_ENTRIES: &[(&str, Lang, u16)] = &[\n");
    for (word, variant, rank) in &entries {
        writeln!(out, "    ({word:?}, Lang::{variant}, {rank}),").unwrap();
    }
    out.push_str("];\n");

    fs::write(out_dir.join("dict_data.rs"), out).expect("write dict_data.rs");
}

fn emit_ngram(data_root: &Path, out_dir: &Path) {
    let mut out = String::from("// Generated by build.rs — do not edit.\n\n");

    for (feat, variant) in LANGS {
        if env::var(format!("CARGO_FEATURE_{}", feat.to_uppercase())).is_err() {
            continue;
        }
        let tri_path = data_root.join(feat).join("trigrams.txt");
        println!("cargo:rerun-if-changed={}", tri_path.display());

        let const_upper = variant.to_uppercase();
        let Ok(content) = fs::read_to_string(&tri_path) else {
            println!(
                "cargo:warning=data/{feat}/trigrams.txt missing — run `cargo xtask build-data --langs {feat}`",
            );
            writeln!(
                out,
                "pub(crate) static {const_upper}_TRIGRAMS: ::phf::Map<&'static str, f32> = ::phf::phf_map! {{}};"
            )
            .unwrap();
            writeln!(out, "pub(crate) const {const_upper}_FLOOR: f32 = 0.0;\n").unwrap();
            continue;
        };

        let mut floor: f32 = 0.0;
        let mut builder = phf_codegen::Map::<String>::new();
        for line in content.lines() {
            let Some((key, val)) = line.split_once('\t') else {
                continue;
            };
            let Ok(v) = val.trim().parse::<f32>() else {
                continue;
            };
            if key == "FLOOR" {
                floor = v;
            } else {
                builder.entry(key.to_string(), &format!("{v:?}_f32"));
            }
        }

        writeln!(
            out,
            "pub(crate) static {const_upper}_TRIGRAMS: ::phf::Map<&'static str, f32> = {};",
            builder.build()
        )
        .unwrap();
        writeln!(out, "pub(crate) const {const_upper}_FLOOR: f32 = {floor:?}_f32;\n").unwrap();
    }

    fs::write(out_dir.join("ngram_data.rs"), out).expect("write ngram_data.rs");
}
