use crate::inspect::smp::special_conditions::*;

pub(in crate::inspect::smp) fn parse_pre_recipe_scalar_plateau_probe(
    bytes: &[u8],
    file_extension_hint: Option<&str>,
    container_profile: Option<&SmpContainerProfile>,
    special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
) -> Option<SmpPreRecipeScalarPlateauProbe> {
    special_conditions_probe?;
    if PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET > bytes.len() {
        return None;
    }

    let profile_family = container_profile
        .map(|profile| profile.profile_family.clone())
        .unwrap_or_else(|| "unknown".to_string());
    let source_kind = match file_extension_hint.unwrap_or("") {
        "gmp" => "pre-recipe-scalar-plateau",
        "gms" => "pre-recipe-scalar-plateau",
        "gmx" => "pre-recipe-scalar-plateau",
        _ => "pre-recipe-scalar-plateau",
    }
    .to_string();

    let aligned_dword_count =
        (PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET - PRE_RECIPE_SCALAR_PLATEAU_OFFSET) / 4;
    let mut nonzero_lanes = Vec::new();
    for index in 0..aligned_dword_count {
        let absolute_offset = PRE_RECIPE_SCALAR_PLATEAU_OFFSET + index * 4;
        let value = read_u32_at(bytes, absolute_offset)?;
        if value == 0 {
            continue;
        }
        nonzero_lanes.push(SmpPreRecipeScalarPlateauLane {
            absolute_offset,
            relative_offset: absolute_offset - PRE_RECIPE_SCALAR_PLATEAU_OFFSET,
            absolute_offset_hex: format!("0x{absolute_offset:04x}"),
            relative_offset_hex: format!(
                "0x{:x}",
                absolute_offset - PRE_RECIPE_SCALAR_PLATEAU_OFFSET
            ),
            value,
            value_hex: format!("0x{value:08x}"),
            probable_f32_le: probable_normal_f32_string(value),
        });
    }

    let family_signature = match (
        read_u32_at(bytes, 0x0faf),
        read_u32_at(bytes, 0x0fb3),
        read_u32_at(bytes, 0x0fcb),
    ) {
        (Some(0x4000003f), Some(0xe560423f), Some(0x00000000)) => {
            "rt3-105-scenario-pre-recipe-plateau-v1"
        }
        (Some(0x8000003f), Some(0x75c28f3f), Some(0x00300000)) => {
            "rt3-105-base-pre-recipe-plateau-v1"
        }
        (Some(0x8000003f), Some(0x75c28f3f), Some(0xcdcdcd00)) => {
            "rt3-105-alt-pre-recipe-plateau-v1"
        }
        _ => "unknown",
    }
    .to_string();

    let mut evidence = vec![
        format!(
            "aligned scalar plateau spans file offsets 0x{PRE_RECIPE_SCALAR_PLATEAU_OFFSET:04x}..0x{PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET:04x}"
        ),
        "this plateau ends immediately before the grounded recipe-book root at [world+0x0fe7]".to_string(),
        "current grounding inside this span is still structural rather than semantic, so the probe only records aligned dword lanes and observed family signatures".to_string(),
    ];
    if !nonzero_lanes.is_empty() {
        evidence.push(format!(
            "observed {} nonzero aligned dword lanes in the pre-recipe plateau",
            nonzero_lanes.len()
        ));
    }
    if family_signature != "unknown" {
        evidence.push(format!(
            "matched observed family signature {family_signature}"
        ));
    }

    Some(SmpPreRecipeScalarPlateauProbe {
        profile_family,
        source_kind,
        window_offset: PRE_RECIPE_SCALAR_PLATEAU_OFFSET,
        window_end_offset: PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET,
        window_len: PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET - PRE_RECIPE_SCALAR_PLATEAU_OFFSET,
        window_len_hex: format!(
            "0x{:x}",
            PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET - PRE_RECIPE_SCALAR_PLATEAU_OFFSET
        ),
        aligned_dword_count,
        family_signature,
        nonzero_lanes,
        evidence,
    })
}

pub(in crate::inspect::smp) fn parse_recipe_book_summary_probe(
    bytes: &[u8],
    file_extension_hint: Option<&str>,
    container_profile: Option<&SmpContainerProfile>,
    special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
) -> Option<SmpRecipeBookSummaryProbe> {
    special_conditions_probe?;
    if RECIPE_BOOK_SUMMARY_END_OFFSET > bytes.len() {
        return None;
    }

    let profile_family = container_profile
        .map(|profile| profile.profile_family.clone())
        .unwrap_or_else(|| "unknown".to_string());
    let source_kind = match file_extension_hint.unwrap_or("") {
        "gmp" => "recipe-book-summary",
        "gms" => "recipe-book-summary",
        "gmx" => "recipe-book-summary",
        _ => "recipe-book-summary",
    }
    .to_string();

    let mut books = Vec::with_capacity(RECIPE_BOOK_COUNT);
    let mut mixed_head_count = 0usize;
    let mut mixed_line_area_count = 0usize;
    let mut cdcd_line_area_count = 0usize;
    let mut zero_line_area_count = 0usize;

    for book_index in 0..RECIPE_BOOK_COUNT {
        let book_offset = RECIPE_BOOK_ROOT_OFFSET + book_index * RECIPE_BOOK_STRIDE;
        let head = &bytes[book_offset..book_offset + RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET];
        let line_area_offset = book_offset + RECIPE_BOOK_LINE_AREA_OFFSET;
        let line_area = &bytes[line_area_offset..line_area_offset + RECIPE_BOOK_LINE_AREA_LEN];
        let max_annual_production_offset = book_offset + RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET;
        let max_annual_production_word = read_u32_at(bytes, max_annual_production_offset)?;
        let mut lines = Vec::with_capacity(RECIPE_BOOK_LINE_COUNT);
        for line_index in 0..RECIPE_BOOK_LINE_COUNT {
            let line_offset = line_area_offset + line_index * RECIPE_BOOK_LINE_STRIDE;
            let line = &bytes[line_offset..line_offset + RECIPE_BOOK_LINE_STRIDE];
            let supplied_cargo_token_window = &line[0x08..0x20];
            let demanded_cargo_token_window = &line[0x1c..0x30];
            let mode_word = read_u32_at(bytes, line_offset)?;
            let annual_amount_word = read_u32_at(bytes, line_offset + 0x04)?;
            let supplied_cargo_token_word = read_u32_at(bytes, line_offset + 0x08)?;
            let demanded_cargo_token_word = read_u32_at(bytes, line_offset + 0x1c)?;
            lines.push(SmpRecipeBookLineSummary {
                line_index,
                line_offset,
                line_offset_hex: format!("0x{line_offset:04x}"),
                line_kind: classify_recipe_book_region_kind(line).to_string(),
                line_signature_kind: classify_recipe_line_signature(
                    mode_word,
                    supplied_cargo_token_word,
                    demanded_cargo_token_word,
                )
                .to_string(),
                imports_to_runtime_descriptor: mode_word != 0,
                runtime_import_branch_kind: classify_recipe_runtime_import_branch(mode_word)
                    .to_string(),
                line_nonzero_byte_count: line.iter().filter(|byte| **byte != 0).count(),
                line_cdcd_byte_count: line.iter().filter(|byte| **byte == 0xcd).count(),
                line_first_16_hex: hex_encode(&line[..RECIPE_BOOK_HEAD_SAMPLE_LEN.min(line.len())]),
                mode_word_offset: line_offset,
                mode_word_offset_hex: format!("0x{line_offset:04x}"),
                mode_word,
                mode_word_hex: format!("0x{mode_word:08x}"),
                annual_amount_offset: line_offset + 0x04,
                annual_amount_offset_hex: format!("0x{:04x}", line_offset + 0x04),
                annual_amount_word,
                annual_amount_word_hex: format!("0x{annual_amount_word:08x}"),
                annual_amount_probable_f32_le: probable_normal_f32_string(annual_amount_word),
                supplied_cargo_token_offset: line_offset + 0x08,
                supplied_cargo_token_offset_hex: format!("0x{:04x}", line_offset + 0x08),
                supplied_cargo_token_word,
                supplied_cargo_token_word_hex: format!("0x{supplied_cargo_token_word:08x}"),
                supplied_cargo_token_layout_kind: classify_recipe_token_layout(
                    supplied_cargo_token_word,
                )
                .to_string(),
                supplied_cargo_token_window_hex: hex_encode(supplied_cargo_token_window),
                supplied_cargo_token_window_ascii: ascii_preview(supplied_cargo_token_window),
                supplied_cargo_token_active_in_runtime_import: mode_word != 0 && mode_word != 1,
                supplied_cargo_token_probable_high16_ascii_stem:
                    probable_recipe_token_high16_ascii_stem(supplied_cargo_token_word),
                demanded_cargo_token_offset: line_offset + 0x1c,
                demanded_cargo_token_offset_hex: format!("0x{:04x}", line_offset + 0x1c),
                demanded_cargo_token_word,
                demanded_cargo_token_word_hex: format!("0x{demanded_cargo_token_word:08x}"),
                demanded_cargo_token_layout_kind: classify_recipe_token_layout(
                    demanded_cargo_token_word,
                )
                .to_string(),
                demanded_cargo_token_window_hex: hex_encode(demanded_cargo_token_window),
                demanded_cargo_token_window_ascii: ascii_preview(demanded_cargo_token_window),
                demanded_cargo_token_active_in_runtime_import: mode_word == 1 || mode_word == 3,
                demanded_cargo_token_probable_high16_ascii_stem:
                    probable_recipe_token_high16_ascii_stem(demanded_cargo_token_word),
            });
        }

        let head_kind = classify_recipe_book_region_kind(head).to_string();
        let line_area_kind = classify_recipe_book_region_kind(line_area).to_string();
        if head_kind == "mixed" {
            mixed_head_count += 1;
        }
        match line_area_kind.as_str() {
            "zero" => zero_line_area_count += 1,
            "cdcd" => cdcd_line_area_count += 1,
            _ => mixed_line_area_count += 1,
        }

        books.push(SmpRecipeBookSummaryBook {
            book_index,
            book_offset,
            book_offset_hex: format!("0x{book_offset:04x}"),
            head_kind,
            head_nonzero_byte_count: head.iter().filter(|byte| **byte != 0).count(),
            head_cdcd_byte_count: head.iter().filter(|byte| **byte == 0xcd).count(),
            head_first_16_hex: hex_encode(&head[..RECIPE_BOOK_HEAD_SAMPLE_LEN.min(head.len())]),
            max_annual_production_offset,
            max_annual_production_offset_hex: format!("0x{max_annual_production_offset:04x}"),
            max_annual_production_word,
            max_annual_production_word_hex: format!("0x{max_annual_production_word:08x}"),
            max_annual_production_probable_f32_le: probable_normal_f32_string(
                max_annual_production_word,
            ),
            line_area_offset,
            line_area_offset_hex: format!("0x{line_area_offset:04x}"),
            line_area_len: RECIPE_BOOK_LINE_AREA_LEN,
            line_area_len_hex: format!("0x{:x}", RECIPE_BOOK_LINE_AREA_LEN),
            line_area_kind,
            line_area_nonzero_byte_count: line_area.iter().filter(|byte| **byte != 0).count(),
            line_area_cdcd_byte_count: line_area.iter().filter(|byte| **byte == 0xcd).count(),
            line_area_first_16_hex: hex_encode(
                &line_area[..RECIPE_BOOK_HEAD_SAMPLE_LEN.min(line_area.len())],
            ),
            lines,
        });
    }

    let mut evidence = vec![
        format!(
            "grounded recipe-book root begins at file offset 0x{RECIPE_BOOK_ROOT_OFFSET:04x} and runtime offset [world+0x{RECIPE_BOOK_ROOT_OFFSET:04x}]"
        ),
        format!(
            "parsed {RECIPE_BOOK_COUNT} fixed books with stride 0x{RECIPE_BOOK_STRIDE:x}, shared cap lane at +0x{RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET:x}, and five line slots at +0x{RECIPE_BOOK_LINE_AREA_OFFSET:x} with stride 0x{RECIPE_BOOK_LINE_STRIDE:x}"
        ),
        "this probe is structural only: it summarizes per-book heads plus five raw line records without decoding the mode or cargo-token semantics beyond the grounded offsets".to_string(),
    ];
    evidence.push(format!(
        "{mixed_head_count} books have mixed pre-line heads; line areas split into {zero_line_area_count} zero, {cdcd_line_area_count} cdcd, and {mixed_line_area_count} mixed books"
    ));

    Some(SmpRecipeBookSummaryProbe {
        profile_family,
        source_kind,
        root_offset: RECIPE_BOOK_ROOT_OFFSET,
        root_offset_hex: format!("0x{RECIPE_BOOK_ROOT_OFFSET:04x}"),
        runtime_object_root_offset: RECIPE_BOOK_ROOT_OFFSET,
        runtime_object_root_offset_hex: format!("0x{RECIPE_BOOK_ROOT_OFFSET:04x}"),
        book_count: RECIPE_BOOK_COUNT,
        book_stride: RECIPE_BOOK_STRIDE,
        book_stride_hex: format!("0x{:x}", RECIPE_BOOK_STRIDE),
        max_annual_production_relative_offset: RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET,
        max_annual_production_relative_offset_hex: format!(
            "0x{:x}",
            RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET
        ),
        line_area_relative_offset: RECIPE_BOOK_LINE_AREA_OFFSET,
        line_area_relative_offset_hex: format!("0x{:x}", RECIPE_BOOK_LINE_AREA_OFFSET),
        line_count: RECIPE_BOOK_LINE_COUNT,
        line_stride: RECIPE_BOOK_LINE_STRIDE,
        line_stride_hex: format!("0x{:x}", RECIPE_BOOK_LINE_STRIDE),
        books,
        evidence,
    })
}

pub(in crate::inspect::smp) fn matches_candidate_availability_table_header(
    bytes: &[u8],
    header_offset: usize,
) -> bool {
    matches!(
        (
            read_u32_at(bytes, header_offset + 0x08),
            read_u32_at(bytes, header_offset + 0x0c),
            read_u32_at(bytes, header_offset + 0x10),
            read_u32_at(bytes, header_offset + 0x14),
            read_u32_at(bytes, header_offset + 0x18),
            read_u32_at(bytes, header_offset + 0x1c),
            read_u32_at(bytes, header_offset + 0x20),
            read_u32_at(bytes, header_offset + 0x24),
            read_u32_at(bytes, header_offset + 0x28),
        ),
        (
            Some(0x0000332e),
            Some(0x00000001),
            Some(0x00000022),
            Some(0x00000002),
            Some(0x00000002),
            Some(_),
            Some(_),
            Some(0x00000000),
            Some(0x00000001),
        )
    )
}

pub(in crate::inspect::smp) fn classify_name_table_footer_progress_alignment(
    value: u32,
) -> Option<&'static str> {
    match value {
        0x32dc => Some(
            "Footer progress word 0x000032dc matches the grounded late rehydrate progress id 0x32dc.",
        ),
        0x3714 => Some(
            "Footer progress word 0x00003714 matches the grounded late rehydrate progress id 0x3714.",
        ),
        _ => None,
    }
}
