use super::*;

#[test]
fn parses_company_tagged_collection_header_probe_from_exact_u32_tags() {
    let mut bytes = vec![0u8; 0x400];
    let metadata_tag_offset = 0x40usize;
    let records_tag_offset = 0x140usize;
    let close_tag_offset = 0x180usize;
    bytes[metadata_tag_offset..metadata_tag_offset + 4]
        .copy_from_slice(&0x000061a9u32.to_le_bytes());
    bytes[records_tag_offset..records_tag_offset + 4].copy_from_slice(&0x000061aau32.to_le_bytes());
    bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000061abu32.to_le_bytes());
    let header_words = [
        1u32, 0x7684, 5, 5, 5, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
    ];
    for (index, word) in header_words.into_iter().enumerate() {
        let offset = metadata_tag_offset + 4 + index * 4;
        bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
    }

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

    assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
    assert_eq!(probe.records_tag_offset, records_tag_offset);
    assert_eq!(probe.close_tag_offset, close_tag_offset);
    assert_eq!(probe.direct_record_stride, 0x7684);
    assert_eq!(probe.live_id_bound, 5);
    assert_eq!(probe.live_record_count, 1);
}

#[test]
fn parses_chairman_profile_tagged_collection_header_probe_from_exact_u32_tags() {
    let mut bytes = vec![0u8; 0x400];
    let metadata_tag_offset = 0x40usize;
    let records_tag_offset = 0x140usize;
    let close_tag_offset = 0x180usize;
    bytes[metadata_tag_offset..metadata_tag_offset + 4]
        .copy_from_slice(&0x00005209u32.to_le_bytes());
    bytes[records_tag_offset..records_tag_offset + 4].copy_from_slice(&0x0000520au32.to_le_bytes());
    bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
    let header_words = [
        1u32, 0xcab, 8, 6, 8, 2, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
    ];
    for (index, word) in header_words.into_iter().enumerate() {
        let offset = metadata_tag_offset + 4 + index * 4;
        bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
    }

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

    assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
    assert_eq!(probe.records_tag_offset, records_tag_offset);
    assert_eq!(probe.close_tag_offset, close_tag_offset);
    assert_eq!(probe.direct_record_stride, 0xcab);
    assert_eq!(probe.live_id_bound, 8);
    assert_eq!(probe.live_record_count, 2);
}

#[test]
fn parses_train_tagged_collection_header_probe_from_exact_u32_tags() {
    let mut bytes = vec![0u8; 0x400];
    let metadata_tag_offset = 0x40usize;
    let records_tag_offset = 0x140usize;
    let close_tag_offset = 0x180usize;
    bytes[metadata_tag_offset..metadata_tag_offset + 4]
        .copy_from_slice(&0x00005209u32.to_le_bytes());
    bytes[records_tag_offset..records_tag_offset + 4].copy_from_slice(&0x0000520au32.to_le_bytes());
    bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
    let header_words = [
        1u32, 0x1d5, 0x32, 0x14, 0x32, 0x14, 0x14, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
    ];
    for (index, word) in header_words.into_iter().enumerate() {
        let offset = metadata_tag_offset + 4 + index * 4;
        bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
    }

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

    assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
    assert_eq!(probe.records_tag_offset, records_tag_offset);
    assert_eq!(probe.close_tag_offset, close_tag_offset);
    assert_eq!(probe.direct_collection_flag, 1);
    assert_eq!(probe.direct_record_stride, 0x1d5);
    assert_eq!(probe.live_id_bound, 0x32);
    assert_eq!(probe.live_record_count, 0x14);
}

#[test]
fn parses_region_tagged_collection_header_probe_from_marker09_family() {
    let mut bytes = vec![0u8; 0x400];
    let metadata_tag_offset = 0x40usize;
    let records_tag_offset = 0x180usize;
    let close_tag_offset = 0x1c0usize;
    bytes[metadata_tag_offset..metadata_tag_offset + 4]
        .copy_from_slice(&0x00005209u32.to_le_bytes());
    bytes[records_tag_offset..records_tag_offset + 4].copy_from_slice(&0x0000520au32.to_le_bytes());
    bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
    let header_words = [
        0u32, 0x06, 0x0a, 0x14, 0x96, 0x91, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
    ];
    for (index, word) in header_words.into_iter().enumerate() {
        let offset = metadata_tag_offset + 4 + index * 4;
        bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
    }
    let marker_offset = records_tag_offset + 4 + 0x20;
    bytes[marker_offset..marker_offset + 8].copy_from_slice(b"Marker09");

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

    assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
    assert_eq!(probe.records_tag_offset, records_tag_offset);
    assert_eq!(probe.close_tag_offset, close_tag_offset);
    assert_eq!(probe.direct_collection_flag, 0);
    assert_eq!(probe.direct_record_stride, 0x06);
    assert_eq!(probe.live_id_bound, 0x96);
    assert_eq!(probe.live_record_count, 0x91);
}

#[test]
fn parses_train_collection_directory_probe_from_tagged_metadata_triplets() {
    let mut bytes = vec![0u8; 0x400];
    let metadata_tag_offset = 0x40usize;
    let records_tag_offset = 0x180usize;
    let close_tag_offset = 0x1c0usize;
    bytes[metadata_tag_offset..metadata_tag_offset + 4]
        .copy_from_slice(&0x00005209u32.to_le_bytes());
    bytes[records_tag_offset..records_tag_offset + 4].copy_from_slice(&0x0000520au32.to_le_bytes());
    bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
    let header_words = [
        1u32, 0x1d5, 0x32, 0x03, 0x32, 0x03, 0x14, 1, 0, 0, 1, 1, 0x14, 0, 0, 0,
    ];
    for (index, word) in header_words.into_iter().enumerate() {
        let offset = metadata_tag_offset + 4 + index * 4;
        bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
    }
    let triplets = [(0x2af8u32, 0u32, 2u32), (0x2ee0, 1, 3), (0x32c8, 2, 0)];
    for (index, (offset_word, prev, next)) in triplets.into_iter().enumerate() {
        let base = metadata_tag_offset
            + 4
            + (SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX + index * 3) * 4;
        bytes[base..base + 4].copy_from_slice(&offset_word.to_le_bytes());
        bytes[base + 4..base + 8].copy_from_slice(&prev.to_le_bytes());
        bytes[base + 8..base + 12].copy_from_slice(&next.to_le_bytes());
    }

    let header_probe = parse_save_train_collection_header_probe(
        &bytes,
        Some("gms"),
        Some(&SmpContainerProfile {
            profile_family: "rt3-105-save-container-v1".to_string(),
            profile_evidence: vec![],
            is_known_profile: true,
        }),
    )
    .expect("train header probe should parse");
    let directory_probe = parse_save_train_collection_directory_probe(&bytes, Some(&header_probe))
        .expect("train directory probe should parse");

    assert_eq!(directory_probe.directory_root_dword_index, 16);
    assert_eq!(directory_probe.live_record_count, 3);
    assert_eq!(directory_probe.chain_head_live_entry_id, Some(1));
    assert_eq!(directory_probe.chain_tail_live_entry_id, Some(3));
    assert_eq!(directory_probe.entries.len(), 3);
    assert_eq!(directory_probe.entries[0].live_entry_id, 1);
    assert_eq!(directory_probe.entries[0].payload_relative_offset, 0x2af8);
    assert_eq!(directory_probe.entries[0].previous_live_entry_id, 0);
    assert_eq!(directory_probe.entries[0].next_live_entry_id, 2);
    assert_eq!(directory_probe.entries[2].live_entry_id, 3);
    assert_eq!(directory_probe.entries[2].previous_live_entry_id, 2);
    assert_eq!(directory_probe.entries[2].next_live_entry_id, 0);
}

#[test]
fn parses_region_record_triplet_probe_from_marker09_records() {
    let mut bytes = vec![0u8; 0x400];
    let metadata_tag_offset = 0x40usize;
    let records_tag_offset = 0x140usize;
    let close_tag_offset = 0x260usize;
    bytes[metadata_tag_offset..metadata_tag_offset + 4]
        .copy_from_slice(&0x00005209u32.to_le_bytes());
    bytes[records_tag_offset..records_tag_offset + 4].copy_from_slice(&0x0000520au32.to_le_bytes());
    bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
    let mut cursor = records_tag_offset + 4;
    for (name, x, density, y) in [
        ("Marker09", 368.0f32, 0.0f32, 92.0f32),
        ("Marker10", 552.0f32, 1.5f32, 276.0f32),
    ] {
        bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes());
        bytes[cursor + 4] = name.len() as u8;
        bytes[cursor + 5..cursor + 5 + name.len()].copy_from_slice(name.as_bytes());
        cursor += 0x10;
        bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_POLICY_TAG.to_le_bytes());
        bytes[cursor + 4..cursor + 8].copy_from_slice(&x.to_bits().to_le_bytes());
        bytes[cursor + 8..cursor + 12].copy_from_slice(&density.to_bits().to_le_bytes());
        bytes[cursor + 12..cursor + 16].copy_from_slice(&y.to_bits().to_le_bytes());
        bytes[cursor + 28..cursor + 30].copy_from_slice(&1u16.to_le_bytes());
        cursor += 0x1e;
        bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_PROFILE_TAG.to_le_bytes());
        cursor += 0x40;
    }

    let header_probe = SmpSaveTaggedCollectionHeaderProbe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        source_kind: "save-region-tagged-header-counts".to_string(),
        semantic_family: "scenario-save-region-header-counts".to_string(),
        metadata_tag_offset,
        records_tag_offset,
        close_tag_offset,
        direct_collection_flag: 0,
        direct_collection_flag_hex: "0x00000000".to_string(),
        direct_record_stride: 0x06,
        direct_record_stride_hex: "0x00000006".to_string(),
        live_id_bound: 0x96,
        live_id_bound_hex: "0x00000096".to_string(),
        live_record_count: 2,
        live_record_count_hex: "0x00000002".to_string(),
        header_words: vec![0, 6, 0x0a, 0x14, 0x96, 0x02],
        header_hex_words: vec![],
        evidence: vec![],
    };
    let triplet_probe = parse_save_region_record_triplet_probe(&bytes, Some(&header_probe))
        .expect("region triplet probe should parse");

    assert_eq!(triplet_probe.record_count, 2);
    assert_eq!(triplet_probe.entries[0].name, "Marker09");
    assert_eq!(triplet_probe.entries[0].policy_tag_relative_offset, 0x10);
    assert_eq!(triplet_probe.entries[0].profile_tag_relative_offset, 0x2e);
    assert_eq!(triplet_probe.entries[0].policy_leading_f32_0, 368.0);
    assert_eq!(triplet_probe.entries[0].policy_leading_f32_1, 0.0);
    assert_eq!(triplet_probe.entries[0].policy_leading_f32_2, 92.0);
    assert!(
        triplet_probe.entries[0]
            .pre_name_prefix_dword_candidates
            .is_empty()
    );
    assert_eq!(
        triplet_probe.entries[0].policy_reserved_dwords,
        vec![0, 0, 0]
    );
    assert_eq!(
        triplet_probe.entries[0]
            .policy_reserved_dword_candidates
            .len(),
        3
    );
    assert_eq!(
        triplet_probe.entries[0].policy_reserved_dword_candidates[0].relative_offset_hex,
        "0x20"
    );
    assert_eq!(triplet_probe.entries[0].policy_trailing_word, 1);
    assert_eq!(triplet_probe.entries[1].name, "Marker10");
    assert_eq!(triplet_probe.entries[1].policy_leading_f32_0, 552.0);
    assert_eq!(triplet_probe.entries[1].policy_leading_f32_1, 1.5);
    assert_eq!(triplet_probe.entries[1].policy_leading_f32_2, 276.0);
    assert!(
        triplet_probe.entries[1]
            .pre_name_prefix_dword_candidates
            .is_empty()
    );
    assert_eq!(
        triplet_probe.entries[1]
            .policy_reserved_dword_candidates
            .len(),
        3
    );
}

#[test]
fn parses_region_record_triplet_prefix_dword_candidates() {
    let mut bytes = vec![0u8; 0x320];
    let metadata_tag_offset = 0x0usize;
    let records_tag_offset = 0x100usize;
    let close_tag_offset = 0x200usize;
    bytes[metadata_tag_offset..metadata_tag_offset + 4]
        .copy_from_slice(&0x00005209u32.to_le_bytes());
    bytes[records_tag_offset..records_tag_offset + 4].copy_from_slice(&0x0000520au32.to_le_bytes());
    bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
    let mut cursor = records_tag_offset + 4;
    let first_record_relative_offset = 0usize;
    bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes());
    bytes[cursor + 4] = 8;
    bytes[cursor + 5..cursor + 13].copy_from_slice(b"Marker11");
    cursor += 0x10;
    bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_POLICY_TAG.to_le_bytes());
    bytes[cursor + 4..cursor + 8].copy_from_slice(&100.0f32.to_bits().to_le_bytes());
    bytes[cursor + 8..cursor + 12].copy_from_slice(&2.0f32.to_bits().to_le_bytes());
    bytes[cursor + 12..cursor + 16].copy_from_slice(&50.0f32.to_bits().to_le_bytes());
    bytes[cursor + 28..cursor + 30].copy_from_slice(&1u16.to_le_bytes());
    cursor += 0x1e;
    bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_PROFILE_TAG.to_le_bytes());
    cursor += 0x20;
    let second_record_relative_offset = cursor - (records_tag_offset + 4);
    bytes[cursor..cursor + 4].copy_from_slice(&0x11223344u32.to_le_bytes());
    bytes[cursor + 4..cursor + 8].copy_from_slice(&0x55667788u32.to_le_bytes());
    cursor += 8;
    bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes());
    bytes[cursor + 4] = 8;
    bytes[cursor + 5..cursor + 13].copy_from_slice(b"Marker12");
    cursor += 0x10;
    bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_POLICY_TAG.to_le_bytes());
    bytes[cursor + 4..cursor + 8].copy_from_slice(&120.0f32.to_bits().to_le_bytes());
    bytes[cursor + 8..cursor + 12].copy_from_slice(&3.0f32.to_bits().to_le_bytes());
    bytes[cursor + 12..cursor + 16].copy_from_slice(&60.0f32.to_bits().to_le_bytes());
    bytes[cursor + 16..cursor + 20].copy_from_slice(&0x01020304u32.to_le_bytes());
    bytes[cursor + 20..cursor + 24].copy_from_slice(&0u32.to_le_bytes());
    bytes[cursor + 24..cursor + 28].copy_from_slice(&0x05060708u32.to_le_bytes());
    bytes[cursor + 28..cursor + 30].copy_from_slice(&1u16.to_le_bytes());
    cursor += 0x1e;
    bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_PROFILE_TAG.to_le_bytes());
    let directory_root_byte_offset = SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX * 4;
    let first_payload_relative_offset = records_tag_offset - metadata_tag_offset;
    let second_payload_relative_offset =
        first_payload_relative_offset + second_record_relative_offset;
    bytes[metadata_tag_offset + 4 + directory_root_byte_offset
        ..metadata_tag_offset + 8 + directory_root_byte_offset]
        .copy_from_slice(&(first_payload_relative_offset as u32).to_le_bytes());
    bytes[metadata_tag_offset + 16 + directory_root_byte_offset
        ..metadata_tag_offset + 20 + directory_root_byte_offset]
        .copy_from_slice(&(second_payload_relative_offset as u32).to_le_bytes());

    let header_probe = SmpSaveTaggedCollectionHeaderProbe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        source_kind: "save-region-tagged-header-counts".to_string(),
        semantic_family: "scenario-save-region-header-counts".to_string(),
        metadata_tag_offset,
        records_tag_offset,
        close_tag_offset,
        direct_collection_flag: 0,
        direct_collection_flag_hex: "0x00000000".to_string(),
        direct_record_stride: 0x06,
        direct_record_stride_hex: "0x00000006".to_string(),
        live_id_bound: 0x96,
        live_id_bound_hex: "0x00000096".to_string(),
        live_record_count: 2,
        live_record_count_hex: "0x00000002".to_string(),
        header_words: vec![0, 6, 0x0a, 0x14, 0x96, 0x02],
        header_hex_words: vec![],
        evidence: vec![],
    };
    let triplet_probe = parse_save_region_record_triplet_probe(&bytes, Some(&header_probe))
        .expect("region triplet probe should parse");

    assert_eq!(triplet_probe.entries.len(), 2);
    assert_eq!(
        triplet_probe.entries[0].record_payload_relative_offset,
        first_record_relative_offset
    );
    assert_eq!(triplet_probe.entries[0].pre_name_prefix_len, 0);
    assert_eq!(
        triplet_probe.entries[1].record_payload_relative_offset,
        second_record_relative_offset
    );
    assert_eq!(triplet_probe.entries[1].pre_name_prefix_len, 8);
    assert_eq!(
        triplet_probe.entries[1]
            .pre_name_prefix_dword_candidates
            .len(),
        2
    );
    assert_eq!(
        triplet_probe.entries[1].pre_name_prefix_dword_candidates[0].raw_u32_hex,
        "0x11223344"
    );
    assert_eq!(
        triplet_probe.entries[1].pre_name_prefix_dword_candidates[1].relative_offset_hex,
        "0x52"
    );
    assert_eq!(
        triplet_probe.entries[1].policy_reserved_dword_candidates[2].relative_offset_hex,
        "0xcc"
    );
    assert_eq!(
        triplet_probe.entries[1].policy_reserved_dword_candidates[0].raw_u32_hex,
        "0x01020304"
    );
    assert_eq!(
        triplet_probe.entries[1].policy_reserved_dword_candidates[2].raw_u32_hex,
        "0x05060708"
    );
    assert!(triplet_probe.evidence.iter().any(|line| line.contains(
        "fixed 0x55f2 policy reserved dwords are nonzero on 1 of 2 decoded region records"
    )));
}

#[test]
fn parses_region_profile_collection_probe_from_fixed_name_rows() {
    let mut payload = vec![0u8; 0x80];
    let header_words = [1u32, 0x22, 2, 2, 3, 2, 0, 1];
    for (index, word) in header_words.into_iter().enumerate() {
        let offset = index * 4;
        payload[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
    }
    let first_row_offset = 0x20usize;
    let first_name = b"House";
    payload[first_row_offset..first_row_offset + first_name.len()].copy_from_slice(first_name);
    payload[first_row_offset + 0x1e..first_row_offset + 0x22]
        .copy_from_slice(&0.2f32.to_bits().to_le_bytes());
    let second_row_offset = first_row_offset + 0x22;
    let second_name = b"Farm Corn";
    payload[second_row_offset..second_row_offset + second_name.len()].copy_from_slice(second_name);
    payload[second_row_offset + 0x1e..second_row_offset + 0x22]
        .copy_from_slice(&0.45f32.to_bits().to_le_bytes());

    let profile_probe = parse_save_region_profile_collection_probe(&payload)
        .expect("profile collection probe should parse");

    assert_eq!(profile_probe.direct_collection_flag, 1);
    assert_eq!(profile_probe.entry_stride, 0x22);
    assert_eq!(profile_probe.live_id_bound, 3);
    assert_eq!(profile_probe.live_record_count, 2);
    assert_eq!(profile_probe.entry_start_relative_offset, 0x20);
    assert_eq!(profile_probe.entries.len(), 2);
    assert_eq!(profile_probe.entries[0].name, "House");
    assert_eq!(profile_probe.entries[0].trailing_weight_f32, 0.2);
    assert_eq!(profile_probe.entries[1].name, "Farm Corn");
    assert_eq!(profile_probe.entries[1].trailing_weight_f32, 0.45);
}

#[test]
fn parses_region_queued_notice_record_probe_from_seeded_node() {
    let mut bytes = vec![0u8; 0x200];
    let node_base_offset = 0x80usize;
    bytes[node_base_offset + 4..node_base_offset + 8]
        .copy_from_slice(&SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED.to_le_bytes());
    bytes[node_base_offset + 8..node_base_offset + 12]
        .copy_from_slice(&SAVE_REGION_QUEUED_NOTICE_NODE_KIND.to_le_bytes());
    bytes[node_base_offset + 12..node_base_offset + 16].copy_from_slice(&0u32.to_le_bytes());
    bytes[node_base_offset + 16..node_base_offset + 20].copy_from_slice(&5u32.to_le_bytes());
    bytes[node_base_offset + 20..node_base_offset + 24].copy_from_slice(&1200u32.to_le_bytes());
    bytes[node_base_offset + 24..node_base_offset + 28].copy_from_slice(&(-1i32).to_le_bytes());
    bytes[node_base_offset + 28..node_base_offset + 32].copy_from_slice(&(-1i32).to_le_bytes());

    let probe = parse_save_region_queued_notice_record_probe(
        &bytes,
        Some("gms"),
        Some(&SmpContainerProfile {
            profile_family: "rt3-105-save-container-v1".to_string(),
            profile_evidence: vec![],
            is_known_profile: true,
        }),
        Some(&SmpSaveTaggedCollectionHeaderProbe {
            profile_family: "rt3-105-save-container-v1".to_string(),
            source_kind: "save-region-tagged-header-counts".to_string(),
            semantic_family: "scenario-save-region-header-counts".to_string(),
            metadata_tag_offset: 0,
            records_tag_offset: 0,
            close_tag_offset: 0,
            direct_collection_flag: 0,
            direct_collection_flag_hex: "0x00000000".to_string(),
            direct_record_stride: 0x06,
            direct_record_stride_hex: "0x00000006".to_string(),
            live_id_bound: 0x96,
            live_id_bound_hex: "0x00000096".to_string(),
            live_record_count: 0x91,
            live_record_count_hex: "0x00000091".to_string(),
            header_words: vec![],
            header_hex_words: vec![],
            evidence: vec![],
        }),
    )
    .expect("region queued notice record probe should parse");

    assert_eq!(probe.entries.len(), 1);
    assert_eq!(probe.entries[0].node_base_offset, node_base_offset);
    assert_eq!(probe.entries[0].payload_seed_dword_hex, "0x005c87a8");
    assert_eq!(probe.entries[0].kind, SAVE_REGION_QUEUED_NOTICE_NODE_KIND);
    assert_eq!(probe.entries[0].region_id, 5);
    assert_eq!(probe.entries[0].amount, 1200);
    assert_eq!(probe.entries[0].trailing_sentinel_i32_0, -1);
    assert_eq!(probe.entries[0].trailing_sentinel_i32_1, -1);
}

#[test]
fn parses_region_fixed_row_run_candidate_probe_from_seeded_rows() {
    let mut bytes = vec![0u8; 0x200];
    let count_offset = 0x20usize;
    let rows_offset = count_offset + 4;
    let metadata_tag_offset = 0x120usize;
    bytes[count_offset..count_offset + 4].copy_from_slice(&2u32.to_le_bytes());

    let first_row = rows_offset;
    bytes[first_row..first_row + 4].copy_from_slice(&11u32.to_le_bytes());
    bytes[first_row + 4..first_row + 8].copy_from_slice(&0.25f32.to_bits().to_le_bytes());
    bytes[first_row + 8..first_row + 12].copy_from_slice(&0x11223344u32.to_le_bytes());
    bytes[first_row + 0x28] = 0x07;

    let second_row = rows_offset + SAVE_REGION_FIXED_ROW_STRIDE;
    bytes[second_row..second_row + 4].copy_from_slice(&12u32.to_le_bytes());
    bytes[second_row + 4..second_row + 8].copy_from_slice(&0.5f32.to_bits().to_le_bytes());
    bytes[second_row + 8..second_row + 12].copy_from_slice(&0x55667788u32.to_le_bytes());
    bytes[second_row + 0x28] = 0x08;

    let probe = parse_save_region_fixed_row_run_candidate_probe(
        &bytes,
        Some("gms"),
        Some(&SmpContainerProfile {
            profile_family: "rt3-105-save-container-v1".to_string(),
            profile_evidence: vec![],
            is_known_profile: true,
        }),
        Some(&SmpSaveTaggedCollectionHeaderProbe {
            profile_family: "rt3-105-save-container-v1".to_string(),
            source_kind: "save-region-tagged-header-counts".to_string(),
            semantic_family: "scenario-save-region-header-counts".to_string(),
            metadata_tag_offset,
            records_tag_offset: 0,
            close_tag_offset: 0,
            direct_collection_flag: 0,
            direct_collection_flag_hex: "0x00000000".to_string(),
            direct_record_stride: 0x06,
            direct_record_stride_hex: "0x00000006".to_string(),
            live_id_bound: 0x96,
            live_id_bound_hex: "0x00000096".to_string(),
            live_record_count: 2,
            live_record_count_hex: "0x00000002".to_string(),
            header_words: vec![],
            header_hex_words: vec![],
            evidence: vec![],
        }),
    )
    .expect("region fixed-row run candidate probe should parse");

    assert_eq!(probe.target_row_count, 2);
    assert_eq!(probe.target_row_stride, SAVE_REGION_FIXED_ROW_STRIDE);
    assert_eq!(probe.candidates.len(), 1);
    assert_eq!(probe.candidates[0].count_offset, count_offset);
    assert_eq!(probe.candidates[0].rows_offset, rows_offset);
    assert_eq!(
        probe.candidates[0].best_probable_density_lane_relative_offset_hex,
        Some("0x4".to_string())
    );
    assert_eq!(
        probe.candidates[0].dword_lane_summaries[0].small_unsigned_count,
        2
    );
    assert_eq!(
        probe.candidates[0].dword_lane_summaries[1].probable_normal_f32_count,
        2
    );
    assert_eq!(probe.candidates[0].trailing_byte_nonzero_count, 2);
    assert_eq!(
        probe.candidates[0].trailing_byte_sample_values_hex,
        vec!["0x07".to_string(), "0x08".to_string()]
    );
    assert_eq!(
        probe.candidates[0].shape_signature,
        "pf32=[0x4:2,0x8:2]|small=[0x0:2]|zero=[]|trail=0/2"
    );
    assert_eq!(
        probe.candidates[0].shape_family_signature,
        "dense_pf32=[0x4,0x8]|small_nonzero=[0x0,0x4,0x8]|partial_zero=[]|trail_bucket=0/0"
    );
}

#[test]
fn compares_region_fixed_row_run_candidates_by_shape_signature() {
    let mut left = empty_analysis_report();
    left.region_fixed_row_run_candidates = Some(SmpSaveRegionFixedRowRunCandidateProbe {
        profile_family: left.profile_family.clone(),
        source_kind: "save-region-fixed-row-run-candidates".to_string(),
        semantic_family: "scenario-save-region-fixed-row-run-candidates".to_string(),
        target_row_count: 145,
        target_row_stride: 0x29,
        target_row_stride_hex: "0x29".to_string(),
        scan_start_offset: 0,
        scan_start_offset_hex: "0x0".to_string(),
        scan_end_offset: 0x100,
        scan_end_offset_hex: "0x100".to_string(),
        candidates: vec![
            SmpSaveRegionFixedRowRunCandidate {
                count_offset: 0x20,
                count_offset_hex: "0x20".to_string(),
                row_count: 145,
                row_stride: 0x29,
                row_stride_hex: "0x29".to_string(),
                rows_offset: 0x24,
                rows_offset_hex: "0x24".to_string(),
                rows_end_offset: 0x39,
                rows_end_offset_hex: "0x39".to_string(),
                distance_to_region_metadata_tag: 0xc7,
                distance_to_region_metadata_tag_hex: "0xc7".to_string(),
                dword_lane_summaries: Vec::new(),
                shape_signature: "pf32=[0x14:120]|small=[0x20:17]|zero=[0x20:11]|trail=28/63"
                    .to_string(),
                shape_family_signature:
                    "dense_pf32=[0x14]|small_nonzero=[0x20]|partial_zero=[0x20]|trail_bucket=3/7"
                        .to_string(),
                trailing_byte_zero_count: 28,
                trailing_byte_nonzero_count: 117,
                trailing_byte_distinct_value_count: 63,
                trailing_byte_sample_values_hex: Vec::new(),
                best_probable_density_lane_relative_offset_hex: Some("0x14".to_string()),
            },
            SmpSaveRegionFixedRowRunCandidate {
                count_offset: 0x40,
                count_offset_hex: "0x40".to_string(),
                row_count: 145,
                row_stride: 0x29,
                row_stride_hex: "0x29".to_string(),
                rows_offset: 0x44,
                rows_offset_hex: "0x44".to_string(),
                rows_end_offset: 0x59,
                rows_end_offset_hex: "0x59".to_string(),
                distance_to_region_metadata_tag: 0xa7,
                distance_to_region_metadata_tag_hex: "0xa7".to_string(),
                dword_lane_summaries: Vec::new(),
                shape_signature: "pf32=[0x18:107]|small=[0x10:17]|zero=[0x14:12]|trail=32/58"
                    .to_string(),
                shape_family_signature:
                    "dense_pf32=[]|small_nonzero=[0x10]|partial_zero=[0x14]|trail_bucket=4/7"
                        .to_string(),
                trailing_byte_zero_count: 32,
                trailing_byte_nonzero_count: 113,
                trailing_byte_distinct_value_count: 58,
                trailing_byte_sample_values_hex: Vec::new(),
                best_probable_density_lane_relative_offset_hex: Some("0x18".to_string()),
            },
        ],
        evidence: Vec::new(),
    });

    let mut right = empty_analysis_report();
    right.region_fixed_row_run_candidates = Some(SmpSaveRegionFixedRowRunCandidateProbe {
        profile_family: right.profile_family.clone(),
        source_kind: "save-region-fixed-row-run-candidates".to_string(),
        semantic_family: "scenario-save-region-fixed-row-run-candidates".to_string(),
        target_row_count: 145,
        target_row_stride: 0x29,
        target_row_stride_hex: "0x29".to_string(),
        scan_start_offset: 0,
        scan_start_offset_hex: "0x0".to_string(),
        scan_end_offset: 0x100,
        scan_end_offset_hex: "0x100".to_string(),
        candidates: vec![
            SmpSaveRegionFixedRowRunCandidate {
                count_offset: 0x80,
                count_offset_hex: "0x80".to_string(),
                row_count: 145,
                row_stride: 0x29,
                row_stride_hex: "0x29".to_string(),
                rows_offset: 0x84,
                rows_offset_hex: "0x84".to_string(),
                rows_end_offset: 0x99,
                rows_end_offset_hex: "0x99".to_string(),
                distance_to_region_metadata_tag: 0x67,
                distance_to_region_metadata_tag_hex: "0x67".to_string(),
                dword_lane_summaries: Vec::new(),
                shape_signature: "pf32=[0x18:107]|small=[0x10:17]|zero=[0x14:12]|trail=32/58"
                    .to_string(),
                shape_family_signature:
                    "dense_pf32=[]|small_nonzero=[0x10]|partial_zero=[0x14]|trail_bucket=4/7"
                        .to_string(),
                trailing_byte_zero_count: 32,
                trailing_byte_nonzero_count: 113,
                trailing_byte_distinct_value_count: 58,
                trailing_byte_sample_values_hex: Vec::new(),
                best_probable_density_lane_relative_offset_hex: Some("0x18".to_string()),
            },
            SmpSaveRegionFixedRowRunCandidate {
                count_offset: 0xa0,
                count_offset_hex: "0xa0".to_string(),
                row_count: 145,
                row_stride: 0x29,
                row_stride_hex: "0x29".to_string(),
                rows_offset: 0xa4,
                rows_offset_hex: "0xa4".to_string(),
                rows_end_offset: 0xb9,
                rows_end_offset_hex: "0xb9".to_string(),
                distance_to_region_metadata_tag: 0x47,
                distance_to_region_metadata_tag_hex: "0x47".to_string(),
                dword_lane_summaries: Vec::new(),
                shape_signature: "pf32=[0x24:100]|small=[0xc:16]|zero=[0xc:11]|trail=34/60"
                    .to_string(),
                shape_family_signature:
                    "dense_pf32=[]|small_nonzero=[0xc]|partial_zero=[0xc]|trail_bucket=4/7"
                        .to_string(),
                trailing_byte_zero_count: 34,
                trailing_byte_nonzero_count: 111,
                trailing_byte_distinct_value_count: 60,
                trailing_byte_sample_values_hex: Vec::new(),
                best_probable_density_lane_relative_offset_hex: Some("0x24".to_string()),
            },
        ],
        evidence: Vec::new(),
    });

    let comparison = compare_save_region_fixed_row_run_candidates(&left, &right)
        .expect("comparison should build");
    assert_eq!(comparison.shared_shape_matches.len(), 1);
    assert_eq!(
        comparison.shared_shape_matches[0].shape_signature,
        "pf32=[0x18:107]|small=[0x10:17]|zero=[0x14:12]|trail=32/58"
    );
    assert_eq!(comparison.shared_shape_matches[0].left_rank, 2);
    assert_eq!(comparison.shared_shape_matches[0].right_rank, 1);
    assert_eq!(comparison.shared_shape_family_matches.len(), 1);
    assert_eq!(
        comparison.shared_shape_family_matches[0].shape_signature,
        "dense_pf32=[]|small_nonzero=[0x10]|partial_zero=[0x14]|trail_bucket=4/7"
    );
    assert_eq!(comparison.left_only_shape_signatures.len(), 1);
    assert_eq!(comparison.right_only_shape_signatures.len(), 1);
}
