use std::io::Cursor;

use binrw::BinRead;
use tracing::{info, info_span};

use crate::error::{Error, Result};
use crate::scd::format::ScdFormat;
use crate::scd::header::{
    BINARY_HEADER_SIZE, BinaryHeader, ENDIAN_TYPE_LITTLE, SCD_HEADER_SIZE, SCD_MAGIC_SUBTYPE,
    SCD_MAGIC_TYPE, ScdHeader, WireBinaryHeader, WireScdHeader,
};
use crate::scd::track::{
    ADPCM_WAVE_FORMAT_SIZE, ATRAC9_HEADER_SIZE, AUDIO_BASIC_DESC_SIZE, AudioBasicDesc, ExtraData,
    OGG_VORBIS_SEEK_TABLE_HEADER_SIZE, OggVorbisSeekTableHeader, PS3_MP3_SEEK_TABLE_HEADER_SIZE,
    Ps3Mp3SeekTableHeader, SUB_INFO_MARKER_CHUNK_SIZE, SubInfoMarkerChunk, Track,
    WireAdpcmWaveFormat, WireAtrac9Header, WireAudioBasicDesc, WireOggVorbisSeekTableHeader,
    WirePs3Mp3SeekTableHeader, WireSubInfoMarkerChunk,
};
use crate::tracing_schema::span_names;

/// Zero-copy view over a parsed SCD sound-container file.
///
/// Each SCD contains zero or more audio tracks.  All tracks are decoded eagerly on
/// [`Scd::parse`]; the raw bytes remain as slices into the original input.
#[allow(clippy::struct_field_names)]
#[derive(Debug, Clone)]
pub struct Scd<'a> {
    bytes: &'a [u8],
    binary_header: BinaryHeader,
    scd_header: ScdHeader,
    sound_offsets: Vec<u32>,
    track_offsets: Vec<u32>,
    audio_offsets: Vec<u32>,
    tracks: Vec<Track<'a>>,
}

impl<'a> Scd<'a> {
    /// Parses an SCD file from `bytes`, returning a zero-copy view.
    ///
    /// Validates the `"SEDB"` / `"SSCF"` magic bytes and rejects big-endian files.
    /// All offset tables are read and all audio tracks are decoded eagerly.
    ///
    /// Emits a `scd.parse` tracing event on success.
    pub fn parse(bytes: &'a [u8]) -> Result<Self> {
        let span = info_span!(span_names::SCD_PARSE, bytes = bytes.len());
        let _enter = span.enter();
        let start = std::time::Instant::now();

        if bytes.len() < BINARY_HEADER_SIZE {
            return Err(Error::InvalidScdLength {
                found: bytes.len(),
                minimum: BINARY_HEADER_SIZE,
            });
        }
        let mut cursor = Cursor::new(bytes);
        let wire_bin = WireBinaryHeader::read(&mut cursor)
            .map_err(|e| Error::decode("scd.binary_header", &e))?;

        if wire_bin.r#type != SCD_MAGIC_TYPE || wire_bin.sub_type != SCD_MAGIC_SUBTYPE {
            return Err(Error::InvalidScdMagic {
                found_type: wire_bin.r#type,
                found_sub_type: wire_bin.sub_type,
            });
        }
        if wire_bin.endian_type != ENDIAN_TYPE_LITTLE {
            return Err(Error::UnsupportedScdEndianness {
                endian_type: wire_bin.endian_type,
            });
        }
        let binary_header: BinaryHeader = wire_bin.into();

        let scd_header_pos = usize::from(binary_header.scd_header_offset);
        if scd_header_pos < BINARY_HEADER_SIZE {
            return Err(Error::InvalidScdTrack {
                reason: "scd_header_offset overlaps the binary header",
            });
        }
        let scd_header_end =
            scd_header_pos
                .checked_add(SCD_HEADER_SIZE)
                .ok_or(Error::InvalidScdTrack {
                    reason: "scd_header_offset + SCD_HEADER_SIZE overflows usize",
                })?;
        if scd_header_end > bytes.len() {
            return Err(Error::InvalidScdLength {
                found: bytes.len(),
                minimum: scd_header_end,
            });
        }
        cursor.set_position(scd_header_pos as u64);
        let wire_scd =
            WireScdHeader::read(&mut cursor).map_err(|e| Error::decode("scd.header", &e))?;
        let scd_header: ScdHeader = wire_scd.into();

        let (sound_offsets, track_offsets, audio_offsets, tracks) =
            read_offset_tables_and_tracks(bytes, &scd_header, scd_header_end)?;

        let elapsed_ms = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX);
        info!(
            target: "scd",
            bytes = bytes.len(),
            version = binary_header.version,
            sound_count = scd_header.sound_count,
            track_count = scd_header.track_count,
            audio_count = scd_header.audio_count,
            elapsed_ms,
            "scd.parse",
        );

        Ok(Self {
            bytes,
            binary_header,
            scd_header,
            sound_offsets,
            track_offsets,
            audio_offsets,
            tracks,
        })
    }

    /// Returns the entire raw byte slice passed to [`Scd::parse`].
    #[must_use]
    pub fn raw(&self) -> &'a [u8] {
        self.bytes
    }

    /// Returns the [`BinaryHeader`] at offset 0 of the file.
    #[must_use]
    pub fn binary_header(&self) -> &BinaryHeader {
        &self.binary_header
    }

    /// Returns the [`ScdHeader`] located at [`BinaryHeader::scd_header_offset`].
    #[must_use]
    pub fn header(&self) -> &ScdHeader {
        &self.scd_header
    }

    /// Returns the sound offset table.
    #[must_use]
    pub fn sound_offsets(&self) -> &[u32] {
        &self.sound_offsets
    }

    /// Returns the track offset table.
    #[must_use]
    pub fn track_offsets(&self) -> &[u32] {
        &self.track_offsets
    }

    /// Returns the audio offset table.
    ///
    /// Each entry is the byte offset of a track's [`AudioBasicDesc`](crate::scd::track::AudioBasicDesc)
    /// and corresponds to the [`Track`] at the same index in [`Scd::tracks`].
    #[must_use]
    pub fn audio_offsets(&self) -> &[u32] {
        &self.audio_offsets
    }

    /// Returns all parsed audio tracks in file order.
    #[must_use]
    pub fn tracks(&self) -> &[Track<'a>] {
        &self.tracks
    }
}

#[allow(clippy::type_complexity)]
fn read_offset_tables_and_tracks<'a>(
    bytes: &'a [u8],
    scd_header: &ScdHeader,
    scd_header_end: usize,
) -> Result<(Vec<u32>, Vec<u32>, Vec<u32>, Vec<Track<'a>>)> {
    let sound_count = usize::from(scd_header.sound_count);
    let track_count = usize::from(scd_header.track_count);
    let audio_count = usize::from(scd_header.audio_count);

    let sound_offsets = read_offset_table(bytes, scd_header_end, sound_count)?;

    let track_offsets = if track_count > 0 {
        read_offset_table(
            bytes,
            usize::try_from(scd_header.track_offset).map_err(|_| Error::InvalidScdTrack {
                reason: "track_offset does not fit usize",
            })?,
            track_count,
        )?
    } else {
        Vec::new()
    };

    let audio_offsets = if audio_count > 0 {
        read_offset_table(
            bytes,
            usize::try_from(scd_header.audio_offset).map_err(|_| Error::InvalidScdTrack {
                reason: "audio_offset does not fit usize",
            })?,
            audio_count,
        )?
    } else {
        Vec::new()
    };

    let mut tracks: Vec<Track<'a>> = Vec::with_capacity(audio_offsets.len());
    for &raw_offset in &audio_offsets {
        tracks.push(parse_track(bytes, raw_offset)?);
    }

    Ok((sound_offsets, track_offsets, audio_offsets, tracks))
}

fn read_offset_table(bytes: &[u8], start_pos: usize, count: usize) -> Result<Vec<u32>> {
    if count == 0 {
        return Ok(Vec::new());
    }
    let byte_size = count.checked_mul(4).ok_or(Error::InvalidScdTrack {
        reason: "offset table size overflows usize",
    })?;
    let end_pos = start_pos
        .checked_add(byte_size)
        .ok_or(Error::InvalidScdTrack {
            reason: "offset table extends past usize::MAX",
        })?;
    if end_pos > bytes.len() {
        return Err(Error::InvalidScdLength {
            found: bytes.len(),
            minimum: end_pos,
        });
    }
    let mut out = Vec::with_capacity(count);
    let mut cursor = Cursor::new(&bytes[start_pos..end_pos]);
    for _ in 0..count {
        let v = u32::read_le(&mut cursor).map_err(|e| Error::decode("scd.offset_table", &e))?;
        out.push(v);
    }
    Ok(out)
}

fn parse_track(bytes: &[u8], raw_offset: u32) -> Result<Track<'_>> {
    let desc_pos = usize::try_from(raw_offset).map_err(|_| Error::InvalidScdTrack {
        reason: "audio_offset entry does not fit usize",
    })?;
    let desc_end = desc_pos
        .checked_add(AUDIO_BASIC_DESC_SIZE)
        .ok_or(Error::InvalidScdTrack {
            reason: "audio_offset + AUDIO_BASIC_DESC_SIZE overflows usize",
        })?;
    if desc_end > bytes.len() {
        return Err(Error::InvalidScdLength {
            found: bytes.len(),
            minimum: desc_end,
        });
    }

    let mut cursor = Cursor::new(&bytes[desc_pos..desc_end]);
    let wire = WireAudioBasicDesc::read(&mut cursor)
        .map_err(|e| Error::decode("scd.audio_basic_desc", &e))?;
    let desc: AudioBasicDesc = wire.into();

    let sub_info_start = desc_end;
    let sub_info_size =
        usize::try_from(desc.sub_info_size).map_err(|_| Error::InvalidScdTrack {
            reason: "sub_info_size does not fit usize",
        })?;
    let sub_info_end = sub_info_start
        .checked_add(sub_info_size)
        .ok_or(Error::InvalidScdTrack {
            reason: "sub_info_start + sub_info_size overflows usize",
        })?;
    if sub_info_end > bytes.len() {
        return Err(Error::InvalidScdLength {
            found: bytes.len(),
            minimum: sub_info_end,
        });
    }
    let sub_info_bytes = &bytes[sub_info_start..sub_info_end];

    let payload_start = sub_info_end;
    let payload_size = usize::try_from(desc.payload_size).map_err(|_| Error::InvalidScdTrack {
        reason: "payload_size does not fit usize",
    })?;
    let payload_end = payload_start
        .checked_add(payload_size)
        .ok_or(Error::InvalidScdTrack {
            reason: "payload_start + payload_size overflows usize",
        })?;
    if payload_end > bytes.len() {
        return Err(Error::InvalidScdLength {
            found: bytes.len(),
            minimum: payload_end,
        });
    }

    let (marker_chunk, marker_positions, after_marker_offset) = parse_marker_chunk(
        bytes,
        sub_info_start,
        sub_info_end,
        desc.flags.has_marker_chunk(),
    )?;

    let extra_data = parse_extra_data(
        bytes,
        after_marker_offset,
        sub_info_end,
        desc.format,
        desc.sub_info_size,
    )?;

    let raw_bytes = if let ExtraData::OggVorbis { header, .. } = extra_data {
        let ogg_header_size =
            usize::try_from(header.ogg_header_size).map_err(|_| Error::InvalidScdTrack {
                reason: "ogg vorbis ogg_header_size does not fit usize",
            })?;
        let ogg_total =
            ogg_header_size
                .checked_add(payload_size)
                .ok_or(Error::InvalidScdTrack {
                    reason: "ogg vorbis payload + header overruns file",
                })?;
        let ogg_end = payload_start
            .checked_add(ogg_total)
            .ok_or(Error::InvalidScdTrack {
                reason: "ogg vorbis payload + header overruns file",
            })?;
        if ogg_end > bytes.len() {
            return Err(Error::InvalidScdTrack {
                reason: "ogg vorbis payload + header overruns file",
            });
        }
        &bytes[payload_start..ogg_end]
    } else {
        &bytes[payload_start..payload_end]
    };

    Ok(Track::new(
        desc,
        marker_chunk,
        marker_positions,
        extra_data,
        sub_info_bytes,
        raw_bytes,
        raw_offset,
    ))
}

fn parse_marker_chunk(
    bytes: &[u8],
    sub_info_start: usize,
    sub_info_end: usize,
    has_marker_chunk: bool,
) -> Result<(Option<SubInfoMarkerChunk>, &[u8], usize)> {
    if !has_marker_chunk {
        return Ok((None, &bytes[sub_info_start..sub_info_start], sub_info_start));
    }
    let header_end = sub_info_start
        .checked_add(SUB_INFO_MARKER_CHUNK_SIZE)
        .ok_or(Error::InvalidScdTrack {
            reason: "marker_chunk header extends past usize::MAX",
        })?;
    if header_end > sub_info_end {
        return Err(Error::InvalidScdTrack {
            reason: "marker_chunk header extends past sub_info region",
        });
    }
    let mut cursor = Cursor::new(&bytes[sub_info_start..header_end]);
    let wire = WireSubInfoMarkerChunk::read(&mut cursor)
        .map_err(|e| Error::decode("scd.marker_chunk", &e))?;
    let chunk: SubInfoMarkerChunk = wire.into();
    let chunk_size = usize::try_from(chunk.chunk_size).map_err(|_| Error::InvalidScdTrack {
        reason: "marker_chunk.chunk_size does not fit usize",
    })?;
    if chunk_size < SUB_INFO_MARKER_CHUNK_SIZE {
        return Err(Error::InvalidScdTrack {
            reason: "marker_chunk.chunk_size smaller than header",
        });
    }
    let chunk_end = sub_info_start
        .checked_add(chunk_size)
        .ok_or(Error::InvalidScdTrack {
            reason: "marker_chunk extends past usize::MAX",
        })?;
    if chunk_end > sub_info_end {
        return Err(Error::InvalidScdTrack {
            reason: "marker_chunk extends past sub_info region",
        });
    }
    let marker_count = usize::try_from(chunk.marker_count).map_err(|_| Error::InvalidScdTrack {
        reason: "marker_chunk.marker_count is negative or does not fit usize",
    })?;
    let marker_bytes_len = marker_count.checked_mul(4).ok_or(Error::InvalidScdTrack {
        reason: "marker_count * 4 overflows usize",
    })?;
    let marker_bytes_end =
        header_end
            .checked_add(marker_bytes_len)
            .ok_or(Error::InvalidScdTrack {
                reason: "marker positions extend past usize::MAX",
            })?;
    if marker_bytes_end > chunk_end {
        return Err(Error::InvalidScdTrack {
            reason: "marker positions extend past marker chunk",
        });
    }
    let marker_positions = &bytes[header_end..marker_bytes_end];
    Ok((Some(chunk), marker_positions, chunk_end))
}

fn parse_extra_data(
    bytes: &[u8],
    start_pos: usize,
    sub_info_end: usize,
    format: ScdFormat,
    sub_info_size: u32,
) -> Result<ExtraData<'_>> {
    if sub_info_size == 0 {
        return Ok(ExtraData::None);
    }
    if start_pos > sub_info_end {
        return Err(Error::InvalidScdTrack {
            reason: "sub_info post-marker cursor extends past sub_info region",
        });
    }
    let remaining = sub_info_end - start_pos;
    match format {
        ScdFormat::Empty => Ok(ExtraData::None),
        ScdFormat::MsAdpcm => parse_ms_adpcm_extra(bytes, start_pos, remaining),
        ScdFormat::OggVorbis => parse_ogg_vorbis_extra(bytes, start_pos, sub_info_end, remaining),
        ScdFormat::Atrac9 => parse_atrac9_extra(bytes, start_pos, remaining),
        ScdFormat::Mp3 => parse_mp3_extra(bytes, start_pos, sub_info_end, remaining),
        ScdFormat::Unknown(_) => Ok(ExtraData::Opaque(&bytes[start_pos..sub_info_end])),
    }
}

fn parse_ms_adpcm_extra(bytes: &[u8], start_pos: usize, remaining: usize) -> Result<ExtraData<'_>> {
    if remaining < ADPCM_WAVE_FORMAT_SIZE {
        return Err(Error::InvalidScdTrack {
            reason: "MS-ADPCM header truncated by sub_info region",
        });
    }
    let end = start_pos + ADPCM_WAVE_FORMAT_SIZE;
    let mut cursor = Cursor::new(&bytes[start_pos..end]);
    let wire = WireAdpcmWaveFormat::read(&mut cursor)
        .map_err(|e| Error::decode("scd.adpcm_wave_format", &e))?;
    Ok(ExtraData::MsAdpcm(wire.into()))
}

fn parse_ogg_vorbis_extra(
    bytes: &[u8],
    start_pos: usize,
    sub_info_end: usize,
    remaining: usize,
) -> Result<ExtraData<'_>> {
    if remaining < OGG_VORBIS_SEEK_TABLE_HEADER_SIZE {
        return Err(Error::InvalidScdTrack {
            reason: "OGG Vorbis seek-table header truncated by sub_info region",
        });
    }
    let header_end = start_pos + OGG_VORBIS_SEEK_TABLE_HEADER_SIZE;
    let mut cursor = Cursor::new(&bytes[start_pos..header_end]);
    let wire = WireOggVorbisSeekTableHeader::read(&mut cursor)
        .map_err(|e| Error::decode("scd.ogg_seek_table_header", &e))?;
    let header: OggVorbisSeekTableHeader = wire.into();
    let seek_table_len =
        usize::try_from(header.seek_table_size).map_err(|_| Error::InvalidScdTrack {
            reason: "OGG Vorbis seek_table_size does not fit usize",
        })?;
    let seek_table_end = header_end
        .checked_add(seek_table_len)
        .ok_or(Error::InvalidScdTrack {
            reason: "OGG Vorbis seek table extends past usize::MAX",
        })?;
    if seek_table_end > sub_info_end {
        return Err(Error::InvalidScdTrack {
            reason: "OGG Vorbis seek table extends past sub_info region",
        });
    }
    let seek_table = &bytes[header_end..seek_table_end];
    Ok(ExtraData::OggVorbis { header, seek_table })
}

fn parse_atrac9_extra(bytes: &[u8], start_pos: usize, remaining: usize) -> Result<ExtraData<'_>> {
    if remaining < ATRAC9_HEADER_SIZE {
        return Err(Error::InvalidScdTrack {
            reason: "ATRAC9 header truncated by sub_info region",
        });
    }
    let end = start_pos + ATRAC9_HEADER_SIZE;
    let mut cursor = Cursor::new(&bytes[start_pos..end]);
    let wire =
        WireAtrac9Header::read(&mut cursor).map_err(|e| Error::decode("scd.atrac9_header", &e))?;
    Ok(ExtraData::Atrac9 {
        header: wire.into(),
    })
}

fn parse_mp3_extra(
    bytes: &[u8],
    start_pos: usize,
    sub_info_end: usize,
    remaining: usize,
) -> Result<ExtraData<'_>> {
    if remaining < 4 || &bytes[start_pos..start_pos + 4] != b"STBL" {
        return Ok(ExtraData::Opaque(&bytes[start_pos..sub_info_end]));
    }
    if remaining < PS3_MP3_SEEK_TABLE_HEADER_SIZE {
        return Err(Error::InvalidScdTrack {
            reason: "PS3 MP3 seek-table header truncated by sub_info region",
        });
    }
    let header_end = start_pos + PS3_MP3_SEEK_TABLE_HEADER_SIZE;
    let mut cursor = Cursor::new(&bytes[start_pos..header_end]);
    let wire = WirePs3Mp3SeekTableHeader::read(&mut cursor)
        .map_err(|e| Error::decode("scd.ps3_mp3_seek_table_header", &e))?;
    let header: Ps3Mp3SeekTableHeader = wire.into();
    let num_element = usize::try_from(header.num_element).map_err(|_| Error::InvalidScdTrack {
        reason: "PS3 MP3 num_element is negative or does not fit usize",
    })?;
    let seek_table_len = num_element.checked_mul(4).ok_or(Error::InvalidScdTrack {
        reason: "PS3 MP3 seek table size overflows usize",
    })?;
    let seek_table_end = header_end
        .checked_add(seek_table_len)
        .ok_or(Error::InvalidScdTrack {
            reason: "PS3 MP3 seek table extends past usize::MAX",
        })?;
    if seek_table_end > sub_info_end {
        return Err(Error::InvalidScdTrack {
            reason: "PS3 MP3 seek table extends past sub_info region",
        });
    }
    let seek_table = &bytes[header_end..seek_table_end];
    Ok(ExtraData::Mp3 { header, seek_table })
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::scd::track::AudioFlags;

    struct TrackFixture<'a> {
        format_raw: i32,
        channel_count: u32,
        sample_rate: u32,
        loop_start: u32,
        loop_end: u32,
        flags: u32,
        sub_info: &'a [u8],
        payload: &'a [u8],
        declared_payload_size: Option<u32>,
    }

    fn build_scd(audio_tracks: &[TrackFixture<'_>]) -> Vec<u8> {
        let mut buf = Vec::new();

        buf.extend_from_slice(b"SEDB");
        buf.extend_from_slice(b"SSCF");
        buf.extend_from_slice(&4u32.to_le_bytes());
        buf.push(0);
        buf.push(4);
        let scd_header_offset_u16 = u16::try_from(BINARY_HEADER_SIZE).unwrap();
        buf.extend_from_slice(&scd_header_offset_u16.to_le_bytes());
        let size_pos = buf.len();
        buf.extend_from_slice(&0u64.to_le_bytes());
        buf.extend_from_slice(&0u64.to_le_bytes());
        for _ in 0..4 {
            buf.extend_from_slice(&0u32.to_le_bytes());
        }
        assert_eq!(buf.len(), BINARY_HEADER_SIZE);

        let audio_count = u16::try_from(audio_tracks.len()).unwrap();
        buf.extend_from_slice(&0u16.to_le_bytes());
        buf.extend_from_slice(&0u16.to_le_bytes());
        buf.extend_from_slice(&audio_count.to_le_bytes());
        buf.extend_from_slice(&0u16.to_le_bytes());

        let audio_offsets_start = u32::try_from(BINARY_HEADER_SIZE + SCD_HEADER_SIZE).unwrap();
        let audio_offsets_size = u32::from(audio_count) * 4;
        let after_offsets = audio_offsets_start + audio_offsets_size;
        let track_offset = after_offsets;
        let audio_offset = audio_offsets_start;
        buf.extend_from_slice(&track_offset.to_le_bytes());
        buf.extend_from_slice(&audio_offset.to_le_bytes());
        buf.extend_from_slice(&0u32.to_le_bytes());
        buf.extend_from_slice(&0u32.to_le_bytes());
        buf.extend_from_slice(&0u32.to_le_bytes());
        buf.extend_from_slice(&0u16.to_le_bytes());
        buf.extend_from_slice(&0u16.to_le_bytes());
        assert_eq!(buf.len(), BINARY_HEADER_SIZE + SCD_HEADER_SIZE);

        let offsets_pos = buf.len();
        buf.resize(buf.len() + (audio_tracks.len() * 4), 0);
        assert_eq!(u32::try_from(buf.len()).unwrap(), after_offsets);

        for (i, track) in audio_tracks.iter().enumerate() {
            let desc_pos = buf.len();
            let off_le = u32::try_from(desc_pos).unwrap().to_le_bytes();
            buf[offsets_pos + i * 4..offsets_pos + i * 4 + 4].copy_from_slice(&off_le);

            let payload_size = track
                .declared_payload_size
                .unwrap_or_else(|| u32::try_from(track.payload.len()).unwrap());
            let sub_info_size = u32::try_from(track.sub_info.len()).unwrap();
            buf.extend_from_slice(&payload_size.to_le_bytes());
            buf.extend_from_slice(&track.channel_count.to_le_bytes());
            buf.extend_from_slice(&track.sample_rate.to_le_bytes());
            buf.extend_from_slice(&track.format_raw.to_le_bytes());
            buf.extend_from_slice(&track.loop_start.to_le_bytes());
            buf.extend_from_slice(&track.loop_end.to_le_bytes());
            buf.extend_from_slice(&sub_info_size.to_le_bytes());
            buf.extend_from_slice(&track.flags.to_le_bytes());
            buf.extend_from_slice(track.sub_info);
            buf.extend_from_slice(track.payload);
        }

        let total = u64::try_from(buf.len()).unwrap();
        buf[size_pos..size_pos + 8].copy_from_slice(&total.to_le_bytes());

        buf
    }

    fn track_default<'a>(
        format_raw: i32,
        payload: &'a [u8],
        sub_info: &'a [u8],
    ) -> TrackFixture<'a> {
        TrackFixture {
            format_raw,
            channel_count: 2,
            sample_rate: 44100,
            loop_start: 0,
            loop_end: 0,
            flags: 0,
            sub_info,
            payload,
            declared_payload_size: None,
        }
    }

    #[test]
    fn rejects_input_shorter_than_binary_header() {
        let bytes = vec![0u8; 16];
        let err = Scd::parse(&bytes).unwrap_err();
        assert!(matches!(
            err,
            Error::InvalidScdLength {
                found: 16,
                minimum: BINARY_HEADER_SIZE,
            }
        ));
    }

    #[test]
    fn rejects_bad_magic_type() {
        let mut bytes = vec![0u8; BINARY_HEADER_SIZE];
        bytes[0..4].copy_from_slice(b"NOPE");
        bytes[4..8].copy_from_slice(b"SSCF");
        let err = Scd::parse(&bytes).unwrap_err();
        assert!(
            matches!(err, Error::InvalidScdMagic { found_type, .. } if &found_type == b"NOPE"),
            "got {err:?}",
        );
    }

    #[test]
    fn rejects_bad_magic_sub_type() {
        let mut bytes = vec![0u8; BINARY_HEADER_SIZE];
        bytes[0..4].copy_from_slice(b"SEDB");
        bytes[4..8].copy_from_slice(b"WRNG");
        let err = Scd::parse(&bytes).unwrap_err();
        assert!(matches!(
            err,
            Error::InvalidScdMagic { found_sub_type, .. } if &found_sub_type == b"WRNG",
        ));
    }

    #[test]
    fn rejects_big_endian_files() {
        let scd = build_scd(&[track_default(-1, &[], &[])]);
        let mut bytes = scd;
        bytes[0x0C] = 1;
        let err = Scd::parse(&bytes).unwrap_err();
        assert!(matches!(
            err,
            Error::UnsupportedScdEndianness { endian_type: 1 }
        ));
    }

    #[test]
    fn parses_minimal_one_track_pcm16() {
        let payload = vec![0x12u8; 64];
        let bytes = build_scd(&[track_default(3, &payload, &[])]);
        let scd = Scd::parse(&bytes).expect("parse");
        assert_eq!(scd.binary_header().r#type, *b"SEDB");
        assert_eq!(scd.binary_header().sub_type, *b"SSCF");
        assert_eq!(scd.header().audio_count, 1);
        assert_eq!(scd.header().sound_count, 0);
        assert_eq!(scd.header().track_count, 0);
        assert_eq!(scd.tracks().len(), 1);
        let t = &scd.tracks()[0];
        assert_eq!(t.format(), ScdFormat::Unknown(3));
        assert_eq!(t.sample_rate(), 44100);
        assert_eq!(t.channel_count(), 2);
        assert_eq!(t.raw_bytes().len(), 64);
        assert!(t.raw_bytes().iter().all(|&b| b == 0x12));
        assert!(matches!(t.extra_data(), ExtraData::None));
    }

    #[test]
    fn parses_ms_adpcm_track_with_extra_data() {
        let mut sub_info = Vec::with_capacity(ADPCM_WAVE_FORMAT_SIZE);
        sub_info.extend_from_slice(&2u16.to_le_bytes());
        sub_info.extend_from_slice(&2i16.to_le_bytes());
        sub_info.extend_from_slice(&44100i32.to_le_bytes());
        sub_info.extend_from_slice(&22050i32.to_le_bytes());
        sub_info.extend_from_slice(&512i16.to_le_bytes());
        sub_info.extend_from_slice(&4u16.to_le_bytes());
        sub_info.extend_from_slice(&32i16.to_le_bytes());
        sub_info.extend_from_slice(&512u16.to_le_bytes());
        sub_info.extend_from_slice(&7u16.to_le_bytes());
        for i in 0..14i16 {
            sub_info.extend_from_slice(&i.to_le_bytes());
        }
        assert_eq!(sub_info.len(), ADPCM_WAVE_FORMAT_SIZE);

        let payload = vec![0xFFu8; 128];
        let mut tf = track_default(12, &payload, &sub_info);
        tf.loop_start = 16;
        tf.loop_end = 1024;
        let bytes = build_scd(&[tf]);
        let scd = Scd::parse(&bytes).expect("parse");
        let t = &scd.tracks()[0];
        assert_eq!(t.format(), ScdFormat::MsAdpcm);
        assert_eq!(t.loop_start(), 16);
        assert_eq!(t.loop_end(), 1024);
        match t.extra_data() {
            ExtraData::MsAdpcm(h) => {
                assert_eq!(h.format_tag, 2);
                assert_eq!(h.channels, 2);
                assert_eq!(h.samples_per_sec, 44100);
                assert_eq!(h.num_coef, 7);
                assert_eq!(h.coefficients[13], 13);
            }
            other => panic!("expected MsAdpcm extra_data, got {other:?}"),
        }
        assert_eq!(t.sub_info_bytes().len(), ADPCM_WAVE_FORMAT_SIZE);
        assert_eq!(t.raw_bytes().len(), 128);
    }

    #[test]
    fn parses_ogg_vorbis_track_with_seek_table() {
        let mut sub_info = Vec::new();
        sub_info.push(2u8);
        sub_info.push(32);
        sub_info.push(0x5A);
        sub_info.extend_from_slice(&[0u8; 9]);
        sub_info.extend_from_slice(&1.0f32.to_le_bytes());
        sub_info.extend_from_slice(&8u32.to_le_bytes());
        sub_info.extend_from_slice(&16u32.to_le_bytes());
        sub_info.extend_from_slice(&[0u8; 8]);
        assert_eq!(sub_info.len(), OGG_VORBIS_SEEK_TABLE_HEADER_SIZE);
        sub_info.extend_from_slice(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]);

        let mut payload = vec![0xABu8; 16 + 256];
        payload[..16].copy_from_slice(&[0x77u8; 16]);
        let mut tf = track_default(6, &payload, &sub_info);
        tf.declared_payload_size = Some(256);
        let bytes = build_scd(&[tf]);
        let scd = Scd::parse(&bytes).expect("parse");
        let t = &scd.tracks()[0];
        assert_eq!(t.format(), ScdFormat::OggVorbis);
        match t.extra_data() {
            ExtraData::OggVorbis { header, seek_table } => {
                assert_eq!(header.version, 2);
                assert_eq!(header.xor_byte, 0x5A);
                assert_eq!(header.seek_table_size, 8);
                assert_eq!(header.ogg_header_size, 16);
                assert_eq!(seek_table.len(), 8);
                assert_eq!(seek_table[0], 0x11);
            }
            other => panic!("expected OggVorbis extra_data, got {other:?}"),
        }
        assert_eq!(t.raw_bytes().len(), 16 + 256);
    }

    #[test]
    fn parses_atrac9_track_with_sample_count() {
        let mut sub_info = Vec::new();
        sub_info.extend_from_slice(&1u16.to_le_bytes());
        sub_info.extend_from_slice(&48u16.to_le_bytes());
        sub_info.extend_from_slice(&256u16.to_le_bytes());
        sub_info.extend_from_slice(&2048u16.to_le_bytes());
        sub_info.extend_from_slice(&0u32.to_le_bytes());
        sub_info.extend_from_slice(&0x1234_5678u32.to_le_bytes());
        sub_info.extend_from_slice(&100_000u32.to_le_bytes());
        sub_info.extend_from_slice(&0u32.to_le_bytes());
        sub_info.extend_from_slice(&256u32.to_le_bytes());
        sub_info.extend_from_slice(&48000u32.to_le_bytes());
        sub_info.extend_from_slice(&1024u32.to_le_bytes());
        sub_info.extend_from_slice(&98_976u32.to_le_bytes());
        sub_info.extend_from_slice(&[0u8; 8]);
        assert_eq!(sub_info.len(), ATRAC9_HEADER_SIZE);

        let payload = vec![0xCDu8; 32];
        let bytes = build_scd(&[track_default(22, &payload, &sub_info)]);
        let scd = Scd::parse(&bytes).expect("parse");
        let t = &scd.tracks()[0];
        assert_eq!(t.format(), ScdFormat::Atrac9);
        assert_eq!(t.sample_count(), Some(100_000));
        match t.extra_data() {
            ExtraData::Atrac9 { header: h } => {
                assert_eq!(h.superframe_samples, 2048);
                assert_eq!(h.sample_rate, 48000);
                assert_eq!(h.sample_loop_end, 98_976);
            }
            other => panic!("expected Atrac9 extra_data, got {other:?}"),
        }
    }

    #[test]
    fn parses_empty_format_track() {
        let bytes = build_scd(&[track_default(-1, &[], &[])]);
        let scd = Scd::parse(&bytes).expect("parse");
        let t = &scd.tracks()[0];
        assert_eq!(t.format(), ScdFormat::Empty);
        assert!(t.raw_bytes().is_empty());
        assert!(matches!(t.extra_data(), ExtraData::None));
        assert_eq!(t.sample_count(), None);
    }

    #[test]
    fn unknown_format_falls_through_to_opaque() {
        let sub_info = vec![0xDEu8; 16];
        let payload = vec![0xEFu8; 8];
        let bytes = build_scd(&[track_default(99, &payload, &sub_info)]);
        let scd = Scd::parse(&bytes).expect("parse");
        let t = &scd.tracks()[0];
        assert_eq!(t.format(), ScdFormat::Unknown(99));
        match t.extra_data() {
            ExtraData::Opaque(b) => assert_eq!(b.len(), 16),
            other => panic!("expected Opaque, got {other:?}"),
        }
    }

    #[test]
    fn parses_three_tracks_each_distinct_format() {
        let mut sub_adpcm = Vec::with_capacity(ADPCM_WAVE_FORMAT_SIZE);
        sub_adpcm.extend_from_slice(&2u16.to_le_bytes());
        sub_adpcm.extend_from_slice(&1i16.to_le_bytes());
        sub_adpcm.extend_from_slice(&22050i32.to_le_bytes());
        sub_adpcm.extend_from_slice(&11025i32.to_le_bytes());
        sub_adpcm.extend_from_slice(&256i16.to_le_bytes());
        sub_adpcm.extend_from_slice(&4u16.to_le_bytes());
        sub_adpcm.extend_from_slice(&32i16.to_le_bytes());
        sub_adpcm.extend_from_slice(&256u16.to_le_bytes());
        sub_adpcm.extend_from_slice(&7u16.to_le_bytes());
        for c in 0..14i16 {
            sub_adpcm.extend_from_slice(&c.to_le_bytes());
        }
        assert_eq!(sub_adpcm.len(), ADPCM_WAVE_FORMAT_SIZE);
        let payload_adpcm = vec![0x01u8; 32];

        let mut sub_ogg = Vec::new();
        sub_ogg.push(3u8);
        sub_ogg.push(32u8);
        sub_ogg.push(0xBBu8);
        sub_ogg.extend_from_slice(&[0u8; 9]);
        sub_ogg.extend_from_slice(&0.5f32.to_le_bytes());
        sub_ogg.extend_from_slice(&4u32.to_le_bytes());
        sub_ogg.extend_from_slice(&8u32.to_le_bytes());
        sub_ogg.extend_from_slice(&[0u8; 8]);
        assert_eq!(sub_ogg.len(), OGG_VORBIS_SEEK_TABLE_HEADER_SIZE);
        sub_ogg.extend_from_slice(&[0xAAu8; 4]);
        let mut payload_ogg = vec![0x77u8; 8 + 16];
        payload_ogg[..8].fill(0x55);

        let mut sub_at9 = Vec::new();
        sub_at9.extend_from_slice(&2u16.to_le_bytes());
        sub_at9.extend_from_slice(&48u16.to_le_bytes());
        sub_at9.extend_from_slice(&512u16.to_le_bytes());
        sub_at9.extend_from_slice(&4096u16.to_le_bytes());
        sub_at9.extend_from_slice(&0u32.to_le_bytes());
        sub_at9.extend_from_slice(&0xABCD_EF01u32.to_le_bytes());
        sub_at9.extend_from_slice(&200_000u32.to_le_bytes());
        sub_at9.extend_from_slice(&0u32.to_le_bytes());
        sub_at9.extend_from_slice(&512u32.to_le_bytes());
        sub_at9.extend_from_slice(&44100u32.to_le_bytes());
        sub_at9.extend_from_slice(&0u32.to_le_bytes());
        sub_at9.extend_from_slice(&200_000u32.to_le_bytes());
        sub_at9.extend_from_slice(&[0u8; 8]);
        assert_eq!(sub_at9.len(), ATRAC9_HEADER_SIZE);
        let payload_at9 = vec![0xCCu8; 64];

        let mut tf_ogg = track_default(6, &payload_ogg, &sub_ogg);
        tf_ogg.declared_payload_size = Some(16);

        let bytes = build_scd(&[
            track_default(12, &payload_adpcm, &sub_adpcm),
            tf_ogg,
            track_default(22, &payload_at9, &sub_at9),
        ]);
        let scd = Scd::parse(&bytes).expect("parse");
        assert_eq!(scd.tracks().len(), 3);

        assert_eq!(scd.tracks()[0].format(), ScdFormat::MsAdpcm);
        match scd.tracks()[0].extra_data() {
            ExtraData::MsAdpcm(h) => {
                assert_eq!(h.format_tag, 2);
                assert_eq!(h.channels, 1);
                assert_eq!(h.num_coef, 7);
            }
            other => panic!("track 0: expected MsAdpcm, got {other:?}"),
        }

        assert_eq!(scd.tracks()[1].format(), ScdFormat::OggVorbis);
        match scd.tracks()[1].extra_data() {
            ExtraData::OggVorbis { header, seek_table } => {
                assert_eq!(header.version, 3);
                assert_eq!(header.ogg_header_size, 8);
                assert_eq!(header.seek_table_size, 4);
                assert_eq!(seek_table.len(), 4);
            }
            other => panic!("track 1: expected OggVorbis, got {other:?}"),
        }
        assert_eq!(
            scd.tracks()[1].raw_bytes().len(),
            8 + 16,
            "OggVorbis raw_bytes must include the OGG header region"
        );

        assert_eq!(scd.tracks()[2].format(), ScdFormat::Atrac9);
        match scd.tracks()[2].extra_data() {
            ExtraData::Atrac9 { header: h } => {
                assert_eq!(h.sample_count, 200_000);
                assert_eq!(h.superframe_samples, 4096);
            }
            other => panic!("track 2: expected Atrac9, got {other:?}"),
        }
        assert_eq!(scd.tracks()[2].sample_count(), Some(200_000));

        let offsets: Vec<u32> = scd.tracks().iter().map(Track::descriptor_offset).collect();
        assert_eq!(offsets, scd.audio_offsets());
        for w in offsets.windows(2) {
            assert!(w[1] > w[0], "descriptor offsets must increase: {w:?}");
        }
    }

    #[test]
    fn rejects_audio_offset_past_end_of_file() {
        let bytes = build_scd(&[track_default(3, &[0u8; 16], &[])]);
        let truncated = &bytes[..bytes.len() - 32];
        let err = Scd::parse(truncated).unwrap_err();
        assert!(
            matches!(err, Error::InvalidScdLength { .. }),
            "expected InvalidScdLength, got {err:?}",
        );
    }

    #[test]
    fn rejects_track_payload_past_end_of_file() {
        let mut bytes = build_scd(&[track_default(3, &[], &[])]);
        let desc_off = BINARY_HEADER_SIZE + SCD_HEADER_SIZE + 4;
        bytes[desc_off..desc_off + 4].copy_from_slice(&(1u32 << 20).to_le_bytes());
        let err = Scd::parse(&bytes).unwrap_err();
        assert!(
            matches!(err, Error::InvalidScdLength { .. }),
            "expected InvalidScdLength, got {err:?}",
        );
    }

    #[test]
    fn rejects_scd_header_offset_inside_binary_header() {
        let mut bytes = build_scd(&[track_default(3, &[], &[])]);
        bytes[0x0E..0x10].copy_from_slice(&0u16.to_le_bytes());
        let err = Scd::parse(&bytes).unwrap_err();
        assert!(
            matches!(err, Error::InvalidScdTrack { .. }),
            "expected InvalidScdTrack, got {err:?}",
        );
    }

    #[test]
    fn marker_chunk_is_parsed_when_flag_set() {
        let mut sub_info = Vec::new();
        sub_info.extend_from_slice(&0xDEAD_BEEFu32.to_le_bytes());
        sub_info.extend_from_slice(&28u32.to_le_bytes());
        sub_info.extend_from_slice(&100i32.to_le_bytes());
        sub_info.extend_from_slice(&1000i32.to_le_bytes());
        sub_info.extend_from_slice(&2i32.to_le_bytes());
        sub_info.extend_from_slice(&500u32.to_le_bytes());
        sub_info.extend_from_slice(&750u32.to_le_bytes());
        assert_eq!(sub_info.len(), 28);

        let mut tf = track_default(3, &[], &sub_info);
        tf.flags = AudioFlags::MARKER_CHUNK;
        let bytes = build_scd(&[tf]);
        let scd = Scd::parse(&bytes).expect("parse");
        let t = &scd.tracks()[0];
        let mc = t.marker_chunk().expect("marker chunk present");
        assert_eq!(mc.chunk_id, 0xDEAD_BEEF);
        assert_eq!(mc.chunk_size, 28);
        assert_eq!(mc.sample_loop_start, 100);
        assert_eq!(mc.sample_loop_end, 1000);
        assert_eq!(mc.marker_count, 2);
        assert_eq!(t.marker_positions_bytes().len(), 8);
    }

    #[test]
    fn marker_chunk_with_negative_count_rejected() {
        let mut sub_info = Vec::new();
        sub_info.extend_from_slice(&0u32.to_le_bytes());
        sub_info.extend_from_slice(&20u32.to_le_bytes());
        sub_info.extend_from_slice(&0i32.to_le_bytes());
        sub_info.extend_from_slice(&0i32.to_le_bytes());
        sub_info.extend_from_slice(&(-1i32).to_le_bytes());
        let mut tf = track_default(3, &[], &sub_info);
        tf.flags = AudioFlags::MARKER_CHUNK;
        let bytes = build_scd(&[tf]);
        let err = Scd::parse(&bytes).unwrap_err();
        assert!(matches!(err, Error::InvalidScdTrack { .. }));
    }

    #[test]
    fn zero_audio_count_yields_no_tracks() {
        let bytes = build_scd(&[]);
        let scd = Scd::parse(&bytes).expect("parse");
        assert_eq!(scd.tracks().len(), 0);
        assert_eq!(scd.audio_offsets().len(), 0);
        assert_eq!(scd.track_offsets().len(), 0);
        assert_eq!(scd.sound_offsets().len(), 0);
    }

    #[test]
    fn audio_offsets_and_tracks_agree_on_count_and_offsets() {
        let payload_a = vec![1u8; 4];
        let payload_b = vec![2u8; 4];
        let bytes = build_scd(&[
            track_default(-1, &payload_a, &[]),
            track_default(3, &payload_b, &[]),
        ]);
        let scd = Scd::parse(&bytes).expect("parse");
        assert_eq!(scd.audio_offsets().len(), scd.tracks().len());
        for (i, t) in scd.tracks().iter().enumerate() {
            assert_eq!(t.descriptor_offset(), scd.audio_offsets()[i]);
        }
    }

    #[test]
    fn rejects_mp3_seek_table_with_negative_num_element() {
        let mut sub_info = Vec::new();
        sub_info.extend_from_slice(b"STBL");
        sub_info.push(0);
        sub_info.extend_from_slice(&[0u8; 3]);
        sub_info.extend_from_slice(&1.0f32.to_le_bytes());
        sub_info.extend_from_slice(&(-5i32).to_le_bytes());
        assert_eq!(sub_info.len(), PS3_MP3_SEEK_TABLE_HEADER_SIZE);
        let bytes = build_scd(&[track_default(7, &[0u8; 8], &sub_info)]);
        let err = Scd::parse(&bytes).unwrap_err();
        assert!(matches!(err, Error::InvalidScdTrack { .. }));
    }

    #[test]
    fn ogg_vorbis_raw_bytes_includes_ogg_header_region() {
        const OGG_HEADER_SIZE: u32 = 20;
        const PAYLOAD_BODY_SIZE: u32 = 48;

        let mut sub_info = Vec::new();
        sub_info.push(2u8);
        sub_info.push(32u8);
        sub_info.push(0u8);
        sub_info.extend_from_slice(&[0u8; 9]);
        sub_info.extend_from_slice(&1.0f32.to_le_bytes());
        sub_info.extend_from_slice(&8u32.to_le_bytes());
        sub_info.extend_from_slice(&OGG_HEADER_SIZE.to_le_bytes());
        sub_info.extend_from_slice(&[0u32.to_le_bytes(), 0u32.to_le_bytes()].concat());
        assert_eq!(sub_info.len(), OGG_VORBIS_SEEK_TABLE_HEADER_SIZE);
        sub_info.extend_from_slice(&[0x00u8; 8]);

        let ogg_header_bytes = OGG_HEADER_SIZE as usize;
        let body_bytes = PAYLOAD_BODY_SIZE as usize;
        let mut payload_region = vec![0u8; ogg_header_bytes + body_bytes];
        payload_region[..ogg_header_bytes].fill(0xAB);
        payload_region[ogg_header_bytes..].fill(0xCD);

        let mut tf = track_default(6, &payload_region, &sub_info);
        tf.declared_payload_size = Some(PAYLOAD_BODY_SIZE);

        let bytes = build_scd(&[tf]);
        let scd = Scd::parse(&bytes).expect("parse");
        let t = &scd.tracks()[0];
        assert_eq!(t.format(), ScdFormat::OggVorbis);

        let raw = t.raw_bytes();

        assert_eq!(
            raw.len(),
            ogg_header_bytes + body_bytes,
            "raw_bytes() must span ogg_header_size + payload_size bytes"
        );

        assert!(
            raw[..ogg_header_bytes].iter().all(|&b| b == 0xAB),
            "first {ogg_header_bytes} bytes of raw_bytes() must be the OGG header (0xAB sentinel)"
        );

        assert!(
            raw[ogg_header_bytes..].iter().all(|&b| b == 0xCD),
            "remaining {body_bytes} bytes of raw_bytes() must be the OGG body (0xCD sentinel)"
        );
    }

    #[test]
    fn parses_mp3_stbl_track_with_seek_table() {
        let num_element: i32 = 3;
        let mut sub_info = Vec::new();
        sub_info.extend_from_slice(b"STBL");
        sub_info.push(1u8);
        sub_info.extend_from_slice(&[0u8; 3]);
        sub_info.extend_from_slice(&0.25f32.to_le_bytes());
        sub_info.extend_from_slice(&num_element.to_le_bytes());
        assert_eq!(sub_info.len(), PS3_MP3_SEEK_TABLE_HEADER_SIZE);
        sub_info.extend_from_slice(&100u32.to_le_bytes());
        sub_info.extend_from_slice(&200u32.to_le_bytes());
        sub_info.extend_from_slice(&300u32.to_le_bytes());

        let payload = vec![0xEEu8; 24];
        let bytes = build_scd(&[track_default(7, &payload, &sub_info)]);
        let scd = Scd::parse(&bytes).expect("parse");
        let t = &scd.tracks()[0];
        assert_eq!(t.format(), ScdFormat::Mp3);
        match t.extra_data() {
            ExtraData::Mp3 { header, seek_table } => {
                assert_eq!(&header.id, b"STBL");
                assert_eq!(header.version, 1);
                assert_eq!(header.num_element, 3);
                assert!((header.step - 0.25f32).abs() < 1e-6, "step mismatch");
                assert_eq!(seek_table.len(), 12);
                assert_eq!(&seek_table[0..4], &100u32.to_le_bytes());
            }
            other => panic!("expected Mp3 extra_data, got {other:?}"),
        }
    }

    #[test]
    fn parses_mp3_non_stbl_track_as_opaque() {
        let sub_info = [
            0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
        ];
        let payload = vec![0xFFu8; 8];
        let bytes = build_scd(&[track_default(7, &payload, &sub_info)]);
        let scd = Scd::parse(&bytes).expect("parse");
        let t = &scd.tracks()[0];
        assert_eq!(t.format(), ScdFormat::Mp3);
        match t.extra_data() {
            ExtraData::Opaque(b) => {
                assert_eq!(b.len(), sub_info.len());
                assert_eq!(b[0], 0xDE);
            }
            other => panic!("expected Opaque extra_data for non-STBL Mp3, got {other:?}"),
        }
    }

    #[test]
    fn unknown_discriminant_3_routes_to_opaque() {
        let sub_info = vec![0x11u8; 12];
        let payload = vec![0x22u8; 8];
        let bytes = build_scd(&[track_default(3, &payload, &sub_info)]);
        let scd = Scd::parse(&bytes).expect("parse");
        let t = &scd.tracks()[0];
        assert_eq!(t.format(), ScdFormat::Unknown(3));
        match t.extra_data() {
            ExtraData::Opaque(b) => assert_eq!(b.len(), 12),
            other => panic!("expected Opaque for Unknown(3), got {other:?}"),
        }
    }

    #[test]
    fn unknown_discriminant_10_routes_to_opaque() {
        let sub_info = vec![0x33u8; 8];
        let payload = vec![0x44u8; 16];
        let bytes = build_scd(&[track_default(10, &payload, &sub_info)]);
        let scd = Scd::parse(&bytes).expect("parse");
        let t = &scd.tracks()[0];
        assert_eq!(t.format(), ScdFormat::Unknown(10));
        match t.extra_data() {
            ExtraData::Opaque(b) => assert_eq!(b.len(), 8),
            other => panic!("expected Opaque for Unknown(10), got {other:?}"),
        }
    }

    #[test]
    fn invalid_scd_magic_triggers_correctly() {
        let mut bytes = vec![0u8; BINARY_HEADER_SIZE];
        bytes[0..4].copy_from_slice(b"XXXX");
        bytes[4..8].copy_from_slice(b"SSCF");
        assert!(matches!(
            Scd::parse(&bytes).unwrap_err(),
            Error::InvalidScdMagic { .. }
        ));
    }

    #[test]
    fn unsupported_endianness_triggers_correctly() {
        let mut bytes = build_scd(&[]);
        bytes[0x0C] = 2;
        assert!(matches!(
            Scd::parse(&bytes).unwrap_err(),
            Error::UnsupportedScdEndianness { endian_type: 2 }
        ));
    }

    #[test]
    fn invalid_scd_length_triggers_correctly() {
        let bytes = vec![0u8; 10];
        assert!(matches!(
            Scd::parse(&bytes).unwrap_err(),
            Error::InvalidScdLength {
                found: 10,
                minimum: BINARY_HEADER_SIZE
            }
        ));
    }

    #[test]
    fn invalid_scd_track_triggers_correctly() {
        let bytes = build_scd(&[track_default(3, &[0u8; 16], &[])]);
        let truncated = &bytes[..bytes.len() - 40];
        let err = Scd::parse(truncated).unwrap_err();
        assert!(
            matches!(
                err,
                Error::InvalidScdLength { .. } | Error::InvalidScdTrack { .. }
            ),
            "expected structural error, got {err:?}",
        );
    }

    #[test]
    fn zero_track_scd_parses_and_yields_empty_tracks() {
        let bytes = build_scd(&[]);
        let scd = Scd::parse(&bytes).expect("parse");
        assert_eq!(scd.tracks().len(), 0);
        assert_eq!(scd.header().audio_count, 0);
        assert_eq!(scd.audio_offsets().len(), 0);
    }

    #[test]
    fn opaque_extra_data_is_exercised_for_unknown_codec() {
        let sub_info = vec![0xFFu8; 24];
        let bytes = build_scd(&[track_default(999, &[0u8; 4], &sub_info)]);
        let scd = Scd::parse(&bytes).expect("parse");
        let t = &scd.tracks()[0];
        assert!(matches!(t.extra_data(), ExtraData::Opaque(b) if b.len() == 24));
    }

    #[test]
    fn sub_info_exactly_at_codec_header_boundary_parses() {
        let mut sub_info = Vec::with_capacity(ADPCM_WAVE_FORMAT_SIZE);
        sub_info.extend_from_slice(&2u16.to_le_bytes());
        sub_info.extend_from_slice(&2i16.to_le_bytes());
        sub_info.extend_from_slice(&44100i32.to_le_bytes());
        sub_info.extend_from_slice(&22050i32.to_le_bytes());
        sub_info.extend_from_slice(&512i16.to_le_bytes());
        sub_info.extend_from_slice(&4u16.to_le_bytes());
        sub_info.extend_from_slice(&32i16.to_le_bytes());
        sub_info.extend_from_slice(&512u16.to_le_bytes());
        sub_info.extend_from_slice(&7u16.to_le_bytes());
        for i in 0..14i16 {
            sub_info.extend_from_slice(&i.to_le_bytes());
        }
        assert_eq!(sub_info.len(), ADPCM_WAVE_FORMAT_SIZE);

        let payload = vec![0xAAu8; 8];
        let bytes = build_scd(&[track_default(12, &payload, &sub_info)]);
        let scd = Scd::parse(&bytes).expect("parse at exact codec header boundary");
        assert!(matches!(
            scd.tracks()[0].extra_data(),
            ExtraData::MsAdpcm(_)
        ));
    }

    #[test]
    fn payload_at_exact_file_end_parses() {
        let payload = vec![0xFEu8; 16];
        let bytes = build_scd(&[track_default(3, &payload, &[])]);
        let scd = Scd::parse(&bytes).expect("parse");
        assert_eq!(scd.tracks()[0].raw_bytes().len(), 16);
        assert!(scd.tracks()[0].raw_bytes().iter().all(|&b| b == 0xFE));
    }

    fn lcg_next(state: &mut u64) -> u8 {
        *state = state
            .wrapping_mul(6_364_136_223_846_793_005)
            .wrapping_add(1_442_695_040_888_963_407);
        ((*state >> 33) & 0xFF) as u8
    }

    #[test]
    fn scd_parse_never_panics_on_random_buffers_up_to_4kib() {
        let mut state: u64 = 0xDEAD_BEEF_CAFE_F00D;
        for _ in 0..1000 {
            let len = (usize::from(lcg_next(&mut state)) * 16).min(4096);
            let buf: Vec<u8> = (0..len).map(|_| lcg_next(&mut state)).collect();
            let _ = Scd::parse(&buf);
        }
    }
}
