use byteorder::{BigEndian, ReadBytesExt};
/// TrueType cmap table extraction for font character mapping
///
/// This module extracts Unicode mappings from TrueType font cmap tables,
/// providing a fallback for Type0 fonts missing ToUnicode CMaps.
///
/// The cmap table maps glyph IDs (GIDs) to Unicode code points.
/// We support formats 4 (BMP), 6 (trimmed), and 12 (Unicode full).
use std::collections::HashMap;
use std::io::Cursor;

/// Represents a TrueType cmap table extracted from an embedded font
#[derive(Debug, Clone)]
pub struct TrueTypeCMap {
    /// Mapping from Glyph ID to Unicode character
    gid_to_unicode: HashMap<u16, char>,
}

impl TrueTypeCMap {
    /// Parse TrueType cmap table from font data
    ///
    /// The TrueType sfnt structure contains a directory of tables.
    /// We locate the 'cmap' table and parse the best available subtable.
    ///
    /// Priority for cmap subtables:
    /// 1. Platform 3 (Windows), Encoding 10 (Unicode full repertoire) - supports all Unicode
    /// 2. Platform 3 (Windows), Encoding 1 (Unicode BMP) - supports basic multilingual plane
    /// 3. Platform 0 (Unicode), Encoding 3 - fallback to old Unicode platform
    pub fn from_font_data(data: &[u8]) -> Result<Self, String> {
        let mut cursor = Cursor::new(data);

        // Parse sfnt header to locate table directory
        let (num_tables, search_range, entry_selector, range_shift) =
            Self::parse_sfnt_header(&mut cursor)?;

        // Find cmap table entry in the directory
        let cmap_offset = Self::find_cmap_table(
            &mut cursor,
            num_tables,
            search_range,
            entry_selector,
            range_shift,
        )?;

        // Parse cmap table and find the best subtable
        cursor.set_position(cmap_offset as u64);
        let cmap_version = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read cmap version: {}", e))?;

        if cmap_version != 0 {
            return Err(format!("Unsupported cmap table version: {}", cmap_version));
        }

        let num_subtables = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read cmap subtable count: {}", e))?;

        // Read all subtable records
        let mut best_subtable: Option<(u32, u32, u32)> = None; // (platform_id, encoding_id, offset)
        let mut best_priority = -1i32;

        for _ in 0..num_subtables {
            let platform_id = cursor
                .read_u16::<BigEndian>()
                .map_err(|e| format!("Failed to read platform ID: {}", e))?;
            let encoding_id = cursor
                .read_u16::<BigEndian>()
                .map_err(|e| format!("Failed to read encoding ID: {}", e))?;
            let offset = cursor
                .read_u32::<BigEndian>()
                .map_err(|e| format!("Failed to read subtable offset: {}", e))?;

            // Calculate priority: higher is better
            let priority = match (platform_id, encoding_id) {
                (3, 10) => 30, // Windows, Unicode full repertoire
                (3, 1) => 20,  // Windows, Unicode BMP
                (0, 3) => 10,  // Unicode platform, Unicode 2.0
                _ => 0,
            };

            if priority > best_priority {
                best_priority = priority;
                best_subtable = Some((platform_id as u32, encoding_id as u32, offset));
            }
        }

        let (platform_id, encoding_id, subtable_offset) =
            best_subtable.ok_or_else(|| "No suitable cmap subtable found".to_string())?;

        log::debug!(
            "TrueType cmap: selected platform={} encoding={} offset={}",
            platform_id,
            encoding_id,
            subtable_offset
        );

        // Parse the selected cmap subtable
        cursor.set_position((cmap_offset + subtable_offset) as u64);
        let gid_to_unicode = Self::parse_cmap_subtable(&mut cursor)?;

        Ok(TrueTypeCMap { gid_to_unicode })
    }

    /// Get Unicode character for a glyph ID
    pub fn get_unicode(&self, gid: u16) -> Option<char> {
        self.gid_to_unicode.get(&gid).copied()
    }

    /// Get the number of glyph mappings
    pub fn len(&self) -> usize {
        self.gid_to_unicode.len()
    }

    /// Check if cmap is empty
    pub fn is_empty(&self) -> bool {
        self.gid_to_unicode.is_empty()
    }

    // ==================================================================================
    // Private Helper Methods
    // ==================================================================================

    fn parse_sfnt_header(cursor: &mut Cursor<&[u8]>) -> Result<(u16, u16, u16, u16), String> {
        // Read sfnt version (4 bytes - can be 0x00010000 for TrueType or "OTTO" for OpenType)
        let version = cursor
            .read_u32::<BigEndian>()
            .map_err(|e| format!("Failed to read sfnt version: {}", e))?;

        // 0x00010000 = TrueType, 0x4F54544F = OpenType (OTTO), 0x74727565 = Apple TrueType ("true")
        if version != 0x00010000 && version != 0x4F54544F && version != 0x74727565 {
            // 0x4F54544F = "OTTO" (OpenType)
            return Err(format!("Invalid sfnt version: 0x{:08X}", version));
        }

        let num_tables = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read table count: {}", e))?;
        let search_range = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read search range: {}", e))?;
        let entry_selector = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read entry selector: {}", e))?;
        let range_shift = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read range shift: {}", e))?;

        Ok((num_tables, search_range, entry_selector, range_shift))
    }

    fn find_cmap_table(
        cursor: &mut Cursor<&[u8]>,
        num_tables: u16,
        _search_range: u16,
        _entry_selector: u16,
        _range_shift: u16,
    ) -> Result<u32, String> {
        // Linear search through table directory for 'cmap' tag (0x636D6170)
        const CMAP_TAG: u32 = 0x636D6170;

        for _ in 0..num_tables {
            let tag = cursor
                .read_u32::<BigEndian>()
                .map_err(|e| format!("Failed to read table tag: {}", e))?;
            let _checksum = cursor
                .read_u32::<BigEndian>()
                .map_err(|e| format!("Failed to read table checksum: {}", e))?;
            let offset = cursor
                .read_u32::<BigEndian>()
                .map_err(|e| format!("Failed to read table offset: {}", e))?;
            let _length = cursor
                .read_u32::<BigEndian>()
                .map_err(|e| format!("Failed to read table length: {}", e))?;

            if tag == CMAP_TAG {
                return Ok(offset);
            }
        }

        Err("cmap table not found in font".to_string())
    }

    fn parse_cmap_subtable(cursor: &mut Cursor<&[u8]>) -> Result<HashMap<u16, char>, String> {
        let format = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read cmap format: {}", e))?;

        match format {
            4 => Self::parse_cmap_format4(cursor),
            6 => Self::parse_cmap_format6(cursor),
            12 => Self::parse_cmap_format12(cursor),
            _ => Err(format!("Unsupported cmap format: {}", format)),
        }
    }

    /// Parse cmap format 4 (BMP - supports characters U+0000 to U+FFFF)
    fn parse_cmap_format4(cursor: &mut Cursor<&[u8]>) -> Result<HashMap<u16, char>, String> {
        let _length = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read format 4 length: {}", e))?
            as u32;
        let _language = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read format 4 language: {}", e))?;

        let seg_count_x2 = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read segCountX2: {}", e))?
            as usize;
        let seg_count = seg_count_x2 / 2;

        // Skip binary search parameters
        let _search_range = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read searchRange: {}", e))?;
        let _entry_selector = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read entrySelector: {}", e))?;
        let _range_shift = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read rangeShift: {}", e))?;

        // Read segment arrays
        let mut end_codes = vec![0u16; seg_count];
        for i in 0..seg_count {
            end_codes[i] = cursor
                .read_u16::<BigEndian>()
                .map_err(|e| format!("Failed to read endCode[{}]: {}", i, e))?;
        }

        // Reserved pad
        let _reserved = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read reserved pad: {}", e))?;

        let mut start_codes = vec![0u16; seg_count];
        for i in 0..seg_count {
            start_codes[i] = cursor
                .read_u16::<BigEndian>()
                .map_err(|e| format!("Failed to read startCode[{}]: {}", i, e))?;
        }

        let mut id_deltas = vec![0i16; seg_count];
        for i in 0..seg_count {
            id_deltas[i] = cursor
                .read_i16::<BigEndian>()
                .map_err(|e| format!("Failed to read idDelta[{}]: {}", i, e))?;
        }

        // id_range_offsets require special parsing - just read as array
        let mut id_range_offsets = vec![0u16; seg_count];
        for i in 0..seg_count {
            id_range_offsets[i] = cursor
                .read_u16::<BigEndian>()
                .map_err(|e| format!("Failed to read idRangeOffset[{}]: {}", i, e))?;
        }

        // Read remaining bytes as glyphIdArray (used when idRangeOffset != 0)
        let mut glyph_id_array = Vec::new();
        while let Ok(val) = cursor.read_u16::<BigEndian>() {
            glyph_id_array.push(val);
        }

        // Build character to GID mappings
        let mut gid_to_unicode = HashMap::new();

        for seg in 0..seg_count {
            let start = start_codes[seg] as u32;
            let end = end_codes[seg] as u32;
            let id_delta = id_deltas[seg] as i32;

            for char_code in start..=end {
                if char_code == 0xFFFF {
                    break; // End segment marker
                }

                let gid = if id_range_offsets[seg] == 0 {
                    // Simple formula: GID = charCode + idDelta
                    (char_code as i32 + id_delta) as u16
                } else {
                    // Per TrueType spec: index into glyphIdArray
                    // offset = idRangeOffset[i]/2 + (charCode - startCode[i]) + i - segCount
                    let offset = (id_range_offsets[seg] as usize) / 2
                        + (char_code as usize - start as usize)
                        + seg
                        - seg_count;
                    if offset < glyph_id_array.len() {
                        let raw = glyph_id_array[offset];
                        if raw != 0 {
                            (raw as i32 + id_delta) as u16
                        } else {
                            0
                        }
                    } else {
                        0
                    }
                };

                if gid != 0 {
                    if let Some(ch) = char::from_u32(char_code) {
                        gid_to_unicode.insert(gid, ch);
                    }
                }
            }
        }

        Ok(gid_to_unicode)
    }

    /// Parse cmap format 6 (trimmed table)
    fn parse_cmap_format6(cursor: &mut Cursor<&[u8]>) -> Result<HashMap<u16, char>, String> {
        let _length = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read format 6 length: {}", e))?;
        let _language = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read format 6 language: {}", e))?;

        let first_code = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read firstCode: {}", e))?;
        let count = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read entryCount: {}", e))? as usize;

        let mut gid_to_unicode = HashMap::new();

        for i in 0..count {
            let gid = cursor
                .read_u16::<BigEndian>()
                .map_err(|e| format!("Failed to read glyphId[{}]: {}", i, e))?;

            let char_code = first_code as u32 + i as u32;
            if let Some(ch) = char::from_u32(char_code) {
                gid_to_unicode.insert(gid, ch);
            }
        }

        Ok(gid_to_unicode)
    }

    /// Parse cmap format 12 (segmented coverage - supports full Unicode)
    fn parse_cmap_format12(cursor: &mut Cursor<&[u8]>) -> Result<HashMap<u16, char>, String> {
        // Skip reserved bytes
        let _reserved = cursor
            .read_u16::<BigEndian>()
            .map_err(|e| format!("Failed to read reserved: {}", e))?;

        let _length = cursor
            .read_u32::<BigEndian>()
            .map_err(|e| format!("Failed to read format 12 length: {}", e))?;
        let _language = cursor
            .read_u32::<BigEndian>()
            .map_err(|e| format!("Failed to read format 12 language: {}", e))?;

        let num_groups = cursor
            .read_u32::<BigEndian>()
            .map_err(|e| format!("Failed to read numGroups: {}", e))?
            as usize;

        let mut gid_to_unicode = HashMap::new();

        for _ in 0..num_groups {
            let start_char_code = cursor
                .read_u32::<BigEndian>()
                .map_err(|e| format!("Failed to read startCharCode: {}", e))?;
            let end_char_code = cursor
                .read_u32::<BigEndian>()
                .map_err(|e| format!("Failed to read endCharCode: {}", e))?;
            let start_gid = cursor
                .read_u32::<BigEndian>()
                .map_err(|e| format!("Failed to read startGlyphId: {}", e))?;

            for (offset, char_code) in (start_char_code..=end_char_code).enumerate() {
                let gid = (start_gid + offset as u32) as u16;
                if let Some(ch) = char::from_u32(char_code) {
                    gid_to_unicode.insert(gid, ch);
                }
            }
        }

        Ok(gid_to_unicode)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use byteorder::{BigEndian, WriteBytesExt};

    /// Build a minimal TrueType font with a cmap format 4 table.
    fn build_truetype_with_cmap_format4(mappings: &[(u16, u16)]) -> Vec<u8> {
        // We need: sfnt header + table directory (1 table: cmap) + cmap table
        let mut data = Vec::new();

        // ---- sfnt header ----
        data.write_u32::<BigEndian>(0x00010000).unwrap(); // TrueType version
        data.write_u16::<BigEndian>(1).unwrap(); // numTables = 1
        data.write_u16::<BigEndian>(16).unwrap(); // searchRange
        data.write_u16::<BigEndian>(0).unwrap(); // entrySelector
        data.write_u16::<BigEndian>(0).unwrap(); // rangeShift

        // ---- table directory (1 entry) ----
        let cmap_offset: u32 = 12 + 16; // sfnt header (12) + 1 table record (16)
        data.write_u32::<BigEndian>(0x636D6170).unwrap(); // 'cmap' tag
        data.write_u32::<BigEndian>(0).unwrap(); // checksum (unused)
        data.write_u32::<BigEndian>(cmap_offset).unwrap(); // offset
        data.write_u32::<BigEndian>(0).unwrap(); // length (unused)

        // ---- cmap table header ----
        data.write_u16::<BigEndian>(0).unwrap(); // version
        data.write_u16::<BigEndian>(1).unwrap(); // numSubtables = 1

        // subtable record: platform=3 (Windows), encoding=1 (Unicode BMP)
        let subtable_offset: u32 = 4 + 8; // cmap header (4) + 1 record (8)
        data.write_u16::<BigEndian>(3).unwrap(); // platformID
        data.write_u16::<BigEndian>(1).unwrap(); // encodingID
        data.write_u32::<BigEndian>(subtable_offset).unwrap();

        // ---- cmap format 4 subtable ----
        // Build segments from mappings. Each mapping is (charCode, gid).
        // For simplicity, create one segment per mapping + the sentinel 0xFFFF segment.
        let mut segments: Vec<(u16, u16, i16)> = Vec::new(); // (start, end, delta)
        for &(char_code, gid) in mappings {
            let delta = gid as i16 - char_code as i16;
            segments.push((char_code, char_code, delta));
        }
        segments.push((0xFFFF, 0xFFFF, 1)); // sentinel

        let seg_count = segments.len();
        let seg_count_x2 = (seg_count * 2) as u16;

        data.write_u16::<BigEndian>(4).unwrap(); // format
                                                 // length placeholder (we'll fill in later)
        let length_pos = data.len();
        data.write_u16::<BigEndian>(0).unwrap(); // length
        data.write_u16::<BigEndian>(0).unwrap(); // language

        data.write_u16::<BigEndian>(seg_count_x2).unwrap();
        data.write_u16::<BigEndian>(0).unwrap(); // searchRange
        data.write_u16::<BigEndian>(0).unwrap(); // entrySelector
        data.write_u16::<BigEndian>(0).unwrap(); // rangeShift

        // endCode array
        for seg in &segments {
            data.write_u16::<BigEndian>(seg.1).unwrap();
        }
        // reserved pad
        data.write_u16::<BigEndian>(0).unwrap();
        // startCode array
        for seg in &segments {
            data.write_u16::<BigEndian>(seg.0).unwrap();
        }
        // idDelta array
        for seg in &segments {
            data.write_i16::<BigEndian>(seg.2).unwrap();
        }
        // idRangeOffset array (all zeros = use delta formula)
        for _ in &segments {
            data.write_u16::<BigEndian>(0).unwrap();
        }

        // Fill in format 4 length
        let fmt4_start = length_pos - 2; // format field
        let fmt4_len = data.len() - fmt4_start;
        let len_bytes = (fmt4_len as u16).to_be_bytes();
        data[length_pos] = len_bytes[0];
        data[length_pos + 1] = len_bytes[1];

        data
    }

    /// Build a minimal TrueType font with a cmap format 6 table.
    fn build_truetype_with_cmap_format6(first_code: u16, gids: &[u16]) -> Vec<u8> {
        let mut data = Vec::new();

        // sfnt header
        data.write_u32::<BigEndian>(0x00010000).unwrap();
        data.write_u16::<BigEndian>(1).unwrap();
        data.write_u16::<BigEndian>(16).unwrap();
        data.write_u16::<BigEndian>(0).unwrap();
        data.write_u16::<BigEndian>(0).unwrap();

        // table directory
        let cmap_offset: u32 = 12 + 16;
        data.write_u32::<BigEndian>(0x636D6170).unwrap();
        data.write_u32::<BigEndian>(0).unwrap();
        data.write_u32::<BigEndian>(cmap_offset).unwrap();
        data.write_u32::<BigEndian>(0).unwrap();

        // cmap header
        data.write_u16::<BigEndian>(0).unwrap();
        data.write_u16::<BigEndian>(1).unwrap();
        data.write_u16::<BigEndian>(3).unwrap(); // platform 3
        data.write_u16::<BigEndian>(1).unwrap(); // encoding 1
        data.write_u32::<BigEndian>(4 + 8).unwrap();

        // format 6
        data.write_u16::<BigEndian>(6).unwrap(); // format
        data.write_u16::<BigEndian>((10 + gids.len() * 2) as u16)
            .unwrap(); // length
        data.write_u16::<BigEndian>(0).unwrap(); // language
        data.write_u16::<BigEndian>(first_code).unwrap();
        data.write_u16::<BigEndian>(gids.len() as u16).unwrap();
        for &gid in gids {
            data.write_u16::<BigEndian>(gid).unwrap();
        }

        data
    }

    /// Build a minimal TrueType font with a cmap format 12 table.
    fn build_truetype_with_cmap_format12(groups: &[(u32, u32, u32)]) -> Vec<u8> {
        let mut data = Vec::new();

        // sfnt header
        data.write_u32::<BigEndian>(0x00010000).unwrap();
        data.write_u16::<BigEndian>(1).unwrap();
        data.write_u16::<BigEndian>(16).unwrap();
        data.write_u16::<BigEndian>(0).unwrap();
        data.write_u16::<BigEndian>(0).unwrap();

        // table directory
        let cmap_offset: u32 = 12 + 16;
        data.write_u32::<BigEndian>(0x636D6170).unwrap();
        data.write_u32::<BigEndian>(0).unwrap();
        data.write_u32::<BigEndian>(cmap_offset).unwrap();
        data.write_u32::<BigEndian>(0).unwrap();

        // cmap header
        data.write_u16::<BigEndian>(0).unwrap();
        data.write_u16::<BigEndian>(1).unwrap();
        data.write_u16::<BigEndian>(3).unwrap(); // platform 3
        data.write_u16::<BigEndian>(10).unwrap(); // encoding 10 (full repertoire)
        data.write_u32::<BigEndian>(4 + 8).unwrap();

        // format 12
        data.write_u16::<BigEndian>(12).unwrap(); // format
        data.write_u16::<BigEndian>(0).unwrap(); // reserved
        data.write_u32::<BigEndian>((16 + groups.len() * 12) as u32)
            .unwrap(); // length
        data.write_u32::<BigEndian>(0).unwrap(); // language
        data.write_u32::<BigEndian>(groups.len() as u32).unwrap();
        for &(start, end, start_gid) in groups {
            data.write_u32::<BigEndian>(start).unwrap();
            data.write_u32::<BigEndian>(end).unwrap();
            data.write_u32::<BigEndian>(start_gid).unwrap();
        }

        data
    }

    #[test]
    fn test_sfnt_header_parsing() {
        // Valid TrueType with empty cmap format 4
        let data = build_truetype_with_cmap_format4(&[]);
        let cmap = TrueTypeCMap::from_font_data(&data).unwrap();
        assert!(cmap.is_empty());
    }

    #[test]
    fn test_invalid_sfnt_version() {
        let mut data = vec![0u8; 100];
        // Invalid version bytes
        data[0] = 0xFF;
        data[1] = 0xFF;
        data[2] = 0xFF;
        data[3] = 0xFF;
        let result = TrueTypeCMap::from_font_data(&data);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("Invalid sfnt version"));
    }

    #[test]
    fn test_opentype_version_accepted() {
        // Build data with OTTO version
        let mut data = build_truetype_with_cmap_format4(&[(65, 1)]); // 'A' -> gid 1
                                                                     // Replace version with OTTO (0x4F54544F)
        data[0] = 0x4F;
        data[1] = 0x54;
        data[2] = 0x54;
        data[3] = 0x4F;
        let result = TrueTypeCMap::from_font_data(&data);
        assert!(result.is_ok());
    }

    #[test]
    fn test_apple_truetype_version_accepted() {
        let mut data = build_truetype_with_cmap_format4(&[(65, 1)]);
        // Replace version with "true" (0x74727565)
        data[0] = 0x74;
        data[1] = 0x72;
        data[2] = 0x75;
        data[3] = 0x65;
        let result = TrueTypeCMap::from_font_data(&data);
        assert!(result.is_ok());
    }

    #[test]
    fn test_no_cmap_table() {
        let mut data = Vec::new();
        // sfnt header with 1 table but NOT cmap
        data.write_u32::<BigEndian>(0x00010000).unwrap();
        data.write_u16::<BigEndian>(1).unwrap();
        data.write_u16::<BigEndian>(16).unwrap();
        data.write_u16::<BigEndian>(0).unwrap();
        data.write_u16::<BigEndian>(0).unwrap();
        // table record for 'head' (not 'cmap')
        data.write_u32::<BigEndian>(0x68656164).unwrap(); // 'head'
        data.write_u32::<BigEndian>(0).unwrap();
        data.write_u32::<BigEndian>(28).unwrap();
        data.write_u32::<BigEndian>(0).unwrap();

        let result = TrueTypeCMap::from_font_data(&data);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("cmap table not found"));
    }

    #[test]
    fn test_format4_basic_ascii() {
        // Map A(65)->1, B(66)->2, C(67)->3
        let data = build_truetype_with_cmap_format4(&[(65, 1), (66, 2), (67, 3)]);
        let cmap = TrueTypeCMap::from_font_data(&data).unwrap();
        assert_eq!(cmap.len(), 3);
        assert_eq!(cmap.get_unicode(1), Some('A'));
        assert_eq!(cmap.get_unicode(2), Some('B'));
        assert_eq!(cmap.get_unicode(3), Some('C'));
        assert_eq!(cmap.get_unicode(4), None);
    }

    #[test]
    fn test_format4_extended_unicode() {
        // Map some non-ASCII: é(233)->10, ñ(241)->11
        let data = build_truetype_with_cmap_format4(&[(233, 10), (241, 11)]);
        let cmap = TrueTypeCMap::from_font_data(&data).unwrap();
        assert_eq!(cmap.get_unicode(10), Some('é'));
        assert_eq!(cmap.get_unicode(11), Some('ñ'));
    }

    #[test]
    fn test_format6_basic() {
        // Format 6: first_code=65, gids=[1, 2, 3] -> maps A->gid1, B->gid2, C->gid3
        let data = build_truetype_with_cmap_format6(65, &[1, 2, 3]);
        let cmap = TrueTypeCMap::from_font_data(&data).unwrap();
        assert_eq!(cmap.len(), 3);
        assert_eq!(cmap.get_unicode(1), Some('A'));
        assert_eq!(cmap.get_unicode(2), Some('B'));
        assert_eq!(cmap.get_unicode(3), Some('C'));
    }

    #[test]
    fn test_format6_non_zero_first_code() {
        // Start at code 48 ('0') for digits
        let data = build_truetype_with_cmap_format6(48, &[10, 11, 12]);
        let cmap = TrueTypeCMap::from_font_data(&data).unwrap();
        assert_eq!(cmap.get_unicode(10), Some('0'));
        assert_eq!(cmap.get_unicode(11), Some('1'));
        assert_eq!(cmap.get_unicode(12), Some('2'));
    }

    #[test]
    fn test_format12_basic() {
        // One group: chars 65-67 -> gids 1-3
        let data = build_truetype_with_cmap_format12(&[(65, 67, 1)]);
        let cmap = TrueTypeCMap::from_font_data(&data).unwrap();
        assert_eq!(cmap.len(), 3);
        assert_eq!(cmap.get_unicode(1), Some('A'));
        assert_eq!(cmap.get_unicode(2), Some('B'));
        assert_eq!(cmap.get_unicode(3), Some('C'));
    }

    #[test]
    fn test_format12_multiple_groups() {
        let data = build_truetype_with_cmap_format12(&[
            (65, 67, 1),  // A-C -> gids 1-3
            (48, 50, 10), // 0-2 -> gids 10-12
        ]);
        let cmap = TrueTypeCMap::from_font_data(&data).unwrap();
        assert_eq!(cmap.len(), 6);
        assert_eq!(cmap.get_unicode(1), Some('A'));
        assert_eq!(cmap.get_unicode(10), Some('0'));
        assert_eq!(cmap.get_unicode(12), Some('2'));
    }

    #[test]
    fn test_get_unicode_missing() {
        let data = build_truetype_with_cmap_format4(&[(65, 1)]);
        let cmap = TrueTypeCMap::from_font_data(&data).unwrap();
        assert_eq!(cmap.get_unicode(999), None);
    }

    #[test]
    fn test_len_and_is_empty() {
        let data_empty = build_truetype_with_cmap_format4(&[]);
        let cmap_empty = TrueTypeCMap::from_font_data(&data_empty).unwrap();
        assert_eq!(cmap_empty.len(), 0);
        assert!(cmap_empty.is_empty());

        let data_one = build_truetype_with_cmap_format4(&[(65, 1)]);
        let cmap_one = TrueTypeCMap::from_font_data(&data_one).unwrap();
        assert_eq!(cmap_one.len(), 1);
        assert!(!cmap_one.is_empty());
    }

    #[test]
    fn test_unsupported_cmap_format() {
        let mut data = Vec::new();
        // sfnt header
        data.write_u32::<BigEndian>(0x00010000).unwrap();
        data.write_u16::<BigEndian>(1).unwrap();
        data.write_u16::<BigEndian>(16).unwrap();
        data.write_u16::<BigEndian>(0).unwrap();
        data.write_u16::<BigEndian>(0).unwrap();
        // cmap table directory entry
        let cmap_offset: u32 = 12 + 16;
        data.write_u32::<BigEndian>(0x636D6170).unwrap();
        data.write_u32::<BigEndian>(0).unwrap();
        data.write_u32::<BigEndian>(cmap_offset).unwrap();
        data.write_u32::<BigEndian>(0).unwrap();
        // cmap header
        data.write_u16::<BigEndian>(0).unwrap(); // version
        data.write_u16::<BigEndian>(1).unwrap(); // 1 subtable
        data.write_u16::<BigEndian>(3).unwrap(); // platform 3
        data.write_u16::<BigEndian>(1).unwrap(); // encoding 1
        data.write_u32::<BigEndian>(4 + 8).unwrap(); // subtable offset
                                                     // format 2 (unsupported)
        data.write_u16::<BigEndian>(2).unwrap();

        let result = TrueTypeCMap::from_font_data(&data);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("Unsupported cmap format"));
    }

    #[test]
    fn test_unsupported_cmap_version() {
        let mut data = Vec::new();
        // sfnt header
        data.write_u32::<BigEndian>(0x00010000).unwrap();
        data.write_u16::<BigEndian>(1).unwrap();
        data.write_u16::<BigEndian>(16).unwrap();
        data.write_u16::<BigEndian>(0).unwrap();
        data.write_u16::<BigEndian>(0).unwrap();
        // cmap table directory entry
        let cmap_offset: u32 = 12 + 16;
        data.write_u32::<BigEndian>(0x636D6170).unwrap();
        data.write_u32::<BigEndian>(0).unwrap();
        data.write_u32::<BigEndian>(cmap_offset).unwrap();
        data.write_u32::<BigEndian>(0).unwrap();
        // cmap header with invalid version
        data.write_u16::<BigEndian>(99).unwrap(); // version != 0

        let result = TrueTypeCMap::from_font_data(&data);
        assert!(result.is_err());
        assert!(result
            .unwrap_err()
            .contains("Unsupported cmap table version"));
    }

    #[test]
    fn test_no_suitable_subtable() {
        let mut data = Vec::new();
        // sfnt header
        data.write_u32::<BigEndian>(0x00010000).unwrap();
        data.write_u16::<BigEndian>(1).unwrap();
        data.write_u16::<BigEndian>(16).unwrap();
        data.write_u16::<BigEndian>(0).unwrap();
        data.write_u16::<BigEndian>(0).unwrap();
        // cmap table directory entry
        let cmap_offset: u32 = 12 + 16;
        data.write_u32::<BigEndian>(0x636D6170).unwrap();
        data.write_u32::<BigEndian>(0).unwrap();
        data.write_u32::<BigEndian>(cmap_offset).unwrap();
        data.write_u32::<BigEndian>(0).unwrap();
        // cmap header with 0 subtables
        data.write_u16::<BigEndian>(0).unwrap(); // version
        data.write_u16::<BigEndian>(0).unwrap(); // 0 subtables

        let result = TrueTypeCMap::from_font_data(&data);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("No suitable cmap subtable"));
    }

    #[test]
    fn test_truncated_data() {
        // Just a few bytes - not even a valid header
        let data = vec![0u8; 4];
        let result = TrueTypeCMap::from_font_data(&data);
        assert!(result.is_err());
    }

    #[test]
    fn test_clone_and_debug() {
        let data = build_truetype_with_cmap_format4(&[(65, 1)]);
        let cmap = TrueTypeCMap::from_font_data(&data).unwrap();
        let cloned = cmap.clone();
        assert_eq!(cloned.get_unicode(1), Some('A'));
        let debug = format!("{:?}", cmap);
        assert!(debug.contains("TrueTypeCMap"));
    }

    #[test]
    fn test_platform_priority_windows_full_over_bmp() {
        // Build a font with 2 subtables: platform 3/encoding 1 and 3/10
        // The 3/10 (full) should be preferred
        let mut data = Vec::new();

        // sfnt header
        data.write_u32::<BigEndian>(0x00010000).unwrap();
        data.write_u16::<BigEndian>(1).unwrap();
        data.write_u16::<BigEndian>(16).unwrap();
        data.write_u16::<BigEndian>(0).unwrap();
        data.write_u16::<BigEndian>(0).unwrap();

        let cmap_offset: u32 = 12 + 16;
        data.write_u32::<BigEndian>(0x636D6170).unwrap();
        data.write_u32::<BigEndian>(0).unwrap();
        data.write_u32::<BigEndian>(cmap_offset).unwrap();
        data.write_u32::<BigEndian>(0).unwrap();

        // cmap header with 2 subtables
        data.write_u16::<BigEndian>(0).unwrap(); // version
        data.write_u16::<BigEndian>(2).unwrap(); // 2 subtables

        // Both point to same subtable (format 12) for simplicity
        let subtable_off: u32 = 4 + 8 * 2; // cmap header + 2 records
                                           // Record 1: platform 3, encoding 1
        data.write_u16::<BigEndian>(3).unwrap();
        data.write_u16::<BigEndian>(1).unwrap();
        data.write_u32::<BigEndian>(subtable_off).unwrap();
        // Record 2: platform 3, encoding 10 (higher priority)
        data.write_u16::<BigEndian>(3).unwrap();
        data.write_u16::<BigEndian>(10).unwrap();
        data.write_u32::<BigEndian>(subtable_off).unwrap();

        // format 12 subtable: one group: A(65)->gid1
        data.write_u16::<BigEndian>(12).unwrap();
        data.write_u16::<BigEndian>(0).unwrap(); // reserved
        data.write_u32::<BigEndian>(28).unwrap(); // length
        data.write_u32::<BigEndian>(0).unwrap(); // language
        data.write_u32::<BigEndian>(1).unwrap(); // 1 group
        data.write_u32::<BigEndian>(65).unwrap();
        data.write_u32::<BigEndian>(65).unwrap();
        data.write_u32::<BigEndian>(1).unwrap();

        let cmap = TrueTypeCMap::from_font_data(&data).unwrap();
        assert_eq!(cmap.get_unicode(1), Some('A'));
    }
}
