/// Maximum number of bone index slots in the legacy bone-table format.
pub const BONE_TABLE_BONE_SLOTS: usize = 64;

/// Per-mesh bone palette that remaps local bone indices to the model's global bone array.
///
/// Two on-disk layouts exist:
///
/// - **Legacy** (model version `≤ 0x01000005`): a fixed 132-byte block with up to
///   [`BONE_TABLE_BONE_SLOTS`] slots and a single `bone_count` byte.
/// - **V2** (model version `≥ 0x01000006`, Dawntrail and later): a compact layout with a
///   `(first_index, count)` header followed by a variable-length index array, 4-byte aligned.
///
/// Use [`BoneTable::indices`] to get the resolved bone indices regardless of format.
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BoneTable {
    indices: Vec<u16>,
    first_index: u16,
    version_tag: BoneTableVersion,
}

/// Indicates which on-disk layout was used to parse a [`BoneTable`].
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoneTableVersion {
    /// Fixed 132-byte layout; model version `≤ 0x01000005`.
    Legacy,
    /// Compact variable-length layout; model version `≥ 0x01000006` (Dawntrail).
    V2,
}

impl BoneTable {
    /// Maximum [`ModelFileHeader::version`] that uses the legacy bone-table layout.
    pub const LEGACY_VERSION_MAX: u32 = 0x0100_0005;

    /// Minimum [`ModelFileHeader::version`] that uses the V2 bone-table layout.
    pub const V2_VERSION_MIN: u32 = 0x0100_0006;

    #[must_use]
    pub(crate) fn from_legacy(indices: [u16; BONE_TABLE_BONE_SLOTS], bone_count: u8) -> Self {
        let used = usize::from(bone_count).min(BONE_TABLE_BONE_SLOTS);
        Self {
            indices: indices[..used].to_vec(),
            first_index: 0,
            version_tag: BoneTableVersion::Legacy,
        }
    }

    #[must_use]
    pub(crate) fn from_v2(first_index: u16, indices: Vec<u16>) -> Self {
        Self {
            indices,
            first_index,
            version_tag: BoneTableVersion::V2,
        }
    }

    /// Returns the on-disk format that was used to decode this table.
    #[must_use]
    pub fn version_tag(&self) -> BoneTableVersion {
        self.version_tag
    }

    /// Returns the bone indices held by this table.
    ///
    /// For legacy tables this is a slice of the first `bone_count` entries from the 64-slot
    /// block.  For V2 tables this is the full variable-length index array read from the file.
    #[must_use]
    pub fn indices(&self) -> &[u16] {
        &self.indices
    }

    /// Returns the `first_index` field from the V2 header; always `0` for legacy tables.
    #[must_use]
    pub fn first_index(&self) -> u16 {
        self.first_index
    }
}

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

    #[test]
    fn legacy_constructor_clamps_count() {
        let mut slots = [0u16; BONE_TABLE_BONE_SLOTS];
        for (i, slot) in slots.iter_mut().enumerate() {
            *slot = u16::try_from(i).expect("i fits u16");
        }
        let bt = BoneTable::from_legacy(slots, 5);
        assert_eq!(bt.indices().len(), 5);
        assert_eq!(bt.indices(), &[0, 1, 2, 3, 4]);
        assert_eq!(bt.version_tag(), BoneTableVersion::Legacy);
        assert_eq!(bt.first_index(), 0);

        let bt = BoneTable::from_legacy(slots, 200);
        assert_eq!(bt.indices().len(), BONE_TABLE_BONE_SLOTS);
    }

    #[test]
    fn v2_constructor_round_trips() {
        let bt = BoneTable::from_v2(7, vec![3, 5, 11]);
        assert_eq!(bt.indices().len(), 3);
        assert_eq!(bt.indices(), &[3, 5, 11]);
        assert_eq!(bt.version_tag(), BoneTableVersion::V2);
        assert_eq!(bt.first_index(), 7);
    }

    #[test]
    fn version_thresholds_match_wire_format() {
        assert_eq!(BoneTable::LEGACY_VERSION_MAX, 0x0100_0005);
        assert_eq!(BoneTable::V2_VERSION_MIN, 0x0100_0006);
        assert_eq!(BoneTable::LEGACY_VERSION_MAX + 1, BoneTable::V2_VERSION_MIN);
    }
}
