use std::collections::BTreeSet;
use std::path::Path;

use crate::inspect::cargo::{
    NAMED_CARGO_PRICE_DESCRIPTOR_ROW_COUNT, NAMED_CARGO_PRODUCTION_DESCRIPTOR_ROW_COUNT,
};

use super::bindings::{CargoBindingRow, load_cargo_bindings};
use super::cargo_skin::inspect_cargo_skin_pk4;
use super::cargo_types::inspect_cargo_types_dir;
use super::registry::build_live_registry_entries;
use super::selector::{build_price_selector_candidate_registry, build_selector_from_bindings};
use super::types::{
    CargoEconomySourceReport, CargoSkinInspectionReport, CargoTypeInspectionReport,
};

pub fn inspect_cargo_economy_sources(
    cargo_types_dir: &Path,
    cargo_skin_pk4_path: &Path,
) -> Result<CargoEconomySourceReport, Box<dyn std::error::Error>> {
    inspect_cargo_economy_sources_with_bindings(cargo_types_dir, cargo_skin_pk4_path, None)
}

pub fn inspect_cargo_economy_sources_with_bindings(
    cargo_types_dir: &Path,
    cargo_skin_pk4_path: &Path,
    cargo_bindings_path: Option<&Path>,
) -> Result<CargoEconomySourceReport, Box<dyn std::error::Error>> {
    let cargo_types = inspect_cargo_types_dir(cargo_types_dir)?;
    let cargo_skins = inspect_cargo_skin_pk4(cargo_skin_pk4_path)?;
    let cargo_bindings = load_cargo_bindings(cargo_bindings_path)?;
    Ok(build_cargo_economy_source_report(
        cargo_types,
        cargo_skins,
        cargo_bindings.as_deref(),
    ))
}

pub(super) fn build_cargo_economy_source_report(
    cargo_types: CargoTypeInspectionReport,
    cargo_skins: CargoSkinInspectionReport,
    cargo_bindings: Option<&[CargoBindingRow]>,
) -> CargoEconomySourceReport {
    let cargo_type_visible_names = cargo_types
        .entries
        .iter()
        .map(|entry| entry.name.visible_name.clone())
        .collect::<BTreeSet<_>>();
    let cargo_skin_visible_names = cargo_skins
        .entries
        .iter()
        .map(|entry| entry.name.visible_name.clone())
        .collect::<BTreeSet<_>>();

    let shared_visible_name_count = cargo_type_visible_names
        .intersection(&cargo_skin_visible_names)
        .count();
    let visible_name_union_count = cargo_type_visible_names
        .union(&cargo_skin_visible_names)
        .count();
    let cargo_type_only_visible_names = cargo_type_visible_names
        .difference(&cargo_skin_visible_names)
        .cloned()
        .collect::<Vec<_>>();
    let cargo_skin_only_visible_names = cargo_skin_visible_names
        .difference(&cargo_type_visible_names)
        .cloned()
        .collect::<Vec<_>>();
    let live_registry_entries =
        build_live_registry_entries(&cargo_types.entries, &cargo_skins.entries);
    let production_selector = cargo_bindings.and_then(|bindings| {
        build_selector_from_bindings(
            bindings,
            &live_registry_entries,
            "cargo_production_named",
            "named_cargo_production",
            NAMED_CARGO_PRODUCTION_DESCRIPTOR_ROW_COUNT,
            "This selector is grounded from the checked-in named cargo production bindings artifact.",
            "The current grounded order matches the 50-row named cargo-production descriptor strip.",
        )
    });
    let price_selector = cargo_bindings
        .and_then(|bindings| {
            build_selector_from_bindings(
                bindings,
                &live_registry_entries,
                "cargo_price_named",
                "named_cargo_price",
                NAMED_CARGO_PRICE_DESCRIPTOR_ROW_COUNT,
                "This selector is grounded from the checked-in named cargo price bindings artifact.",
                "The current grounded order matches the 71-row named cargo-price descriptor strip.",
            )
        })
        .unwrap_or_else(|| build_price_selector_candidate_registry(&live_registry_entries));
    let price_selector_candidate_only_visible_names = price_selector
        .entries
        .iter()
        .map(|entry| entry.visible_name.as_str())
        .collect::<BTreeSet<_>>();
    let price_selector_candidate_only_visible_names = live_registry_entries
        .iter()
        .filter(|entry| {
            !price_selector_candidate_only_visible_names.contains(entry.visible_name.as_str())
        })
        .map(|entry| entry.visible_name.clone())
        .collect::<Vec<_>>();
    let price_selector_candidate_excess_count = live_registry_entries
        .len()
        .saturating_sub(NAMED_CARGO_PRICE_DESCRIPTOR_ROW_COUNT);

    let mut notes = Vec::new();
    notes.push(format!(
        "Named cargo-price descriptors 106..176 span {} rows, while named cargo-production descriptors 180..229 span {} rows.",
        NAMED_CARGO_PRICE_DESCRIPTOR_ROW_COUNT, NAMED_CARGO_PRODUCTION_DESCRIPTOR_ROW_COUNT
    ));
    notes.push(format!(
        "The inspected CargoTypes corpus exposes {} visible names, the inspected cargoSkin corpus exposes {} visible names, and their union exposes {} visible names.",
        cargo_types.unique_visible_name_count, cargo_skins.unique_visible_name_count, visible_name_union_count
    ));
    if !price_selector.exact_resolution {
        notes.push(
            "That visible-name union still does not match the 71-row named cargo-price strip, so this offline source reconstruction is groundwork rather than a complete price-selector binding."
                .to_string(),
        );
    } else {
        notes.push(
            "The checked-in bindings now close the 71-row named cargo-price strip on top of the merged live cargo registry."
                .to_string(),
        );
    }
    if cargo_types.unique_visible_name_count == NAMED_CARGO_PRODUCTION_DESCRIPTOR_ROW_COUNT {
        notes.push(
            "The CargoTypes corpus still matches the 50-row named cargo-production strip cardinality that grounds the current production bindings."
                .to_string(),
        );
    }

    CargoEconomySourceReport {
        cargo_types_dir: cargo_types.directory_path,
        cargo_skin_pk4_path: cargo_skins.pk4_path,
        named_cargo_price_row_count: NAMED_CARGO_PRICE_DESCRIPTOR_ROW_COUNT,
        named_cargo_production_row_count: NAMED_CARGO_PRODUCTION_DESCRIPTOR_ROW_COUNT,
        cargo_type_count: cargo_types.entry_count,
        cargo_skin_count: cargo_skins.entry_count,
        shared_visible_name_count,
        visible_name_union_count,
        cargo_type_only_visible_names,
        cargo_skin_only_visible_names,
        live_registry_count: live_registry_entries.len(),
        live_registry_entries,
        price_selector_candidate_excess_count,
        price_selector_candidate_only_visible_names,
        production_selector,
        price_selector,
        notes,
        cargo_type_entries: cargo_types.entries,
        cargo_skin_entries: cargo_skins.entries,
    }
}
