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

pub(in crate::inspect::smp) fn parse_locomotive_policy_neighborhood_probe(
    bytes: &[u8],
    file_extension_hint: Option<&str>,
    container_profile: Option<&SmpContainerProfile>,
    special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
) -> Option<SmpLocomotivePolicyNeighborhoodProbe> {
    special_conditions_probe?;
    if LOCOMOTIVE_POLICY_NEIGHBORHOOD_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" => "locomotive-policy-neighborhood",
        "gms" => "locomotive-policy-neighborhood",
        "gmx" => "locomotive-policy-neighborhood",
        _ => "locomotive-policy-neighborhood",
    }
    .to_string();

    let exact_fields = [
        (
            "selected-year bucket companion scalar",
            LOCOMOTIVE_POLICY_FIELD_NEG3_RUNTIME_OBJECT_OFFSET,
            0x0f87usize,
            4usize,
        ),
        (
            "startup-dispatch reset-owned band at +0x4cae",
            LOCOMOTIVE_POLICY_FIELD_NEG2_RUNTIME_OBJECT_OFFSET,
            0x0f93usize,
            4usize,
        ),
        (
            "startup-dispatch reset-owned band at +0x4cb2",
            LOCOMOTIVE_POLICY_FIELD_NEG1_RUNTIME_OBJECT_OFFSET,
            0x0f97usize,
            4usize,
        ),
        (
            "linked-site removal follow-on gate",
            LOCOMOTIVE_POLICY_FIELD_0_RUNTIME_OBJECT_OFFSET,
            0x0f78usize,
            1usize,
        ),
        (
            "All Steam Locos Avail.",
            LOCOMOTIVE_POLICY_FIELD_1_RUNTIME_OBJECT_OFFSET,
            0x0f7cusize,
            1usize,
        ),
        (
            "All Diesel Locos Avail.",
            LOCOMOTIVE_POLICY_FIELD_2_RUNTIME_OBJECT_OFFSET,
            0x0f7dusize,
            1usize,
        ),
        (
            "All Electric Locos Avail.",
            LOCOMOTIVE_POLICY_FIELD_3_RUNTIME_OBJECT_OFFSET,
            0x0f7eusize,
            1usize,
        ),
        (
            "station-list selected station id",
            LOCOMOTIVE_POLICY_FIELD_4_RUNTIME_OBJECT_OFFSET,
            0x0f9fusize,
            4usize,
        ),
        (
            "cached available-locomotive rating",
            LOCOMOTIVE_POLICY_FIELD_5_RUNTIME_OBJECT_OFFSET,
            0x0fa3usize,
            4usize,
        ),
    ];

    let grounded_field_observations = exact_fields
        .iter()
        .map(
            |(field_name, runtime_object_offset, file_offset, field_width_bytes)| {
                let raw = &bytes[*file_offset..*file_offset + *field_width_bytes];
                let raw_hex = hex_encode(raw);
                let (value_u8, value_u8_hex, value_u32, value_u32_hex, probable_f32_le) =
                    if *field_width_bytes == 1 {
                        let value = raw[0];
                        (
                            Some(value),
                            Some(format!("0x{value:02x}")),
                            None,
                            None,
                            None,
                        )
                    } else {
                        let value = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]);
                        (
                            None,
                            None,
                            Some(value),
                            Some(format!("0x{value:08x}")),
                            probable_normal_f32_string(value),
                        )
                    };
                SmpLocomotivePolicyFieldObservation {
                    field_name: (*field_name).to_string(),
                    runtime_object_offset: *runtime_object_offset,
                    runtime_object_offset_hex: format!("0x{runtime_object_offset:04x}"),
                    file_offset: *file_offset,
                    file_offset_hex: format!("0x{file_offset:04x}"),
                    field_width_bytes: *field_width_bytes,
                    field_width_bytes_hex: format!("0x{field_width_bytes:x}"),
                    raw_hex,
                    value_u8,
                    value_u8_hex,
                    value_u32,
                    value_u32_hex,
                    probable_f32_le,
                }
            },
        )
        .collect::<Vec<_>>();

    let three_byte_early_float_candidates = exact_fields
        .iter()
        .filter(|(_, _, _, width)| *width == 4usize)
        .filter_map(|(field_name, runtime_object_offset, file_offset, _)| {
            let candidate_offset = file_offset.saturating_sub(3);
            let value = read_u32_at(bytes, candidate_offset)?;
            let probable_f32_le = probable_normal_f32_string(value)?;
            Some(SmpLocomotivePolicyFloatAlignmentCandidate {
                grounded_field_name: (*field_name).to_string(),
                grounded_field_runtime_object_offset: *runtime_object_offset,
                grounded_field_runtime_object_offset_hex: format!("0x{runtime_object_offset:04x}"),
                grounded_field_file_offset: *file_offset,
                grounded_field_file_offset_hex: format!("0x{file_offset:04x}"),
                candidate_offset,
                candidate_offset_hex: format!("0x{candidate_offset:04x}"),
                candidate_value: value,
                candidate_value_hex: format!("0x{value:08x}"),
                probable_f32_le,
            })
        })
        .collect::<Vec<_>>();

    let mut evidence = vec![
        format!(
            "locomotive-policy neighborhood spans file offsets 0x{LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET:04x}..0x{LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET:04x}"
        ),
        "this neighborhood covers the selected-year bucket companion scalar, two startup-reset-owned bands, the linked-site removal gate, the three locomotive-availability policy bytes, the station-list selected-station mirror, and the cached available-locomotive rating".to_string(),
        "the exact byte policy lanes live at 0x0f78 and 0x0f7c..0x0f7e, while the earlier grounded dword starts map to 0x0f87, 0x0f93, and 0x0f97 and the later grounded dword starts map to 0x0f9f and 0x0fa3".to_string(),
    ];
    if three_byte_early_float_candidates.is_empty() {
        evidence.push(
            "no three-byte-early little-endian float-looking starts were observed ahead of the grounded dword fields in this file".to_string(),
        );
    } else {
        evidence.push(format!(
            "observed {} float-looking 4-byte starts exactly three bytes before grounded dword fields in this file",
            three_byte_early_float_candidates.len()
        ));
    }

    Some(SmpLocomotivePolicyNeighborhoodProbe {
        profile_family,
        source_kind,
        window_offset: LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET,
        window_end_offset: LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET,
        window_len: LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET
            - LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET,
        window_len_hex: format!(
            "0x{:x}",
            LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET - LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET
        ),
        grounded_field_observations,
        three_byte_early_float_candidates,
        evidence,
    })
}
