use crate::inspect::smp::common::*;

pub(in crate::inspect::smp) fn parse_save_len_prefixed_ascii_name(bytes: &[u8]) -> Option<String> {
    let len = *bytes.first()? as usize;
    let text_bytes = bytes.get(1..1 + len)?;
    let text = std::str::from_utf8(text_bytes).ok()?.trim_end_matches('\0');
    Some(text.to_string())
}

pub(in crate::inspect::smp) fn parse_save_varlen_ascii_name_at(
    bytes: &[u8],
    offset: usize,
) -> Option<(String, usize)> {
    let first = *bytes.get(offset)?;
    if first == 0 {
        return None;
    }
    let (len, header_len) = if first <= 0x7f {
        (first as usize, 1usize)
    } else {
        let second = *bytes.get(offset + 1)? as usize;
        ((((first as usize) & 0x7f) << 8) | second, 2usize)
    };
    let start = offset + header_len;
    let end = start.checked_add(len)?;
    let text = std::str::from_utf8(bytes.get(start..end)?)
        .ok()?
        .trim_end_matches('\0')
        .to_string();
    Some((text, end))
}

pub(in crate::inspect::smp) fn parse_save_len_prefixed_ascii_name_pair(
    bytes: &[u8],
) -> Option<(String, String)> {
    let (first, second, _) = parse_save_len_prefixed_ascii_name_triplet(bytes)?;
    Some((first, second))
}

pub(in crate::inspect::smp) fn parse_save_len_prefixed_ascii_name_triplet_and_consumed_len(
    bytes: &[u8],
) -> Option<((String, String, Option<String>), usize)> {
    let (first, first_end) = parse_save_varlen_ascii_name_at(bytes, 0)?;
    let mut second_len_offset = first_end;
    while matches!(bytes.get(second_len_offset), Some(0)) {
        second_len_offset += 1;
    }
    let (second, second_end) = parse_save_varlen_ascii_name_at(bytes, second_len_offset)?;
    if first.is_empty() || second.is_empty() {
        return None;
    }
    let mut third_len_offset = second_end;
    while matches!(bytes.get(third_len_offset), Some(0)) {
        third_len_offset += 1;
    }
    let (third, consumed_len) = parse_save_varlen_ascii_name_at(bytes, third_len_offset)
        .map(|(text, end)| {
            let third = (!text.is_empty()).then_some(text);
            (third, end)
        })
        .unwrap_or((None, second_end));
    Some(((first, second, third), consumed_len))
}

pub(in crate::inspect::smp) fn parse_save_len_prefixed_ascii_name_triplet(
    bytes: &[u8],
) -> Option<(String, String, Option<String>)> {
    let ((first, second, third), _) =
        parse_save_len_prefixed_ascii_name_triplet_and_consumed_len(bytes)?;
    Some((first, second, third))
}

pub(in crate::inspect::smp) fn parse_save_fixed_ascii_name(bytes: &[u8]) -> Option<String> {
    let nul_index = bytes
        .iter()
        .position(|byte| *byte == 0)
        .unwrap_or(bytes.len());
    let text = std::str::from_utf8(bytes.get(..nul_index)?).ok()?;
    if text.is_empty()
        || !text
            .bytes()
            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b' ' | b'-' | b'&' | b'/'))
    {
        return None;
    }
    Some(text.to_string())
}

pub(in crate::inspect::smp) fn parse_save_region_profile_collection_probe(
    profile_payload: &[u8],
) -> Option<SmpSaveRegionProfileCollectionProbe> {
    let direct_collection_flag = read_u32_at(profile_payload, 0)?;
    let entry_stride = read_u32_at(profile_payload, 4)?;
    let header_word_2 = read_u32_at(profile_payload, 8)?;
    let header_word_3 = read_u32_at(profile_payload, 12)?;
    let live_id_bound = read_u32_at(profile_payload, 16)?;
    let live_record_count = read_u32_at(profile_payload, 20)?;
    let header_word_6 = read_u32_at(profile_payload, 24)?;
    let header_word_7 = read_u32_at(profile_payload, 28)?;
    if !(direct_collection_flag == 1
        && entry_stride == 0x22
        && header_word_2 == 2
        && header_word_3 == 2
        && live_record_count > 0
        && live_record_count < live_id_bound
        && header_word_6 == 0
        && header_word_7 == 1)
    {
        return None;
    }
    let entry_stride = entry_stride as usize;
    let live_record_count_usize = live_record_count as usize;
    let rows_byte_len = live_record_count_usize.checked_mul(entry_stride)?;
    let mut matched_probe = None;
    for entry_start_relative_offset in 0x20..=0x80 {
        if entry_start_relative_offset + rows_byte_len > profile_payload.len() {
            break;
        }
        let mut entries = Vec::with_capacity(live_record_count_usize);
        let mut matched = true;
        for entry_index in 0..live_record_count_usize {
            let row_relative_offset = entry_start_relative_offset + entry_index * entry_stride;
            let row =
                profile_payload.get(row_relative_offset..row_relative_offset + entry_stride)?;
            let name = match parse_save_fixed_ascii_name(row.get(..12)?) {
                Some(name) => name,
                None => {
                    matched = false;
                    break;
                }
            };
            let trailing_weight_f32 = f32::from_bits(read_u32_at(row, entry_stride - 4)?);
            if !trailing_weight_f32.is_finite() || trailing_weight_f32 < 0.0 {
                matched = false;
                break;
            }
            entries.push(SmpSaveRegionProfileEntryProbe {
                entry_index,
                row_relative_offset,
                name,
                trailing_weight_f32,
            });
        }
        if matched {
            matched_probe = Some(SmpSaveRegionProfileCollectionProbe {
                direct_collection_flag,
                entry_stride: entry_stride as u32,
                live_id_bound,
                live_record_count,
                entry_start_relative_offset,
                trailing_padding_len: profile_payload.len()
                    - (entry_start_relative_offset + rows_byte_len),
                entries,
            });
            break;
        }
    }
    matched_probe
}
