use std::fs;
use std::path::Path;

use crate::inspect::smp::bundle::{
    SmpInspectionReport, SmpKnownTagHit, classify_container_profile, classify_header_variant_probe,
    classify_secondary_variant_probe, parse_preamble, parse_runtime_anchor_cycle_block,
    parse_runtime_post_span_probe, parse_runtime_trailer_block, parse_save_anchor_run_block,
    parse_save_bootstrap_block, parse_shared_header, probe_early_content_layout,
};
use crate::inspect::smp::catalog::offsets::bundle::TAG_OFFSET_SAMPLE_LIMIT;
use crate::inspect::smp::catalog::tags::KNOWN_TAG_DEFINITIONS;
use crate::inspect::smp::common::{
    SmpSaveUnclassifiedTaggedCollectionHeaderProbe,
    filter_unclassified_tagged_collection_header_probes_outside_known_spans, find_first_ascii_run,
    find_u16_le_offsets, scan_save_unclassified_tagged_collection_header_probes,
};
use crate::inspect::smp::events::parse_event_runtime_collection_summary;
use crate::inspect::smp::map_title::parse_map_title_hint_probe;
use crate::inspect::smp::profiles::*;
use crate::inspect::smp::regions::*;
use crate::inspect::smp::save_load::build_save_load_summary;
use crate::inspect::smp::special_conditions::*;
use crate::inspect::smp::structures::*;
use crate::inspect::smp::world::*;
use crate::inspect::smp::*;

pub fn inspect_smp_file(path: &Path) -> Result<SmpInspectionReport, Box<dyn std::error::Error>> {
    let bytes = fs::read(path)?;
    Ok(inspect_bundle_bytes(
        &bytes,
        path.extension()
            .and_then(|extension| extension.to_str())
            .map(|extension| extension.to_ascii_lowercase()),
    ))
}

pub fn inspect_smp_bytes(bytes: &[u8]) -> SmpInspectionReport {
    inspect_bundle_bytes(bytes, None)
}

pub fn inspect_unclassified_save_collection_headers_file(
    path: &Path,
) -> Result<Vec<SmpSaveUnclassifiedTaggedCollectionHeaderProbe>, Box<dyn std::error::Error>> {
    let bytes = fs::read(path)?;
    let file_extension_hint = path
        .extension()
        .and_then(|extension| extension.to_str())
        .map(|extension| extension.to_ascii_lowercase());
    let shared_header = parse_shared_header(&bytes);
    let header_variant_probe = shared_header.as_ref().map(classify_header_variant_probe);
    let first_ascii_run = find_first_ascii_run(&bytes);
    let early_content_probe = first_ascii_run
        .as_ref()
        .and_then(|ascii_run| probe_early_content_layout(&bytes, ascii_run));
    let secondary_variant_probe = early_content_probe
        .as_ref()
        .and_then(classify_secondary_variant_probe);
    let container_profile = classify_container_profile(
        file_extension_hint.as_deref(),
        header_variant_probe.as_ref(),
        secondary_variant_probe.as_ref(),
    );
    let save_company_collection_header_probe = parse_save_company_collection_header_probe(
        &bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
    );
    let save_chairman_profile_collection_header_probe =
        parse_save_chairman_profile_collection_header_probe(
            &bytes,
            file_extension_hint.as_deref(),
            container_profile.as_ref(),
        );
    let save_train_collection_header_probe = parse_save_train_collection_header_probe(
        &bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
    );
    let save_region_collection_header_probe = parse_save_region_collection_header_probe(
        &bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
    );
    let save_placed_structure_collection_header_probe =
        parse_save_placed_structure_collection_header_probe(
            &bytes,
            file_extension_hint.as_deref(),
            container_profile.as_ref(),
        );
    let known_header_probes = [
        save_company_collection_header_probe.as_ref(),
        save_chairman_profile_collection_header_probe.as_ref(),
        save_train_collection_header_probe.as_ref(),
        save_region_collection_header_probe.as_ref(),
        save_placed_structure_collection_header_probe.as_ref(),
    ];
    let probes = scan_save_unclassified_tagged_collection_header_probes(
        &bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
    );
    Ok(
        filter_unclassified_tagged_collection_header_probes_outside_known_spans(
            probes,
            &known_header_probes,
        ),
    )
}

pub(in crate::inspect::smp) fn inspect_bundle_bytes(
    bytes: &[u8],
    file_extension_hint: Option<String>,
) -> SmpInspectionReport {
    let known_tag_hits = KNOWN_TAG_DEFINITIONS
        .iter()
        .filter_map(|definition| {
            let offsets = find_u16_le_offsets(bytes, definition.tag_id);
            if offsets.is_empty() {
                return None;
            }

            Some(SmpKnownTagHit {
                tag_id: definition.tag_id,
                tag_hex: format!("0x{:04x}", definition.tag_id),
                label: definition.label.to_string(),
                grounded_meaning: definition.grounded_meaning.to_string(),
                hit_count: offsets.len(),
                sample_offsets: offsets
                    .iter()
                    .copied()
                    .take(TAG_OFFSET_SAMPLE_LIMIT)
                    .collect(),
                last_offset: offsets.last().copied(),
            })
        })
        .collect::<Vec<_>>();

    let shared_header = parse_shared_header(bytes);
    let header_variant_probe = shared_header.as_ref().map(classify_header_variant_probe);
    let first_ascii_run = find_first_ascii_run(bytes);
    let early_content_probe = first_ascii_run
        .as_ref()
        .and_then(|ascii_run| probe_early_content_layout(bytes, ascii_run));
    let secondary_variant_probe = early_content_probe
        .as_ref()
        .and_then(classify_secondary_variant_probe);
    let container_profile = classify_container_profile(
        file_extension_hint.as_deref(),
        header_variant_probe.as_ref(),
        secondary_variant_probe.as_ref(),
    );
    let runtime_anchor_cycle_block = parse_runtime_anchor_cycle_block(
        bytes,
        container_profile.as_ref(),
        secondary_variant_probe.as_ref(),
    );
    let save_bootstrap_block =
        parse_save_bootstrap_block(container_profile.as_ref(), secondary_variant_probe.as_ref());
    let save_anchor_run_block = parse_save_anchor_run_block(
        bytes,
        container_profile.as_ref(),
        save_bootstrap_block.as_ref(),
    );
    let runtime_trailer_block = parse_runtime_trailer_block(
        container_profile.as_ref(),
        runtime_anchor_cycle_block.as_ref(),
    );
    let runtime_post_span_probe =
        parse_runtime_post_span_probe(bytes, runtime_trailer_block.as_ref());
    let rt3_105_packed_profile_probe = parse_rt3_105_packed_profile_probe(
        bytes,
        file_extension_hint.as_deref(),
        header_variant_probe.as_ref(),
        container_profile.as_ref(),
    );
    let rt3_105_post_span_bridge_probe = parse_rt3_105_post_span_bridge_probe(
        runtime_trailer_block.as_ref(),
        runtime_post_span_probe.as_ref(),
        rt3_105_packed_profile_probe.as_ref(),
    );
    let rt3_105_save_bridge_payload_probe =
        parse_rt3_105_save_bridge_payload_probe(bytes, rt3_105_post_span_bridge_probe.as_ref());
    let save_world_selection_context_probe = parse_save_world_selection_context_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
    );
    let save_world_issue_37_probe = parse_save_world_issue_37_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
    );
    let save_world_economic_tuning_probe = parse_save_world_economic_tuning_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
    );
    let save_world_finance_neighborhood_probe = parse_save_world_finance_neighborhood_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
    );
    let save_company_collection_header_probe = parse_save_company_collection_header_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
    );
    let save_chairman_profile_collection_header_probe =
        parse_save_chairman_profile_collection_header_probe(
            bytes,
            file_extension_hint.as_deref(),
            container_profile.as_ref(),
        );
    let save_train_collection_header_probe = parse_save_train_collection_header_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
    );
    let save_train_collection_directory_probe = parse_save_train_collection_directory_probe(
        bytes,
        save_train_collection_header_probe.as_ref(),
    );
    let save_region_collection_header_probe = parse_save_region_collection_header_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
    );
    let save_region_record_triplet_probe =
        parse_save_region_record_triplet_probe(bytes, save_region_collection_header_probe.as_ref());
    let save_region_queued_notice_record_probe = parse_save_region_queued_notice_record_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
        save_region_collection_header_probe.as_ref(),
    );
    let save_region_fixed_row_run_candidate_probe = parse_save_region_fixed_row_run_candidate_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
        save_region_collection_header_probe.as_ref(),
    );
    let save_placed_structure_collection_header_probe =
        parse_save_placed_structure_collection_header_probe(
            bytes,
            file_extension_hint.as_deref(),
            container_profile.as_ref(),
        );
    let save_placed_structure_record_triplet_probe =
        parse_save_placed_structure_record_triplet_probe(
            bytes,
            save_placed_structure_collection_header_probe.as_ref(),
        );
    let save_placed_structure_dynamic_side_buffer_probe =
        parse_save_placed_structure_dynamic_side_buffer_probe(
            bytes,
            file_extension_hint.as_deref(),
            container_profile.as_ref(),
        );
    let known_header_probes = [
        save_company_collection_header_probe.as_ref(),
        save_chairman_profile_collection_header_probe.as_ref(),
        save_train_collection_header_probe.as_ref(),
        save_region_collection_header_probe.as_ref(),
        save_placed_structure_collection_header_probe.as_ref(),
    ];
    let save_unclassified_tagged_collection_header_probes =
        filter_unclassified_tagged_collection_header_probes_outside_known_spans(
            scan_save_unclassified_tagged_collection_header_probes(
                bytes,
                file_extension_hint.as_deref(),
                container_profile.as_ref(),
            ),
            &known_header_probes,
        );
    let save_company_roster_probe = parse_save_company_roster_probe(
        bytes,
        save_company_collection_header_probe.as_ref(),
        save_world_selection_context_probe.as_ref(),
    );
    let save_chairman_profile_table_probe = parse_save_chairman_profile_table_probe(
        bytes,
        save_chairman_profile_collection_header_probe.as_ref(),
        save_world_selection_context_probe.as_ref(),
        save_company_collection_header_probe.as_ref(),
    );
    let map_title_hint_probe = parse_map_title_hint_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
    );
    let rt3_105_save_name_table_probe = parse_rt3_105_save_name_table_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
        rt3_105_save_bridge_payload_probe.as_ref(),
    );
    let rt3_105_save_named_locomotive_availability_probe =
        parse_rt3_105_save_named_locomotive_availability_probe(
            bytes,
            file_extension_hint.as_deref(),
            container_profile.as_ref(),
            rt3_105_packed_profile_probe.as_ref(),
        );
    let special_conditions_probe = parse_special_conditions_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
    );
    let smp_aligned_runtime_rule_band_probe = parse_smp_aligned_runtime_rule_band_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
        special_conditions_probe.as_ref(),
    );
    let post_special_conditions_scalar_probe = parse_post_special_conditions_scalar_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
        special_conditions_probe.as_ref(),
    );
    let post_text_field_neighborhood_probe = parse_post_text_field_neighborhood_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
        special_conditions_probe.as_ref(),
    );
    let locomotive_policy_neighborhood_probe = parse_locomotive_policy_neighborhood_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
        special_conditions_probe.as_ref(),
    );
    let pre_recipe_scalar_plateau_probe = parse_pre_recipe_scalar_plateau_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
        special_conditions_probe.as_ref(),
    );
    let recipe_book_summary_probe = parse_recipe_book_summary_probe(
        bytes,
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
        special_conditions_probe.as_ref(),
    );
    let classic_rehydrate_profile_probe =
        parse_classic_rehydrate_profile_probe(bytes, runtime_post_span_probe.as_ref());
    let save_load_summary = build_save_load_summary(
        file_extension_hint.as_deref(),
        container_profile.as_ref(),
        runtime_trailer_block.as_ref(),
        rt3_105_post_span_bridge_probe.as_ref(),
        classic_rehydrate_profile_probe.as_ref(),
        rt3_105_packed_profile_probe.as_ref(),
        rt3_105_save_name_table_probe.as_ref(),
    );
    let event_runtime_collection_summary = parse_event_runtime_collection_summary(
        bytes,
        container_profile.as_ref(),
        save_load_summary.as_ref(),
    );
    let mut warnings = Vec::new();
    if bytes.is_empty() {
        warnings
            .push("File is empty, so no `.smp` container structure could be observed.".to_string());
    }

    if known_tag_hits.is_empty() {
        warnings.push(
            "No grounded runtime bundle tags were found in little-endian form. This does not prove the file is invalid."
                .to_string(),
        );
    }
    if shared_header.is_none() && !bytes.is_empty() {
        warnings.push(
            "File is shorter than the observed 64-byte common RT3 bundle preamble.".to_string(),
        );
    }
    if let Some(shared_header) = &shared_header {
        let header_family_is_known = header_variant_probe
            .as_ref()
            .map(|probe| probe.is_known_family)
            .unwrap_or(false);
        if !shared_header.matches_grounded_common_signature && !header_family_is_known {
            warnings.push(
                "The first 64-byte preamble does not match the currently observed shared RT3 bundle signature."
                    .to_string(),
            );
        }
    }
    if first_ascii_run.is_some() && early_content_probe.is_none() {
        warnings.push(
            "Found early text content but could not resolve the next stable nonzero region after its zero padding."
                .to_string(),
        );
    }
    if container_profile
        .as_ref()
        .is_some_and(|profile| !profile.is_known_profile)
    {
        warnings.push(
            "The current probes did not match any known composite container profile.".to_string(),
        );
    }
    if known_tag_hits
        .iter()
        .any(|hit| hit.hit_count > hit.sample_offsets.len())
    {
        warnings.push(
            "Known-tag offsets are sampled in this report. Large hit counts usually mean byte-pattern noise, not validated chunk boundaries."
                .to_string(),
        );
    }
    warnings.push(
        "Inspection scans raw bytes for a small grounded tag set only. It does not validate bundle layout or decode payloads."
            .to_string(),
    );

    SmpInspectionReport {
        inspection_mode: "grounded-tag-scan-plus-preamble".to_string(),
        file_extension_hint,
        file_size: bytes.len(),
        sha256: sha256_hex(bytes),
        preamble: parse_preamble(bytes),
        shared_header,
        header_variant_probe,
        first_ascii_run,
        early_content_probe,
        secondary_variant_probe,
        container_profile,
        save_bootstrap_block,
        save_anchor_run_block,
        runtime_anchor_cycle_block,
        runtime_trailer_block,
        runtime_post_span_probe,
        rt3_105_post_span_bridge_probe,
        rt3_105_save_bridge_payload_probe,
        save_world_selection_context_probe,
        save_world_issue_37_probe,
        save_world_economic_tuning_probe,
        save_world_finance_neighborhood_probe,
        save_company_collection_header_probe,
        save_chairman_profile_collection_header_probe,
        save_train_collection_header_probe,
        save_train_collection_directory_probe,
        save_region_collection_header_probe,
        save_region_record_triplet_probe,
        save_region_queued_notice_record_probe,
        save_region_fixed_row_run_candidate_probe,
        save_placed_structure_collection_header_probe,
        save_placed_structure_record_triplet_probe,
        save_placed_structure_dynamic_side_buffer_probe,
        save_unclassified_tagged_collection_header_probes,
        save_company_roster_probe,
        save_chairman_profile_table_probe,
        map_title_hint_probe,
        rt3_105_save_name_table_probe,
        rt3_105_save_named_locomotive_availability_probe,
        special_conditions_probe,
        smp_aligned_runtime_rule_band_probe,
        post_special_conditions_scalar_probe,
        post_text_field_neighborhood_probe,
        locomotive_policy_neighborhood_probe,
        pre_recipe_scalar_plateau_probe,
        recipe_book_summary_probe,
        classic_rehydrate_profile_probe,
        rt3_105_packed_profile_probe,
        save_load_summary,
        event_runtime_collection_summary,
        contains_grounded_runtime_tags: !known_tag_hits.is_empty(),
        known_tag_hits,
        notes: vec![
            "Grounded `.smp` runtime tags currently include mask-plane payload ids 0x2cee and 0x2d51.".to_string(),
            "Grounded sidecar-byte-plane bundle family currently spans 0x9471..0x9472.".to_string(),
            "The shared-header parse is intentionally conservative: it only names common preamble lanes and checks the observed RT3 bundle-family signature.".to_string(),
            "The header-variant probe classifies the preamble into one of the currently observed install-era families when possible."
                .to_string(),
            "The early-content probe resolves the first stable nonzero block after the padded scenario text and then captures the next aligned word window."
                .to_string(),
            "The secondary-variant probe classifies that aligned word window into one of the currently observed file-family patterns."
                .to_string(),
            "The recipe-book summary probe reports per-book structural signatures at the grounded recipe-book root [world+0x0fe7] without attempting a full cargo-line decode."
                .to_string(),
            "Where a recipe cargo-token word looks like two printable letters in its high 16 bits, the probe exposes that as one probable ASCII stem while still treating the wider token semantics as inferred."
                .to_string(),
            "The container-profile layer combines extension hint, header family, and second-window family into one observed container classification."
                .to_string(),
            "The save-bootstrap reader currently parses one conservative 8-word descriptor only for known save-container profiles."
                .to_string(),
            "The save-anchor-run reader follows that descriptor tail into the observed repeated 9-word anchor cycle and captures the first trailer words after the cycle diverges."
                .to_string(),
            "The runtime-anchor-cycle reader applies the same cycle/trailer scan across the currently known save and sandbox runtime container profiles."
                .to_string(),
            "The runtime-trailer reader classifies the first 16 words after the cycle divergence into the currently observed runtime trailer families."
                .to_string(),
            "The runtime post-span probe follows the trailer's high-16 span lane into the later file region and records the next nonzero bytes, the first aligned high-16-dense candidate window, and any grounded progress-id hits found nearby."
                .to_string(),
            "The RT3 1.05 post-span bridge probe correlates the trailer selector/descriptor lanes with the next candidate region and the later packed-profile block for the currently observed 1.05 save families."
                .to_string(),
            "The RT3 1.05 common-save bridge payload probe captures the two stable bridge-stage blocks currently observed under the base 1.05 save branch."
                .to_string(),
            "The RT3 1.05 candidate-availability table probe decodes the fixed-width trailing name table from either the common-save bridge payload or the fixed 0x6a70..0x73c0 source range when that header validates."
                .to_string(),
            "The RT3 1.05 save-side named locomotive availability probe scans the post-profile save region for the grounded fixed-width locomotive-name-plus-dword row family when that run is present."
                .to_string(),
            "The post-special-conditions scalar probe captures the fixed 0x0df4..0x0f30 dword window immediately after the hidden sentinel slot, splits it into the aligned-band overlap prefix and the later tail, and records the live-object offset alignment of that tail without claiming a byte-for-byte mirror."
                .to_string(),
            "The classic rehydrate-profile probe recognizes the grounded 0x32dc -> 0x3714 -> 0x3715 progress-id sequence and captures the exact 0x108-byte block between the latter two ids when that pattern appears."
                .to_string(),
            "The classic packed-profile block reader exposes the stable map-path, display-name, atlas-tracked latch bytes, and the small set of nonzero word lanes observed inside that 0x108-byte block."
                .to_string(),
            "The RT3 1.05 packed-profile probe recognizes the later string-bearing save block rooted at the first post-header .gmp path and exposes the observed map-path, display-name, atlas-tracked byte lanes, and stable nonzero words."
                .to_string(),
            format!(
                "Restore-side loading of the four sidecar byte planes is only grounded for bundle versions >= 0x{:04x}.",
                SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION
            ),
        ],
        warnings,
    }
}
