//! Scratch analysis: which IDXD fields are DECLARED but left at their default on
//! disc, per schema. Those defaults live in title code, so they can only be read
//! from the running game — this prints the shopping list for that dynamic capture.
//!
//! Run: cargo run -p sylpheed-formats --example defaulted_fields -- <disc-root>

use std::collections::{BTreeMap, BTreeSet};
use sylpheed_formats::idxd::IdxdObject;
use sylpheed_formats::pak::PakArchive;

fn is_number(s: &str) -> bool {
    let s = s.strip_suffix(['f', 'F']).unwrap_or(s);
    let t = s.strip_prefix(['-', '+']).unwrap_or(s);
    !t.is_empty() && t.chars().all(|c| c.is_ascii_digit() || c == '.')
}

/// Same shape-test the parser uses: an identifier-looking token that is not a
/// value literal is a field-name key.
fn is_key(s: &str) -> bool {
    !s.is_empty()
        && s.chars()
            .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
        && s.chars().next().is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
        && !is_number(s)
}

fn is_value(s: &str) -> bool {
    is_number(s) || !is_key(s)
}

fn main() {
    let root = std::env::args()
        .nth(1)
        .unwrap_or_else(|| "/home/fabi/RE - Project Sylpheed/sylph_extract".into());
    let paks: Vec<String> = std::env::args().skip(2).collect();
    let paks = if paks.is_empty() {
        vec![
            "dat/GP_MAIN_GAME_E.pak".to_string(),
            "dat/GP_HANGAR_ARSENAL.pak".to_string(),
        ]
    } else {
        paks
    };

    for pak_rel in &paks {
        let path = std::path::Path::new(&root).join(pak_rel);
        let Ok(arc) = PakArchive::open(&path) else {
            eprintln!("-- skip {pak_rel} (open failed)");
            continue;
        };
        println!("\n================ {pak_rel} ================");

        // schema -> key -> (n_set, n_defaulted, distinct values, example owners)
        type Stat = (usize, usize, BTreeSet<String>, Vec<String>);
        let mut per_schema: BTreeMap<u32, (usize, BTreeMap<String, Stat>)> = BTreeMap::new();

        for e in arc.entries() {
            let Ok(bytes) = arc.read(e) else { continue };
            let Ok(obj) = IdxdObject::parse(&bytes) else {
                continue;
            };
            let ident = obj.identity();
            let toks = obj.tokens().to_vec();
            let entry = per_schema.entry(obj.schema_hash).or_default();
            entry.0 += 1;
            // Track which keys this object declares, and whether each is valued.
            let mut seen_here: BTreeMap<String, Option<String>> = BTreeMap::new();
            for (i, t) in toks.iter().enumerate() {
                if !is_key(t) {
                    continue;
                }
                let prev = if i == 0 { None } else { Some(&toks[i - 1]) };
                let valued = prev.map(|p| is_value(p)).unwrap_or(false);
                let v = if valued { Some(toks[i - 1].clone()) } else { None };
                seen_here.entry(t.clone()).or_insert(v);
            }
            for (k, v) in seen_here {
                let s = entry.1.entry(k).or_default();
                match v {
                    Some(val) => {
                        s.0 += 1;
                        if s.2.len() < 12 {
                            s.2.insert(val);
                        }
                    }
                    None => {
                        s.1 += 1;
                        if s.3.len() < 6 {
                            s.3.push(ident.clone());
                        }
                    }
                }
            }
        }

        for (schema, (n_obj, keys)) in per_schema {
            if n_obj < 2 {
                continue;
            }
            let name = match schema {
                0x0426_e81d => "PLAYER",
                0x6ab4_825a => "WEAPON",
                0x43fa_a517 => "UNIT",
                0x3c5b_0549 => "VESSEL",
                0xbd86_d41c => "CHARACTER",
                0x3c9a_e32e => "STAGE",
                0xb412_e6d8 => "MESSAGE",
                _ => "?",
            };
            let defaulted: Vec<_> = keys
                .iter()
                .filter(|(_, s)| s.1 > 0)
                .collect();
            if defaulted.is_empty() {
                continue;
            }
            println!("\n--- schema 0x{schema:08x} {name}  ({n_obj} objects) ---");
            println!(
                "{:<30} {:>5} {:>5}   {}",
                "KEY", "set", "dflt", "values seen (≤12) | owners defaulting"
            );
            for (k, (n_set, n_def, vals, owners)) in defaulted {
                let vv: Vec<&str> = vals.iter().map(|s| s.as_str()).collect();
                println!(
                    "{:<30} {:>5} {:>5}   {} | {}",
                    k,
                    n_set,
                    n_def,
                    vv.join(","),
                    owners.join(",")
                );
            }
        }
    }
}
