use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::Path;

use super::bindings::load_named_binding_comparison;
use super::probe::{probe_bca_selector_bytes, probe_bty_header};
use super::recovered_tables::summarize_recovered_table_families;
use super::types::{
    BuildingTypeBcaSelectorPatternSummary, BuildingTypeSourceEntry, BuildingTypeSourceFile,
    BuildingTypeSourceKind, BuildingTypeSourceReport,
};

pub fn inspect_building_types_dir(
    path: &Path,
) -> Result<BuildingTypeSourceReport, Box<dyn std::error::Error>> {
    inspect_building_types_dir_with_bindings(path, None)
}

pub fn inspect_building_types_dir_with_bindings(
    path: &Path,
    bindings_path: Option<&Path>,
) -> Result<BuildingTypeSourceReport, Box<dyn std::error::Error>> {
    let mut files = Vec::new();
    for entry in fs::read_dir(path)? {
        let entry = entry?;
        if !entry.file_type()?.is_file() {
            continue;
        }
        let file_name = entry.file_name().to_string_lossy().into_owned();
        let Some(extension) = Path::new(&file_name)
            .extension()
            .and_then(|extension| extension.to_str())
            .map(|extension| extension.to_ascii_lowercase())
        else {
            continue;
        };
        let source_kind = match extension.as_str() {
            "bca" => BuildingTypeSourceKind::Bca,
            "bty" => BuildingTypeSourceKind::Bty,
            _ => continue,
        };
        let bytes = fs::read(entry.path())?;
        let raw_stem = Path::new(&file_name)
            .file_stem()
            .and_then(|stem| stem.to_str())
            .unwrap_or("")
            .to_string();
        if raw_stem.is_empty() {
            continue;
        }
        files.push(BuildingTypeSourceFile {
            file_name,
            canonical_stem: canonicalize_building_stem(&raw_stem),
            raw_stem,
            source_kind: source_kind.clone(),
            byte_len: Some(bytes.len()),
            bca_selector_probe: match source_kind {
                BuildingTypeSourceKind::Bca => Some(probe_bca_selector_bytes(&bytes)),
                BuildingTypeSourceKind::Bty => None,
            },
            bty_header_probe: match source_kind {
                BuildingTypeSourceKind::Bca => None,
                BuildingTypeSourceKind::Bty => Some(probe_bty_header(&bytes)),
            },
        });
    }

    files.sort_by(|left, right| {
        left.canonical_stem
            .cmp(&right.canonical_stem)
            .then_with(|| left.file_name.cmp(&right.file_name))
    });

    let mut grouped = BTreeMap::<String, Vec<&BuildingTypeSourceFile>>::new();
    for file in &files {
        grouped
            .entry(file.canonical_stem.clone())
            .or_default()
            .push(file);
    }

    let entries = grouped
        .into_iter()
        .map(|(canonical_stem, group)| BuildingTypeSourceEntry {
            canonical_stem,
            raw_stems: group
                .iter()
                .map(|file| file.raw_stem.clone())
                .collect::<BTreeSet<_>>()
                .into_iter()
                .collect(),
            source_kinds: group
                .iter()
                .map(|file| file.source_kind.clone())
                .collect::<BTreeSet<_>>()
                .into_iter()
                .collect(),
            file_names: group
                .iter()
                .map(|file| file.file_name.clone())
                .collect::<BTreeSet<_>>()
                .into_iter()
                .collect(),
        })
        .collect::<Vec<_>>();

    let bca_file_count = files
        .iter()
        .filter(|file| matches!(file.source_kind, BuildingTypeSourceKind::Bca))
        .count();
    let bty_file_count = files
        .iter()
        .filter(|file| matches!(file.source_kind, BuildingTypeSourceKind::Bty))
        .count();
    let mut grouped_selector_patterns =
        BTreeMap::<(usize, String, String, String, String), Vec<String>>::new();
    for file in &files {
        let Some(probe) = &file.bca_selector_probe else {
            continue;
        };
        grouped_selector_patterns
            .entry((
                file.byte_len.unwrap_or_default(),
                probe.byte_0xb8_hex.clone(),
                probe.byte_0xb9_hex.clone(),
                probe.byte_0xba_hex.clone(),
                probe.byte_0xbb_hex.clone(),
            ))
            .or_default()
            .push(file.file_name.clone());
    }
    let bca_selector_patterns = grouped_selector_patterns
        .into_iter()
        .map(
            |(
                (byte_len, byte_0xb8_hex, byte_0xb9_hex, byte_0xba_hex, byte_0xbb_hex),
                file_names,
            )| BuildingTypeBcaSelectorPatternSummary {
                byte_len,
                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(12).collect(),
            },
        )
        .collect::<Vec<_>>();

    let notes = vec![
        "BuildingTypes sources are grouped by a canonical stem that lowercases and strips spaces, underscores, and hyphens so paired .bca/.bty variants collapse onto one asset token.".to_string(),
        "This report is an offline asset-pool view only; it does not by itself assign live candidate ids or prove scenario candidate-table availability.".to_string(),
        "For .bca files, the report also exposes the narrow selector-byte window at offsets 0xb8..0xbb used by the grounded aux-candidate and live-candidate stream decoders.".to_string(),
        "The recovered stock table above the Tier-2 building seam combines one style/theme subset with one source-kind table; this report now surfaces the matching on-disk filename families directly.".to_string(),
    ];

    let named_binding_comparison = if let Some(bindings_path) = bindings_path {
        Some(load_named_binding_comparison(bindings_path, &entries)?)
    } else {
        None
    };
    let recovered_table_summary = summarize_recovered_table_families(&entries, &files);

    Ok(BuildingTypeSourceReport {
        directory_path: path.display().to_string(),
        bca_file_count,
        bty_file_count,
        unique_canonical_stem_count: entries.len(),
        bca_selector_pattern_count: bca_selector_patterns.len(),
        named_binding_comparison,
        recovered_table_summary,
        notes,
        bca_selector_patterns,
        files,
        entries,
    })
}

pub(super) fn canonicalize_building_stem(stem: &str) -> String {
    stem.chars()
        .filter(|ch| !matches!(ch, ' ' | '_' | '-'))
        .flat_map(|ch| ch.to_lowercase())
        .collect()
}
