pub(in crate::inspect::smp) fn read_u32_window(
    bytes: &[u8],
    offset: usize,
    count: usize,
) -> Vec<u32> {
    let mut words = Vec::new();
    let end = bytes.len().min(offset + count * 4);
    for chunk in bytes[offset..end].chunks_exact(4) {
        words.push(u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
    }
    words
}

pub(in crate::inspect::smp) fn read_u16_window(
    bytes: &[u8],
    offset: usize,
    count: usize,
) -> Vec<u16> {
    let mut words = Vec::new();
    let end = bytes.len().min(offset + count * 2);
    for chunk in bytes[offset..end].chunks_exact(2) {
        words.push(u16::from_le_bytes([chunk[0], chunk[1]]));
    }
    words
}

pub(in crate::inspect::smp) fn read_u8_at(bytes: &[u8], offset: usize) -> Option<u8> {
    bytes.get(offset).copied()
}

pub(in crate::inspect::smp) fn read_u16_at(bytes: &[u8], offset: usize) -> Option<u16> {
    let chunk = bytes.get(offset..offset + 2)?;
    Some(u16::from_le_bytes([chunk[0], chunk[1]]))
}

pub(in crate::inspect::smp) fn read_u32_at(bytes: &[u8], offset: usize) -> Option<u32> {
    let chunk = bytes.get(offset..offset + 4)?;
    Some(u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
}

pub(in crate::inspect::smp) fn read_i32_at(bytes: &[u8], offset: usize) -> Option<i32> {
    let chunk = bytes.get(offset..offset + 4)?;
    Some(i32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
}

pub(in crate::inspect::smp) fn read_i64_at(bytes: &[u8], offset: usize) -> Option<i64> {
    let chunk = bytes.get(offset..offset + 8)?;
    Some(i64::from_le_bytes([
        chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
    ]))
}

pub(in crate::inspect::smp) fn read_u64_at(bytes: &[u8], offset: usize) -> Option<u64> {
    let chunk = bytes.get(offset..offset + 8)?;
    Some(u64::from_le_bytes([
        chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
    ]))
}

pub(in crate::inspect::smp) fn read_f32_at(bytes: &[u8], offset: usize) -> Option<f32> {
    let chunk = bytes.get(offset..offset + 4)?;
    Some(f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
}

pub(in crate::inspect::smp) fn read_f64_at(bytes: &[u8], offset: usize) -> Option<f64> {
    let chunk = bytes.get(offset..offset + 8)?;
    Some(f64::from_le_bytes([
        chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
    ]))
}

pub(in crate::inspect::smp) fn read_ascii_c_string_at(
    bytes: &[u8],
    offset: usize,
    max_len: usize,
) -> Option<String> {
    let chunk = bytes.get(offset..offset + max_len)?;
    let nul_index = chunk.iter().position(|byte| *byte == 0).unwrap_or(max_len);
    let text = std::str::from_utf8(&chunk[..nul_index])
        .ok()?
        .trim()
        .to_string();
    Some(text)
}

pub(in crate::inspect::smp) fn parse_nonzero_u32(
    bytes: &[u8],
    offset: usize,
) -> Option<Option<u32>> {
    read_u32_at(bytes, offset).map(|value| (value != 0).then_some(value))
}

pub(in crate::inspect::smp) fn round_f64_to_i64(value: f64) -> Option<i64> {
    if !value.is_finite() {
        return None;
    }
    let rounded = value.round();
    if rounded < i64::MIN as f64 || rounded > i64::MAX as f64 {
        return None;
    }
    Some(rounded as i64)
}
