use super::*;

#[test]
fn classifies_rt3_105_post_span_bridge_variants() {
    let base_trailer = SmpRuntimeTrailerBlock {
        profile_family: "rt3-105-save-container-v1".to_string(),
        trailer_family: "rt3-105-save-trailer-v1".to_string(),
        trailer_evidence: vec![],
        trailer_offset: 944,
        prefix_words_0_to_5: vec![],
        prefix_hex_words_0_to_5: vec![],
        tag_word_6: 0,
        tag_word_6_hex: String::new(),
        tag_chunk_id_u16: 0x2ee1,
        tag_chunk_id_hex: "0x2ee1".to_string(),
        tag_chunk_id_grounded_alignment: None,
        length_word_7: 0x32c8_0000,
        length_word_7_hex: "0x32c80000".to_string(),
        length_high_u16: 0x32c8,
        length_high_hex: "0x32c8".to_string(),
        selector_word_8: 0x7110_0000,
        selector_word_8_hex: "0x71100000".to_string(),
        selector_high_u16: 0x7110,
        selector_high_hex: "0x7110".to_string(),
        layout_word_9: 0,
        layout_word_9_hex: String::new(),
        descriptor_word_10: 0x7801_0000,
        descriptor_word_10_hex: "0x78010000".to_string(),
        descriptor_high_u16: 0x7801,
        descriptor_high_hex: "0x7801".to_string(),
        descriptor_word_11: 0,
        descriptor_word_11_hex: String::new(),
        counter_word_12: 0,
        counter_word_12_hex: String::new(),
        offset_word_13: 0,
        offset_word_13_hex: String::new(),
        span_word_14: 0,
        span_word_14_hex: String::new(),
        mode_word_15: 0,
        mode_word_15_hex: String::new(),
        words: vec![],
        hex_words: vec![],
    };
    let base_post_span = SmpRuntimePostSpanProbe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        span_target_offset: 13944,
        next_nonzero_offset: Some(14795),
        next_aligned_candidate_offset: Some(20244),
        next_aligned_candidate_words: vec![],
        next_aligned_candidate_hex_words: vec![],
        header_candidates: vec![SmpRuntimePostSpanHeaderCandidate {
            offset: 20244,
            words: vec![],
            hex_words: vec![],
            dense_word_count: 3,
            high_u16_words: vec![0x6200, 0x0000, 0xfff7, 0x5515],
            high_hex_words: vec![],
            grounded_alignments: vec![],
        }],
        grounded_progress_hits: vec![],
    };
    let base_profile = SmpRt3105PackedProfileProbe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        packed_profile_offset: 29632,
        packed_profile_len: 0x108,
        packed_profile_len_hex: "0x108".to_string(),
        packed_profile_block: SmpRt3105PackedProfileBlock {
            relative_len: 0x108,
            relative_len_hex: "0x108".to_string(),
            leading_word_0: 3,
            leading_word_0_hex: "0x00000003".to_string(),
            trailing_zero_word_count_after_leading_word: 2,
            header_flag_word_3: 0x0100_0000,
            header_flag_word_3_hex: "0x01000000".to_string(),
            map_path_offset: 0x10,
            map_path: Some("Alternate USA.gmp".to_string()),
            display_name_offset: 0x43,
            display_name: Some("Alternate USA".to_string()),
            profile_byte_0x77: 0x07,
            profile_byte_0x77_hex: "0x07".to_string(),
            profile_byte_0x82: 0x4d,
            profile_byte_0x82_hex: "0x4d".to_string(),
            profile_byte_0x97: 0,
            profile_byte_0x97_hex: "0x00".to_string(),
            profile_byte_0xc5: 0,
            profile_byte_0xc5_hex: "0x00".to_string(),
            stable_nonzero_words: vec![],
        },
        ascii_runs: vec![],
    };
    let base_bridge = parse_rt3_105_post_span_bridge_probe(
        Some(&base_trailer),
        Some(&base_post_span),
        Some(&base_profile),
    )
    .expect("base bridge should parse");
    assert_eq!(
        base_bridge.bridge_family,
        "rt3-105-save-post-span-bridge-v1"
    );
    assert_eq!(base_bridge.packed_profile_delta_from_span_target, 15688);
    assert_eq!(
        base_bridge.next_candidate_delta_from_packed_profile,
        Some(-9388)
    );
    let base_variant_trailer = SmpRuntimeTrailerBlock {
        descriptor_word_10: 0x7401_0000,
        descriptor_word_10_hex: "0x74010000".to_string(),
        descriptor_high_u16: 0x7401,
        descriptor_high_hex: "0x7401".to_string(),
        ..base_trailer.clone()
    };
    let base_variant_bridge = parse_rt3_105_post_span_bridge_probe(
        Some(&base_variant_trailer),
        Some(&base_post_span),
        Some(&base_profile),
    )
    .expect("base bridge variant should parse");
    assert_eq!(
        base_variant_bridge.bridge_family,
        "rt3-105-save-post-span-bridge-v1"
    );

    let alt_trailer = SmpRuntimeTrailerBlock {
        profile_family: "rt3-105-alt-save-container-v1".to_string(),
        selector_word_8: 0x54cd_0000,
        selector_word_8_hex: "0x54cd0000".to_string(),
        selector_high_u16: 0x54cd,
        selector_high_hex: "0x54cd".to_string(),
        descriptor_word_10: 0x5901_0000,
        descriptor_word_10_hex: "0x59010000".to_string(),
        descriptor_high_u16: 0x5901,
        descriptor_high_hex: "0x5901".to_string(),
        ..base_trailer.clone()
    };
    let alt_post_span = SmpRuntimePostSpanProbe {
        profile_family: "rt3-105-alt-save-container-v1".to_string(),
        next_aligned_candidate_offset: Some(29892),
        header_candidates: vec![SmpRuntimePostSpanHeaderCandidate {
            offset: 29892,
            words: vec![],
            hex_words: vec![],
            dense_word_count: 3,
            high_u16_words: vec![0x1500, 0x0100, 0x4100, 0x0200],
            high_hex_words: vec![],
            grounded_alignments: vec![],
        }],
        ..base_post_span.clone()
    };
    let alt_profile = SmpRt3105PackedProfileProbe {
        profile_family: "rt3-105-alt-save-container-v1".to_string(),
        packed_profile_block: SmpRt3105PackedProfileBlock {
            map_path: Some("Spanish Mainline.gmp".to_string()),
            display_name: Some("Spanish Mainline".to_string()),
            profile_byte_0x82: 0xa3,
            profile_byte_0x82_hex: "0xa3".to_string(),
            ..base_profile.packed_profile_block.clone()
        },
        ..base_profile.clone()
    };
    let alt_bridge = parse_rt3_105_post_span_bridge_probe(
        Some(&alt_trailer),
        Some(&alt_post_span),
        Some(&alt_profile),
    )
    .expect("alt bridge should parse");
    assert_eq!(
        alt_bridge.bridge_family,
        "rt3-105-alt-save-post-span-bridge-v1"
    );
    assert_eq!(
        alt_bridge.next_candidate_delta_from_packed_profile,
        Some(260)
    );

    let scenario_trailer = SmpRuntimeTrailerBlock {
        profile_family: "rt3-105-scenario-save-container-v1".to_string(),
        trailer_family: "unknown".to_string(),
        trailer_offset: 864,
        length_word_7: 0,
        length_word_7_hex: "0x00000000".to_string(),
        length_high_u16: 0,
        length_high_hex: "0x0000".to_string(),
        selector_word_8: 0x0001_86a0,
        selector_word_8_hex: "0x000186a0".to_string(),
        selector_high_u16: 0x0001,
        selector_high_hex: "0x0001".to_string(),
        descriptor_word_10: 0x0186_a000,
        descriptor_word_10_hex: "0x0186a000".to_string(),
        descriptor_high_u16: 0x0186,
        descriptor_high_hex: "0x0186".to_string(),
        ..base_trailer.clone()
    };
    let scenario_post_span = SmpRuntimePostSpanProbe {
        profile_family: "rt3-105-scenario-save-container-v1".to_string(),
        span_target_offset: 864,
        next_aligned_candidate_offset: Some(940),
        header_candidates: vec![SmpRuntimePostSpanHeaderCandidate {
            offset: 940,
            words: vec![],
            hex_words: vec![],
            dense_word_count: 3,
            high_u16_words: vec![0x0186, 0x0006, 0x0006, 0x0001],
            high_hex_words: vec![],
            grounded_alignments: vec![],
        }],
        ..base_post_span.clone()
    };
    let scenario_profile = SmpRt3105PackedProfileProbe {
        profile_family: "rt3-105-scenario-save-container-v1".to_string(),
        packed_profile_block: SmpRt3105PackedProfileBlock {
            map_path: Some("Southern Pacific.gmp".to_string()),
            display_name: Some("Southern Pacific".to_string()),
            profile_byte_0x82: 0x90,
            profile_byte_0x82_hex: "0x90".to_string(),
            ..base_profile.packed_profile_block.clone()
        },
        ..base_profile.clone()
    };
    let scenario_bridge = parse_rt3_105_post_span_bridge_probe(
        Some(&scenario_trailer),
        Some(&scenario_post_span),
        Some(&scenario_profile),
    )
    .expect("scenario bridge should parse");
    assert_eq!(
        scenario_bridge.bridge_family,
        "rt3-105-scenario-post-span-bridge-v1"
    );
    assert_eq!(
        scenario_bridge.next_candidate_delta_from_packed_profile,
        Some(-28692)
    );
}

#[test]
fn parses_rt3_105_save_bridge_payload_probe() {
    let mut bytes = vec![0u8; 0x7000];
    let primary = 0x4f14usize;
    let secondary = 0x671cusize;
    let primary_words: [u32; 8] = [
        0x62000000, 0x00000000, 0xfff70000, 0x55150000, 0x55550000, 0x00000000, 0xfff70000,
        0x54550000,
    ];
    for (index, word) in primary_words.iter().enumerate() {
        bytes[primary + index * 4..primary + (index + 1) * 4]
            .copy_from_slice(&(*word).to_le_bytes());
    }

    let secondary_words: [u32; 8] = [
        0x00050000, 0x00050005, 0xfff70000, 0x54540000, 0x545400f9, 0x00f900f9, 0x00f94008,
        0x00001555,
    ];
    for (index, word) in secondary_words.iter().enumerate() {
        bytes[secondary + index * 4..secondary + (index + 1) * 4]
            .copy_from_slice(&(*word).to_le_bytes());
    }

    let bridge_probe = SmpRt3105PostSpanBridgeProbe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        bridge_family: "rt3-105-save-post-span-bridge-v1".to_string(),
        bridge_evidence: vec![],
        span_target_offset: 0x3678,
        next_candidate_offset: Some(primary),
        next_candidate_delta_from_span_target: Some(primary - 0x3678),
        packed_profile_offset: 0x73c0,
        packed_profile_delta_from_span_target: 0x3d48,
        next_candidate_delta_from_packed_profile: Some(primary as i64 - 0x73c0),
        selector_high_u16: 0x7110,
        selector_high_hex: "0x7110".to_string(),
        descriptor_high_u16: 0x7801,
        descriptor_high_hex: "0x7801".to_string(),
        next_candidate_high_u16_words: vec![0x6200, 0x0000, 0xfff7, 0x5515],
        next_candidate_high_hex_words: vec![],
    };

    let probe = parse_rt3_105_save_bridge_payload_probe(&bytes, Some(&bridge_probe))
        .expect("save bridge payload probe should parse");

    assert_eq!(probe.primary_block_offset, primary);
    assert_eq!(probe.primary_block_len, 0x20);
    assert_eq!(probe.secondary_block_offset, secondary);
    assert_eq!(probe.secondary_block_delta_from_primary, 0x1808);
    assert_eq!(probe.secondary_block_end_offset, 0x73c0);
    assert_eq!(probe.secondary_block_len, 0xca4);
    assert_eq!(probe.primary_words[..4], primary_words[..4]);
    assert_eq!(probe.secondary_words[..8], secondary_words[..8]);
}

#[test]
fn parses_rt3_105_save_name_table_probe() {
    let mut bytes = vec![0u8; 0x7400];
    let secondary = 0x671cusize;
    let header = secondary + 0x354;
    let entries = secondary + 0x3b5;
    let stride = 0x22usize;
    let names = ["AluminumMill", "Nuclear Power Plant", "Bakery"];

    bytes[header..header + 4].copy_from_slice(&0x10000000u32.to_le_bytes());
    bytes[header + 4..header + 8].copy_from_slice(&0x00009000u32.to_le_bytes());
    bytes[header + 8..header + 12].copy_from_slice(&0x0000332eu32.to_le_bytes());
    bytes[header + 0x1c..header + 0x20].copy_from_slice(&4u32.to_le_bytes());
    bytes[header + 0x20..header + 0x24].copy_from_slice(&(names.len() as u32).to_le_bytes());
    bytes[header + 12..header + 16].copy_from_slice(&1u32.to_le_bytes());
    bytes[header + 16..header + 20].copy_from_slice(&0x22u32.to_le_bytes());
    bytes[header + 20..header + 24].copy_from_slice(&2u32.to_le_bytes());
    bytes[header + 24..header + 28].copy_from_slice(&2u32.to_le_bytes());
    bytes[header + 0x28..header + 0x2c].copy_from_slice(&1u32.to_le_bytes());

    for (index, name) in names.iter().enumerate() {
        let off = entries + index * stride;
        let raw = &mut bytes[off..off + stride];
        raw[..name.len()].copy_from_slice(name.as_bytes());
        let trailer = if *name == "Nuclear Power Plant" {
            0u32
        } else {
            1u32
        };
        raw[stride - 4..stride].copy_from_slice(&trailer.to_le_bytes());
    }
    let footer = entries + names.len() * stride;
    bytes[footer..footer + 4].copy_from_slice(&0x32dcu32.to_le_bytes());
    bytes[footer + 4..footer + 8].copy_from_slice(&0x3714u32.to_le_bytes());
    bytes[footer + 8] = 0x00;

    let payload = SmpRt3105SaveBridgePayloadProbe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        bridge_family: "rt3-105-save-post-span-bridge-v1".to_string(),
        primary_block_offset: 0x4f14,
        primary_block_len: 0x20,
        primary_block_len_hex: "0x20".to_string(),
        primary_words: vec![],
        primary_hex_words: vec![],
        secondary_block_offset: secondary,
        secondary_block_delta_from_primary: 0x1808,
        secondary_block_delta_from_primary_hex: "0x1808".to_string(),
        secondary_block_end_offset: footer + 9,
        secondary_block_len: footer + 9 - secondary,
        secondary_block_len_hex: format!("0x{:x}", footer + 9 - secondary),
        secondary_preview_word_count: 32,
        secondary_words: vec![],
        secondary_hex_words: vec![],
        evidence: vec![],
    };

    let probe = parse_rt3_105_save_name_table_probe(
        &bytes,
        Some("gms"),
        Some(&SmpContainerProfile {
            profile_family: "rt3-105-save-container-v1".to_string(),
            profile_evidence: vec![],
            is_known_profile: true,
        }),
        Some(&payload),
    )
    .expect("save name table probe should parse");

    assert_eq!(probe.source_kind, "save-bridge-secondary-block");
    assert_eq!(
        probe.semantic_family,
        "scenario-named-candidate-availability-table"
    );
    assert_eq!(probe.header_offset, header);
    assert_eq!(probe.entry_stride, stride);
    assert_eq!(probe.observed_entry_capacity, 4);
    assert_eq!(probe.observed_entry_count, names.len());
    assert_eq!(probe.entries[0].text, "AluminumMill");
    assert_eq!(probe.entries[0].availability_dword, 1);
    assert_eq!(probe.entries[2].text, "Bakery");
    assert_eq!(probe.zero_trailer_entry_count, 1);
    assert_eq!(
        probe.zero_trailer_entry_names,
        vec!["Nuclear Power Plant".to_string()]
    );
    assert_eq!(probe.trailing_footer_hex, "dc3200001437000000");
    assert_eq!(probe.footer_progress_word_0, 0x32dc);
    assert_eq!(probe.footer_progress_word_1, 0x3714);
    assert_eq!(probe.footer_trailing_byte, 0x00);
}

#[test]
fn parses_rt3_105_map_name_table_probe_from_fixed_offsets() {
    let mut bytes = vec![0u8; 0x7400];
    let header = 0x6a70usize;
    let entries = 0x6ad1usize;
    let stride = 0x22usize;
    let observed_entry_count = 67usize;

    bytes[header..header + 4].copy_from_slice(&0x00000000u32.to_le_bytes());
    bytes[header + 4..header + 8].copy_from_slice(&0x00000000u32.to_le_bytes());
    bytes[header + 8..header + 12].copy_from_slice(&0x0000332eu32.to_le_bytes());
    bytes[header + 12..header + 16].copy_from_slice(&1u32.to_le_bytes());
    bytes[header + 16..header + 20].copy_from_slice(&0x22u32.to_le_bytes());
    bytes[header + 20..header + 24].copy_from_slice(&2u32.to_le_bytes());
    bytes[header + 24..header + 28].copy_from_slice(&2u32.to_le_bytes());
    bytes[header + 0x1c..header + 0x20].copy_from_slice(&0x44u32.to_le_bytes());
    bytes[header + 0x20..header + 0x24]
        .copy_from_slice(&(observed_entry_count as u32).to_le_bytes());
    bytes[header + 0x28..header + 0x2c].copy_from_slice(&1u32.to_le_bytes());

    for index in 0..observed_entry_count {
        let name = match index {
            0 => "AutoPlant".to_string(),
            1 => "Nuclear Power Plant".to_string(),
            66 => "Warehouse11".to_string(),
            _ => format!("Entry{index:02}"),
        };
        let off = entries + index * stride;
        let raw = &mut bytes[off..off + stride];
        raw[..name.len()].copy_from_slice(name.as_bytes());
        let trailer = if name == "Nuclear Power Plant" {
            0u32
        } else {
            1u32
        };
        raw[stride - 4..stride].copy_from_slice(&trailer.to_le_bytes());
    }

    let footer = entries + observed_entry_count * stride;
    bytes[footer..footer + 4].copy_from_slice(&0x32dcu32.to_le_bytes());
    bytes[footer + 4..footer + 8].copy_from_slice(&0x3714u32.to_le_bytes());
    bytes[footer + 8] = 0x00;

    let probe = parse_rt3_105_save_name_table_probe(
        &bytes,
        Some("gmp"),
        Some(&SmpContainerProfile {
            profile_family: "rt3-105-map-container-v1".to_string(),
            profile_evidence: vec![],
            is_known_profile: true,
        }),
        None,
    )
    .expect("map name table probe should parse");

    assert_eq!(probe.profile_family, "rt3-105-map-container-v1");
    assert_eq!(probe.source_kind, "map-fixed-catalog-range");
    assert_eq!(probe.header_offset, header);
    assert_eq!(probe.entries_offset, entries);
    assert_eq!(probe.observed_entry_count, observed_entry_count);
    assert_eq!(probe.entries[0].text, "AutoPlant");
    assert_eq!(probe.entries[66].text, "Warehouse11");
    assert_eq!(
        probe.zero_trailer_entry_names,
        vec!["Nuclear Power Plant".to_string()]
    );
    assert_eq!(probe.footer_progress_word_0, 0x32dc);
    assert_eq!(probe.footer_progress_word_1, 0x3714);
}

#[test]
fn parses_map_title_hint_probe_from_grounded_titles_and_embedded_map_reference() {
    let mut bytes = vec![0u8; 0x9000];
    let embedded_reference = b"Dutchlantis.gmp";
    let title = b"Dutchlantis";
    let later_title = b"Germany";

    bytes[0x73d0..0x73d0 + embedded_reference.len()].copy_from_slice(embedded_reference);
    bytes[0x73e0..0x73e0 + title.len()].copy_from_slice(title);
    bytes[0x8400..0x8400 + later_title.len()].copy_from_slice(later_title);

    let probe = parse_map_title_hint_probe(
        &bytes,
        Some("gmp"),
        Some(&SmpContainerProfile {
            profile_family: "rt3-105-map-container-v1".to_string(),
            profile_evidence: vec![],
            is_known_profile: true,
        }),
    )
    .expect("map title hint probe should parse");

    assert_eq!(probe.source_kind, "grounded-title-string-scan");
    assert_eq!(
        probe.profile_family,
        Some("rt3-105-map-container-v1".to_string())
    );
    assert_eq!(probe.grounded_title_hits.len(), 2);
    assert_eq!(probe.grounded_title_hits[0].title, "Germany");
    assert_eq!(probe.grounded_title_hits[0].earliest_offset, 0x8400);
    assert_eq!(probe.grounded_title_hits[1].title, "Dutchlantis");
    assert_eq!(probe.grounded_title_hits[1].earliest_offset, 0x73d0);
    assert_eq!(probe.embedded_map_references.len(), 1);
    assert_eq!(probe.embedded_map_references[0].offset, 0x73d0);
    assert_eq!(probe.embedded_map_references[0].text, "Dutchlantis.gmp");
    assert_eq!(probe.adjacent_reference_title_pairs.len(), 1);
    assert_eq!(
        probe
            .strongest_same_stem_pair
            .as_ref()
            .map(|pair| pair.title.as_str()),
        Some("Dutchlantis")
    );
    let pair = probe.strongest_same_stem_pair.expect("same-stem pair");
    assert_eq!(pair.map_reference_offset, 0x73d0);
    assert_eq!(pair.title_offset, 0x73d0);
    assert!(pair.normalized_stem_match);
    assert_eq!(pair.byte_distance, 0);
}

#[test]
fn parses_rt3_105_save_named_locomotive_availability_probe() {
    let mut bytes = vec![0u8; 0x9000];
    let packed_profile_offset = 0x73c0usize;
    let packed_profile_len = 0x108usize;
    let entries_offset = 0x7c78usize;
    let names = [
        ("Eight Wheeler 4-4-0", 1u32),
        ("EP-2 Bipolar", 1u32),
        ("ET22", 1u32),
        ("F3", 0u32),
        ("Fairlie 0-6-6-0", 1u32),
        ("Firefly 2-2-2", 0u32),
        ("FP45", 0u32),
        ("Ge 6/6 Crocodile", 1u32),
        ("GG1", 0u32),
        ("GP7", 1u32),
    ];

    for (index, (name, value)) in names.iter().enumerate() {
        let offset = entries_offset + index * RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE;
        bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
        bytes[offset + 4..offset + 4 + name.len()].copy_from_slice(name.as_bytes());
    }

    let probe = parse_rt3_105_save_named_locomotive_availability_probe(
        &bytes,
        Some("gms"),
        Some(&SmpContainerProfile {
            profile_family: "rt3-105-save-container-v1".to_string(),
            profile_evidence: vec![],
            is_known_profile: true,
        }),
        Some(&SmpRt3105PackedProfileProbe {
            profile_family: "rt3-105-save-container-v1".to_string(),
            packed_profile_offset,
            packed_profile_len,
            packed_profile_len_hex: "0x108".to_string(),
            packed_profile_block: SmpRt3105PackedProfileBlock {
                relative_len: packed_profile_len,
                relative_len_hex: "0x108".to_string(),
                leading_word_0: 3,
                leading_word_0_hex: "0x00000003".to_string(),
                trailing_zero_word_count_after_leading_word: 2,
                header_flag_word_3: 1,
                header_flag_word_3_hex: "0x00000001".to_string(),
                map_path_offset: 0x10,
                map_path: Some("Alternate USA.gmp".to_string()),
                display_name_offset: 0x43,
                display_name: Some("Alternate USA".to_string()),
                profile_byte_0x77: 0x07,
                profile_byte_0x77_hex: "0x07".to_string(),
                profile_byte_0x82: 0x4d,
                profile_byte_0x82_hex: "0x4d".to_string(),
                profile_byte_0x97: 0,
                profile_byte_0x97_hex: "0x00".to_string(),
                profile_byte_0xc5: 0,
                profile_byte_0xc5_hex: "0x00".to_string(),
                stable_nonzero_words: vec![],
            },
            ascii_runs: vec![],
        }),
    )
    .expect("save-side locomotive table probe should parse");

    assert_eq!(probe.source_kind, "save-direct-locomotive-row-run");
    assert_eq!(
        probe.semantic_family,
        "scenario-named-locomotive-availability-table"
    );
    assert_eq!(probe.entries_offset, entries_offset);
    assert_eq!(
        probe.entry_stride,
        RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE
    );
    assert_eq!(probe.observed_entry_count, names.len());
    assert_eq!(probe.zero_availability_count, 4);
    assert_eq!(probe.entries[0].text, "Eight Wheeler 4-4-0");
    assert_eq!(probe.entries[9].text, "GP7");
}

#[test]
fn classifies_rt3_105_alt_save_container_profile() {
    let shared_header = SmpSharedHeader {
        byte_len: 64,
        root_kind_word: 0x000025e5,
        root_kind_word_hex: "0x000025e5".to_string(),
        primary_family_tag: 0x00002ee0,
        primary_family_tag_hex: "0x00002ee0".to_string(),
        shared_signature_words_1_to_7: vec![
            0x00002ee0, 0x0001c001, 0x00018000, 0x00010000, 0x00000754, 0x00000754, 0x00000754,
        ],
        shared_signature_hex_words_1_to_7: vec![
            "0x00002ee0".to_string(),
            "0x0001c001".to_string(),
            "0x00018000".to_string(),
            "0x00010000".to_string(),
            "0x00000754".to_string(),
            "0x00000754".to_string(),
            "0x00000754".to_string(),
        ],
        matches_grounded_common_signature: false,
        payload_window_words_8_to_9: vec![0x007a5978, 0x007a9022],
        payload_window_hex_words_8_to_9: vec!["0x007a5978".to_string(), "0x007a9022".to_string()],
        reserved_words_10_to_14: vec![0; 5],
        reserved_words_10_to_14_all_zero: true,
        final_flag_word: 0,
        final_flag_word_hex: "0x00000000".to_string(),
    };
    let early_content_probe = SmpEarlyContentProbe {
        first_post_text_nonzero_offset: 722,
        zero_pad_after_text_len: 431,
        first_post_text_block_len: 35,
        first_post_text_block_hex:
            "0101010000010000000000000100000000000000010000000000000000010100000001".to_string(),
        trailing_zero_pad_after_first_block_len: 45,
        secondary_nonzero_offset: Some(802),
        secondary_aligned_word_window_offset: Some(800),
        secondary_aligned_word_window_words: vec![
            0x00010000, 0x49f00100, 0x00000002, 0xa0000000, 0x00000186, 0x00000000, 0x000186a0,
            0x00000000,
        ],
        secondary_aligned_word_window_hex_words: vec![
            "0x00010000".to_string(),
            "0x49f00100".to_string(),
            "0x00000002".to_string(),
            "0xa0000000".to_string(),
            "0x00000186".to_string(),
            "0x00000000".to_string(),
            "0x000186a0".to_string(),
            "0x00000000".to_string(),
        ],
        secondary_preview_hex: "01000001f04902000000000000a08601000000000000a08601000000000000a0"
            .to_string(),
    };

    let header_variant = classify_header_variant_probe(&shared_header);
    let secondary_variant =
        classify_secondary_variant_probe(&early_content_probe).expect("secondary probe");
    let container_profile =
        classify_container_profile(Some("gms"), Some(&header_variant), Some(&secondary_variant))
            .expect("container profile");

    assert_eq!(header_variant.variant_family, "rt3-105-alt-save-header-v1");
    assert_eq!(
        secondary_variant.variant_family,
        "rt3-105-gms-alt-family-v1"
    );
    assert_eq!(
        container_profile.profile_family,
        "rt3-105-alt-save-container-v1"
    );
    assert!(container_profile.is_known_profile);
}

#[test]
fn classifies_rt3_105_map_container_profiles_from_header_families() {
    let scenario_profile = classify_container_profile(
        Some("gmp"),
        Some(&SmpHeaderVariantProbe {
            variant_family: "rt3-105-scenario-save-header-v1".to_string(),
            variant_evidence: vec![],
            is_known_family: true,
        }),
        Some(&SmpSecondaryVariantProbe {
            aligned_window_offset: 0,
            words: vec![1, 0, 0, 0],
            hex_words: vec![],
            variant_family: "unknown".to_string(),
            variant_evidence: vec![],
        }),
    )
    .expect("scenario map profile");

    let alt_profile = classify_container_profile(
        Some("gmp"),
        Some(&SmpHeaderVariantProbe {
            variant_family: "rt3-105-alt-save-header-v1".to_string(),
            variant_evidence: vec![],
            is_known_family: true,
        }),
        Some(&SmpSecondaryVariantProbe {
            aligned_window_offset: 0,
            words: vec![0x49f00100, 2, 0xa0000000, 0x186],
            hex_words: vec![],
            variant_family: "unknown".to_string(),
            variant_evidence: vec![],
        }),
    )
    .expect("alt map profile");

    assert_eq!(
        scenario_profile.profile_family,
        "rt3-105-scenario-map-container-v1"
    );
    assert!(scenario_profile.is_known_profile);
    assert_eq!(alt_profile.profile_family, "rt3-105-alt-map-container-v1");
    assert!(alt_profile.is_known_profile);

    let generic_map_profile = classify_container_profile(
        Some("gmp"),
        Some(&SmpHeaderVariantProbe {
            variant_family: "rt3-map-header-family".to_string(),
            variant_evidence: vec![],
            is_known_family: true,
        }),
        Some(&SmpSecondaryVariantProbe {
            aligned_window_offset: 0,
            words: vec![0x00140000, 0x93e00100, 0x00000004, 0xa0000000],
            hex_words: vec![],
            variant_family: "rt3-map-secondary-family-v1".to_string(),
            variant_evidence: vec![],
        }),
    )
    .expect("generic map profile");

    assert_eq!(
        generic_map_profile.profile_family,
        "rt3-map-container-family"
    );
    assert!(generic_map_profile.is_known_profile);
}
