use std::collections::BTreeMap;

use super::scan::canonicalize_building_stem;
use super::types::{
    BuildingTypeBtyHeaderAliasSelectorSummary, BuildingTypeBtyHeaderDwordSummary,
    BuildingTypeBtyHeaderNameDwordSummary, BuildingTypeBtyHeaderNameSummary,
    BuildingTypeBtyNameBcaSelectorSummary, BuildingTypeRecoveredSourceFamilySummary,
    BuildingTypeRecoveredTableSummary, BuildingTypeSourceEntry, BuildingTypeSourceFile,
    BuildingTypeSourceKind,
};

pub(super) fn summarize_recovered_table_families(
    entries: &[BuildingTypeSourceEntry],
    files: &[BuildingTypeSourceFile],
) -> BuildingTypeRecoveredTableSummary {
    const RECOVERED_STYLE_THEMES: [&str; 6] =
        ["Victorian", "Tudor", "SoWest", "Persian", "Kyoto", "ClpBrd"];
    const RECOVERED_SOURCE_KINDS: [&str; 5] = [
        "StationSml",
        "StationMed",
        "StationLrg",
        "ServiceTower",
        "Maintenance",
    ];

    let entry_by_canonical = entries
        .iter()
        .map(|entry| (entry.canonical_stem.clone(), entry))
        .collect::<BTreeMap<_, _>>();

    let mut present_style_station_entries = Vec::new();
    for style in RECOVERED_STYLE_THEMES {
        for source_kind in ["StationSml", "StationMed", "StationLrg"] {
            let canonical = canonicalize_building_stem(&format!("{style}{source_kind}"));
            if let Some(entry) = entry_by_canonical.get(&canonical) {
                if let Some(raw_stem) = entry.raw_stems.first() {
                    present_style_station_entries.push(raw_stem.clone());
                }
            }
        }
    }
    present_style_station_entries.sort();
    present_style_station_entries.dedup();

    let mut present_standalone_entries = Vec::new();
    for raw_name in ["ServiceTower", "Maintenance"] {
        let canonical = canonicalize_building_stem(raw_name);
        if let Some(entry) = entry_by_canonical.get(&canonical) {
            if let Some(raw_stem) = entry.raw_stems.first() {
                present_standalone_entries.push(raw_stem.clone());
            }
        }
    }
    present_standalone_entries.sort();
    present_standalone_entries.dedup();

    let recovered_source_family_summaries = summarize_recovered_source_family_rows(
        entries,
        files,
        &present_style_station_entries,
        &present_standalone_entries,
    );

    let mut bare_port_warehouse_files = files
        .iter()
        .filter(|file| matches!(file.canonical_stem.as_str(), "port" | "warehouse"))
        .map(|file| file.file_name.clone())
        .collect::<Vec<_>>();
    bare_port_warehouse_files.sort();
    bare_port_warehouse_files.dedup();

    let mut nonzero_bty_header_dword_groups = BTreeMap::<u32, Vec<String>>::new();
    for file in files {
        let Some(probe) = &file.bty_header_probe else {
            continue;
        };
        if probe.dword_0xbb == 0 {
            continue;
        }
        nonzero_bty_header_dword_groups
            .entry(probe.dword_0xbb)
            .or_default()
            .push(file.file_name.clone());
    }
    let nonzero_bty_header_dword_summaries = nonzero_bty_header_dword_groups
        .into_iter()
        .map(|(dword_0xbb, mut file_names)| {
            file_names.sort();
            file_names.dedup();
            BuildingTypeBtyHeaderDwordSummary {
                dword_0xbb,
                dword_0xbb_hex: format!("0x{dword_0xbb:08x}"),
                file_count: file_names.len(),
                sample_file_names: file_names.into_iter().take(24).collect(),
            }
        })
        .collect();

    let nonzero_bty_header_name_0x40_summaries =
        summarize_nonzero_bty_header_name_lane(files, 0x40, |probe| &probe.name_0x40);
    let nonzero_bty_header_name_0x5e_summaries =
        summarize_nonzero_bty_header_name_lane(files, 0x5e, |probe| &probe.name_0x5e);
    let nonzero_bty_header_name_0x7c_summaries =
        summarize_nonzero_bty_header_name_lane(files, 0x7c, |probe| &probe.name_0x7c);
    let nonzero_bty_header_alias_selector_summaries =
        summarize_nonzero_bty_header_alias_selector_patterns(entries, files);
    let bty_header_name_0x5e_dword_summaries =
        summarize_bty_header_name_lane_by_dword(files, 0x5e, |probe| &probe.name_0x5e);
    let bty_name_0x5e_bca_selector_summaries =
        summarize_bty_name_0x5e_bca_selector_patterns(entries, files);

    BuildingTypeRecoveredTableSummary {
        recovered_style_themes: RECOVERED_STYLE_THEMES
            .into_iter()
            .map(str::to_string)
            .collect(),
        recovered_source_kinds: RECOVERED_SOURCE_KINDS
            .into_iter()
            .map(str::to_string)
            .collect(),
        present_style_station_entries,
        present_standalone_entries,
        recovered_source_family_summaries,
        bare_port_warehouse_files,
        nonzero_bty_header_dword_summaries,
        nonzero_bty_header_name_0x40_summaries,
        nonzero_bty_header_name_0x5e_summaries,
        nonzero_bty_header_name_0x7c_summaries,
        nonzero_bty_header_alias_selector_summaries,
        bty_header_name_0x5e_dword_summaries,
        bty_name_0x5e_bca_selector_summaries,
    }
}

fn summarize_nonzero_bty_header_name_lane(
    files: &[BuildingTypeSourceFile],
    offset: u32,
    selector: impl Fn(&super::types::BuildingTypeBtyHeaderProbe) -> &String,
) -> Vec<BuildingTypeBtyHeaderNameSummary> {
    let mut groups = BTreeMap::<String, Vec<String>>::new();
    for file in files {
        let Some(probe) = &file.bty_header_probe else {
            continue;
        };
        if probe.dword_0xbb == 0 {
            continue;
        }
        let header_value = selector(probe).trim();
        if header_value.is_empty() {
            continue;
        }
        groups
            .entry(header_value.to_string())
            .or_default()
            .push(file.file_name.clone());
    }

    let mut summaries = groups
        .into_iter()
        .map(|(header_value, mut file_names)| {
            file_names.sort();
            file_names.dedup();
            BuildingTypeBtyHeaderNameSummary {
                header_offset_hex: format!("0x{offset:02x}"),
                header_value,
                file_count: file_names.len(),
                sample_file_names: file_names.into_iter().take(24).collect(),
            }
        })
        .collect::<Vec<_>>();
    summaries.sort_by(|left, right| {
        right
            .file_count
            .cmp(&left.file_count)
            .then_with(|| left.header_offset_hex.cmp(&right.header_offset_hex))
            .then_with(|| left.header_value.cmp(&right.header_value))
    });
    summaries
}

fn summarize_recovered_source_family_rows(
    entries: &[BuildingTypeSourceEntry],
    files: &[BuildingTypeSourceFile],
    present_style_station_entries: &[String],
    present_standalone_entries: &[String],
) -> Vec<BuildingTypeRecoveredSourceFamilySummary> {
    let file_by_name = files
        .iter()
        .map(|file| (file.file_name.as_str(), file))
        .collect::<BTreeMap<_, _>>();

    let mut canonical_stems = present_style_station_entries
        .iter()
        .chain(present_standalone_entries.iter())
        .map(|raw_stem| canonicalize_building_stem(raw_stem))
        .collect::<Vec<_>>();
    canonical_stems.sort();
    canonical_stems.dedup();

    let mut summaries = Vec::new();
    for canonical_stem in canonical_stems {
        let Some(entry) = entries
            .iter()
            .find(|entry| entry.canonical_stem == canonical_stem)
        else {
            continue;
        };
        let bty_file = entry
            .file_names
            .iter()
            .filter_map(|name| file_by_name.get(name.as_str()))
            .find(|file| matches!(file.source_kind, BuildingTypeSourceKind::Bty));
        let Some(bty_file) = bty_file else {
            continue;
        };
        let Some(bty_probe) = &bty_file.bty_header_probe else {
            continue;
        };
        let bca_file = entry
            .file_names
            .iter()
            .filter_map(|name| file_by_name.get(name.as_str()))
            .find(|file| matches!(file.source_kind, BuildingTypeSourceKind::Bca));
        let bca_probe = bca_file.and_then(|file| file.bca_selector_probe.as_ref());
        summaries.push(BuildingTypeRecoveredSourceFamilySummary {
            canonical_stem: canonical_stem.clone(),
            sample_raw_stem: entry
                .raw_stems
                .first()
                .cloned()
                .unwrap_or_else(|| canonical_stem.clone()),
            has_bca_pair: bca_file.is_some(),
            type_id_hex: bty_probe.type_id_hex.clone(),
            name_0x40: bty_probe.name_0x40.clone(),
            name_0x5e: bty_probe.name_0x5e.clone(),
            name_0x7c: bty_probe.name_0x7c.clone(),
            dword_0xbb_hex: bty_probe.dword_0xbb_hex.clone(),
            byte_0xba_hex: bca_probe.map(|probe| probe.byte_0xba_hex.clone()),
            byte_0xbb_hex: bca_probe.map(|probe| probe.byte_0xbb_hex.clone()),
        });
    }
    summaries.sort_by(|left, right| {
        left.canonical_stem
            .cmp(&right.canonical_stem)
            .then_with(|| left.sample_raw_stem.cmp(&right.sample_raw_stem))
    });
    summaries
}

fn summarize_bty_header_name_lane_by_dword(
    files: &[BuildingTypeSourceFile],
    offset: u32,
    selector: impl Fn(&super::types::BuildingTypeBtyHeaderProbe) -> &String,
) -> Vec<BuildingTypeBtyHeaderNameDwordSummary> {
    let mut groups = BTreeMap::<(String, u32), Vec<String>>::new();
    for file in files {
        let Some(probe) = &file.bty_header_probe else {
            continue;
        };
        let header_value = selector(probe).trim();
        if header_value.is_empty() {
            continue;
        }
        groups
            .entry((header_value.to_string(), probe.dword_0xbb))
            .or_default()
            .push(file.file_name.clone());
    }

    let mut summaries = groups
        .into_iter()
        .map(|((header_value, dword_0xbb), mut file_names)| {
            file_names.sort();
            file_names.dedup();
            BuildingTypeBtyHeaderNameDwordSummary {
                header_offset_hex: format!("0x{offset:02x}"),
                header_value,
                dword_0xbb,
                dword_0xbb_hex: format!("0x{dword_0xbb:08x}"),
                file_count: file_names.len(),
                sample_file_names: file_names.into_iter().take(24).collect(),
            }
        })
        .collect::<Vec<_>>();
    summaries.sort_by(|left, right| {
        right
            .file_count
            .cmp(&left.file_count)
            .then_with(|| left.dword_0xbb.cmp(&right.dword_0xbb))
            .then_with(|| left.header_offset_hex.cmp(&right.header_offset_hex))
            .then_with(|| left.header_value.cmp(&right.header_value))
    });
    summaries
}

fn summarize_bty_name_0x5e_bca_selector_patterns(
    entries: &[BuildingTypeSourceEntry],
    files: &[BuildingTypeSourceFile],
) -> Vec<BuildingTypeBtyNameBcaSelectorSummary> {
    let file_by_name = files
        .iter()
        .map(|file| (file.file_name.as_str(), file))
        .collect::<BTreeMap<_, _>>();
    let mut groups = BTreeMap::<(String, u32, String, String), Vec<String>>::new();

    for entry in entries {
        let bty_file = entry
            .file_names
            .iter()
            .filter_map(|name| file_by_name.get(name.as_str()))
            .find(|file| matches!(file.source_kind, BuildingTypeSourceKind::Bty));
        let bca_file = entry
            .file_names
            .iter()
            .filter_map(|name| file_by_name.get(name.as_str()))
            .find(|file| matches!(file.source_kind, BuildingTypeSourceKind::Bca));
        let (Some(bty_file), Some(bca_file)) = (bty_file, bca_file) else {
            continue;
        };
        let (Some(bty_probe), Some(bca_probe)) =
            (&bty_file.bty_header_probe, &bca_file.bca_selector_probe)
        else {
            continue;
        };
        let header_value = bty_probe.name_0x5e.trim();
        if header_value.is_empty() {
            continue;
        }
        groups
            .entry((
                header_value.to_string(),
                bty_probe.dword_0xbb,
                bca_probe.byte_0xba_hex.clone(),
                bca_probe.byte_0xbb_hex.clone(),
            ))
            .or_default()
            .push(bty_file.file_name.clone());
    }

    let mut summaries = groups
        .into_iter()
        .map(
            |((header_value, dword_0xbb, byte_0xba_hex, byte_0xbb_hex), mut file_names)| {
                file_names.sort();
                file_names.dedup();
                BuildingTypeBtyNameBcaSelectorSummary {
                    header_offset_hex: "0x5e".to_string(),
                    header_value,
                    dword_0xbb,
                    dword_0xbb_hex: format!("0x{dword_0xbb:08x}"),
                    byte_0xba_hex,
                    byte_0xbb_hex,
                    file_count: file_names.len(),
                    sample_file_names: file_names.into_iter().take(24).collect(),
                }
            },
        )
        .collect::<Vec<_>>();
    summaries.sort_by(|left, right| {
        right
            .file_count
            .cmp(&left.file_count)
            .then_with(|| left.dword_0xbb.cmp(&right.dword_0xbb))
            .then_with(|| left.header_value.cmp(&right.header_value))
            .then_with(|| left.byte_0xba_hex.cmp(&right.byte_0xba_hex))
            .then_with(|| left.byte_0xbb_hex.cmp(&right.byte_0xbb_hex))
    });
    summaries
}

fn summarize_nonzero_bty_header_alias_selector_patterns(
    entries: &[BuildingTypeSourceEntry],
    files: &[BuildingTypeSourceFile],
) -> Vec<BuildingTypeBtyHeaderAliasSelectorSummary> {
    let file_by_name = files
        .iter()
        .map(|file| (file.file_name.as_str(), file))
        .collect::<BTreeMap<_, _>>();
    let mut groups = BTreeMap::<
        (String, String, String, u32, String, String, String, String),
        Vec<String>,
    >::new();

    for entry in entries {
        let bty_file = entry
            .file_names
            .iter()
            .filter_map(|name| file_by_name.get(name.as_str()))
            .find(|file| matches!(file.source_kind, BuildingTypeSourceKind::Bty));
        let bca_file = entry
            .file_names
            .iter()
            .filter_map(|name| file_by_name.get(name.as_str()))
            .find(|file| matches!(file.source_kind, BuildingTypeSourceKind::Bca));
        let (Some(bty_file), Some(bca_file)) = (bty_file, bca_file) else {
            continue;
        };
        let (Some(bty_probe), Some(bca_probe)) =
            (&bty_file.bty_header_probe, &bca_file.bca_selector_probe)
        else {
            continue;
        };
        if bty_probe.dword_0xbb == 0 {
            continue;
        }
        let name_0x40 = bty_probe.name_0x40.trim();
        let name_0x5e = bty_probe.name_0x5e.trim();
        let name_0x7c = bty_probe.name_0x7c.trim();
        if name_0x40.is_empty() || name_0x5e.is_empty() || name_0x7c.is_empty() {
            continue;
        }
        groups
            .entry((
                name_0x40.to_string(),
                name_0x5e.to_string(),
                name_0x7c.to_string(),
                bty_probe.dword_0xbb,
                bca_probe.byte_0xb8_hex.clone(),
                bca_probe.byte_0xb9_hex.clone(),
                bca_probe.byte_0xba_hex.clone(),
                bca_probe.byte_0xbb_hex.clone(),
            ))
            .or_default()
            .push(bty_file.file_name.clone());
    }

    let mut summaries = groups
        .into_iter()
        .map(
            |(
                (
                    name_0x40,
                    name_0x5e,
                    name_0x7c,
                    dword_0xbb,
                    byte_0xb8_hex,
                    byte_0xb9_hex,
                    byte_0xba_hex,
                    byte_0xbb_hex,
                ),
                mut file_names,
            )| {
                file_names.sort();
                file_names.dedup();
                BuildingTypeBtyHeaderAliasSelectorSummary {
                    name_0x40,
                    name_0x5e,
                    name_0x7c,
                    dword_0xbb,
                    dword_0xbb_hex: format!("0x{dword_0xbb:08x}"),
                    byte_0xb8_hex,
                    byte_0xb9_hex,
                    byte_0xba_hex,
                    byte_0xbb_hex,
                    file_count: file_names.len(),
                    sample_file_names: file_names.into_iter().take(24).collect(),
                }
            },
        )
        .collect::<Vec<_>>();
    summaries.sort_by(|left, right| {
        right
            .file_count
            .cmp(&left.file_count)
            .then_with(|| left.dword_0xbb.cmp(&right.dword_0xbb))
            .then_with(|| left.name_0x40.cmp(&right.name_0x40))
            .then_with(|| left.name_0x5e.cmp(&right.name_0x5e))
            .then_with(|| left.name_0x7c.cmp(&right.name_0x7c))
            .then_with(|| left.byte_0xb8_hex.cmp(&right.byte_0xb8_hex))
            .then_with(|| left.byte_0xb9_hex.cmp(&right.byte_0xb9_hex))
            .then_with(|| left.byte_0xba_hex.cmp(&right.byte_0xba_hex))
            .then_with(|| left.byte_0xbb_hex.cmp(&right.byte_0xbb_hex))
    });
    summaries
}
