//! `LSTA` — a display list: a header followed by N inline elements concatenated
//! back-to-back, each either a [`T8aD`](crate::t8ad) sprite or a `PRMD`
//! primitive (a flat coloured quad — the same primitive the UI bundles use to
//! dim a scene).
//!
//! The `count` at `@0x04` is **exact, and counts both kinds**: across all 64
//! lists on the disc, `count == T8aD frames + PRMD primitives` with no
//! exceptions (measured 2026-08-11). An earlier note here said "a few entries
//! disagree with the actual frame count" — they do not; that comparison was
//! counting sprites against a total that includes primitives.
//!
//! [`parse`] walks by magic and returns the **sprites**, deliberately skipping
//! `PRMD` entries, so its result length is `count` only for lists that hold no
//! primitives. Six lists do (in `GP_DEBRIEFING_PILOTLOG`, `GP_MISSION_SELECT`),
//! each with exactly one.

use crate::t8ad::{self, T8adImage, T8AD_MAGIC};

/// Magic at the start of an LSTA sprite list.
pub const LSTA_MAGIC: [u8; 4] = *b"LSTA";

/// Whether `bytes` is an LSTA list.
pub fn is_lsta(bytes: &[u8]) -> bool {
    bytes.len() >= 4 && bytes[0..4] == LSTA_MAGIC
}

/// Decode the inline T8aD sprites, skipping `PRMD` primitives. Returns `None`
/// only for non-LSTA input. Every one of the 1 281 sprite frames on the disc
/// decodes (measured 2026-08-11, after the T8aD rectangle-list fix).
pub fn parse(bytes: &[u8]) -> Option<Vec<T8adImage>> {
    if !is_lsta(bytes) {
        return None;
    }

    // Offsets of each inline T8aD frame (search past the 4-byte magic).
    let mut offs = Vec::new();
    let mut i = 4;
    while i + 4 <= bytes.len() {
        if bytes[i..i + 4] == T8AD_MAGIC {
            offs.push(i);
            i += 4;
        } else {
            i += 1;
        }
    }

    let mut frames = Vec::with_capacity(offs.len());
    for (idx, &off) in offs.iter().enumerate() {
        let next = offs.get(idx + 1).copied().unwrap_or(bytes.len());
        if let Some(img) = t8ad::parse(&bytes[off..next]) {
            frames.push(img);
        }
    }
    Some(frames)
}

#[cfg(test)]
mod tests {
    use super::*;

    /// A faithful one-rectangle T8aD frame: base header, a 1-entry offset table,
    /// then the rectangle header (dst 0,0, size w×h) and its pixels. (Before
    /// 2026-08-11 this fixture wrote no offset-table entry at all and the decoder
    /// read "pixels" from inside the header — the test only ever checked the
    /// dimensions, so it passed anyway.)
    fn t8ad_frame(w: u32, h: u32) -> Vec<u8> {
        let mut b = vec![0u8; 0x2c];
        b[0..4].copy_from_slice(&T8AD_MAGIC);
        b[0x14..0x18].copy_from_slice(&w.to_be_bytes());
        b[0x18..0x1c].copy_from_slice(&h.to_be_bytes());
        b[0x1c..0x20].copy_from_slice(&1u32.to_be_bytes());
        b.extend_from_slice(&0x30u32.to_be_bytes()); // offset table → rect at 0x30
        for v in [0u32, 0, w, h] {
            b.extend_from_slice(&v.to_be_bytes()); // dst X, dst Y, width, height
        }
        b.extend_from_slice(&vec![0x80u8; (w * h * 4) as usize]);
        b
    }

    #[test]
    fn walks_inline_frames() {
        let mut b = LSTA_MAGIC.to_vec();
        b.extend_from_slice(&2u32.to_be_bytes());
        b.extend_from_slice(&[0u8; 15]); // header remainder
        b.extend_from_slice(&t8ad_frame(4, 4));
        b.extend_from_slice(&t8ad_frame(2, 3));
        let frames = parse(&b).unwrap();
        assert_eq!(frames.len(), 2);
        assert_eq!((frames[0].width, frames[0].height), (4, 4));
        assert_eq!((frames[1].width, frames[1].height), (2, 3));
    }

    #[test]
    fn rejects_non_lsta() {
        assert!(parse(b"T8aD....").is_none());
    }
}
