use sylpheed_formats::PakArchive;
use std::collections::BTreeMap;
fn be32(b:&[u8],o:usize)->u32{ if o+4<=b.len(){u32::from_be_bytes([b[o],b[o+1],b[o+2],b[o+3]])}else{0} }
fn magic(b:&[u8])->String{
    if b.len()<4 {return "(<4B)".into();}
    let m=&b[0..4];
    if m.iter().all(|&c|(0x20..0x7f).contains(&c)){ String::from_utf8_lossy(m).into() }
    else if m==b"\x89PNG"{"PNG".into()} else if m==[0,1,0,0]{"ttf".into()}
    else { format!("{:02x}{:02x}{:02x}{:02x}",m[0],m[1],m[2],m[3]) }
}
fn main(){
    let disc=std::env::var("SYLPHEED_DISC").unwrap();
    // Known/understood IDXD schemas (semantic parsers we have)
    let known_schema:BTreeMap<u32,&str>=[(0x067025b9,"ADVERTISE_MOVIE (movie_manifest)"),(0x13cb84ba,"sound registry / sounds.tbl")].into();
    let mut fmt_count:BTreeMap<String,(u64,u64)>=BTreeMap::new(); // fmt -> (count, bytes)
    let mut schema_census:BTreeMap<u32,(u64,u64,String)>=BTreeMap::new(); // schema -> (count,bytes,sample pak)
    let mut unknown_magics:BTreeMap<String,(u64,Vec<String>)>=BTreeMap::new();
    let paks:Vec<String>={let mut v=vec![]; for e in std::fs::read_dir(format!("{disc}/dat")).unwrap(){let p=e.unwrap().path(); if p.extension().map(|x|x=="pak").unwrap_or(false){v.push(p.file_stem().unwrap().to_string_lossy().into());}} v.sort(); v};
    println!("pak                         entries   decompressed   formats");
    for pk in &paks{
        let Ok(arc)=PakArchive::open(format!("{disc}/dat/{pk}.pak")) else{continue};
        let mut per:BTreeMap<String,u64>=BTreeMap::new();
        let mut tot=0u64; let n=arc.entries().len();
        for e in arc.entries(){
            let Ok(b)=arc.read(e) else{continue};
            tot+=b.len() as u64;
            let mut f=magic(&b);
            if f=="IDXD"{ let s=be32(&b,8); schema_census.entry(s).or_insert((0,0,pk.clone())).0+=1; schema_census.get_mut(&s).unwrap().1+=b.len() as u64; f=format!("IDXD:{s:08x}"); }
            else if !["XPR2","IXUD","LSTA","RATC","RIFF","XBG7","OTTO","PNG","ttf","DDS ","T8AD"].contains(&f.as_str()){
                let ent=unknown_magics.entry(f.clone()).or_insert((0,vec![])); ent.0+=1; if ent.1.len()<3 && !ent.1.contains(pk){ent.1.push(pk.clone());}
            }
            *per.entry(if f.starts_with("IDXD:"){"IDXD".into()}else{f.clone()}).or_default()+=1;
            let g=fmt_count.entry(if f.starts_with("IDXD:"){"IDXD".into()}else{f}).or_insert((0,0)); g.0+=1; g.1+=b.len() as u64;
        }
        let mut fs:Vec<_>=per.into_iter().collect(); fs.sort_by_key(|x|std::cmp::Reverse(x.1));
        let top:String=fs.iter().take(4).map(|(k,v)|format!("{k}×{v}")).collect::<Vec<_>>().join(" ");
        println!("{pk:26} {n:>7}   {:>10}KB   {top}", tot/1024);
    }
    println!("\n=== FORMAT TOTALS (across all paks) ===");
    let mut fv:Vec<_>=fmt_count.into_iter().collect(); fv.sort_by_key(|x|std::cmp::Reverse(x.1.1));
    for (f,(c,b)) in &fv{ println!("  {f:12} {c:>6} entries  {:>8}KB", b/1024); }
    println!("\n=== IDXD SCHEMA CENSUS ({} distinct schemas) ===", schema_census.len());
    let mut sv:Vec<_>=schema_census.into_iter().collect(); sv.sort_by_key(|x|std::cmp::Reverse(x.1.1));
    for (s,(c,b,pk)) in &sv{
        let tag=known_schema.get(s).copied().unwrap_or("??? UNDECODED");
        println!("  {s:08x}  {c:>5} ent  {:>7}KB  e.g. {pk:22} {tag}", b/1024);
    }
    println!("\n=== UNKNOWN / UNCLASSIFIED MAGICS ===");
    for (m,(c,pks)) in &unknown_magics{ println!("  {m:12} ×{c:<5} in {pks:?}"); }
}
