//! PDF CMap module for character code to CID/Unicode mapping.
//!
//! Handles ToUnicode CMaps and CID CMaps for PDF text extraction and rendering.

use crate::fitz::error::{Error, Result};

// ============================================================================
// Types
// ============================================================================

/// Represents a single character mapping (bfchar) or range mapping (bfrange)
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CMapEntry {
    /// Single character mapping: src code -> dst bytes (Unicode)
    Single { src: u32, dst: Vec<u8> },
    /// Range mapping: src_lo..=src_hi -> dst_start (incremented per code)
    Range {
        src_lo: u32,
        src_hi: u32,
        dst_start: Vec<u8>,
    },
}

/// Valid code space range for variable-length character codes
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CodeSpaceRange {
    /// Low end of range (as bytes)
    pub lo: Vec<u8>,
    /// High end of range (as bytes)
    pub hi: Vec<u8>,
    /// Number of bytes per code in this range
    pub byte_len: usize,
}

/// Character map for PDF text encoding
#[derive(Debug, Clone)]
pub struct CMap {
    /// CMap name
    pub name: String,
    /// 0=horizontal, 1=vertical
    pub wmode: i32,
    /// Valid code space ranges
    pub code_space_ranges: Vec<CodeSpaceRange>,
    /// bfchar mappings: (code, unicode_bytes)
    pub bf_char_entries: Vec<(u32, Vec<u8>)>,
    /// bfrange mappings: (lo, hi, dst_start_bytes)
    pub bf_range_entries: Vec<(u32, u32, Vec<u8>)>,
    /// cidchar mappings: (code, cid)
    pub cid_char_entries: Vec<(u32, u16)>,
    /// cidrange mappings: (lo, hi, cid_start)
    pub cid_range_entries: Vec<(u32, u32, u16)>,
    /// Parent CMap name for cascading (from usecmap). Inherited mappings are not yet resolved.
    pub parent_cmap: Option<String>,
}

// ============================================================================
// Helper Functions
// ============================================================================

/// Parse a hex string like "004F" or "004F0066" into bytes.
/// Accepts both with and without angle brackets in the input.
/// Per PDF spec: odd-length hex strings are padded with a trailing '0'.
pub fn parse_hex_string(s: &str) -> Vec<u8> {
    let s = s.trim().trim_start_matches('<').trim_end_matches('>');
    let hex_chars: Vec<char> = s.chars().filter(|c| c.is_ascii_hexdigit()).collect();
    let hex_len = hex_chars.len();
    let padded_len = if hex_len % 2 == 1 {
        hex_len + 1
    } else {
        hex_len
    };
    let mut bytes = Vec::with_capacity(padded_len / 2);
    let mut i = 0;
    while i + 1 < hex_chars.len() {
        let high = hex_chars[i].to_digit(16).unwrap_or(0) as u8;
        let low = hex_chars[i + 1].to_digit(16).unwrap_or(0) as u8;
        bytes.push((high << 4) | low);
        i += 2;
    }
    if i < hex_chars.len() {
        let high = hex_chars[i].to_digit(16).unwrap_or(0) as u8;
        bytes.push((high << 4) | 0);
    }
    bytes
}

/// Convert byte slice to u32 code value (big-endian)
pub fn hex_to_u32(bytes: &[u8]) -> u32 {
    let mut code: u32 = 0;
    for &b in bytes {
        code = (code << 8) | (b as u32);
    }
    code
}

/// Decode UTF-16BE bytes to Unicode string. ToUnicode CMap dst values are UTF-16BE.
/// Odd-length trailing bytes are skipped (no panic on bounds).
fn decode_unicode_bytes(bytes: &[u8]) -> String {
    if bytes.len() <= 1 {
        return String::from_utf8_lossy(bytes).into_owned();
    }
    let mut result = String::new();
    let mut code_units = Vec::with_capacity(bytes.len() / 2);
    let mut i = 0;
    while i + 1 < bytes.len() {
        let high = bytes.get(i).copied().unwrap_or(0);
        let low = bytes.get(i + 1).copied().unwrap_or(0);
        let cu = (u16::from(high) << 8) | u16::from(low);
        code_units.push(cu);
        i += 2;
    }
    let mut i = 0;
    while i < code_units.len() {
        let cu = match code_units.get(i) {
            Some(&c) => c,
            None => break,
        };
        if cu >= 0xD800 && cu <= 0xDBFF {
            if let Some(&lo) = code_units.get(i + 1) {
                if lo >= 0xDC00 && lo <= 0xDFFF {
                    let high_val = (cu as u32) - 0xD800;
                    let low_val = (lo as u32) - 0xDC00;
                    let code_point = high_val
                        .checked_shl(10)
                        .and_then(|h| h.checked_add(low_val))
                        .and_then(|v| v.checked_add(0x10000))
                        .and_then(char::from_u32);
                    match code_point {
                        Some(c) => result.push(c),
                        None => result.push('\u{FFFD}'),
                    }
                    i += 1;
                } else {
                    result.push('\u{FFFD}');
                }
            } else {
                result.push('\u{FFFD}');
            }
        } else if cu >= 0xDC00 && cu <= 0xDFFF {
            result.push('\u{FFFD}');
        } else if let Some(c) = char::from_u32(cu as u32) {
            result.push(c);
        } else {
            result.push('\u{FFFD}');
        }
        i += 1;
    }
    result
}

// ============================================================================
// CMap Implementation
// ============================================================================

impl CMap {
    /// Create a new empty CMap with the given name
    pub fn new(name: &str) -> Self {
        Self {
            name: name.to_string(),
            wmode: 0,
            code_space_ranges: Vec::new(),
            bf_char_entries: Vec::new(),
            bf_range_entries: Vec::new(),
            cid_char_entries: Vec::new(),
            cid_range_entries: Vec::new(),
            parent_cmap: None,
        }
    }

    /// Parse a CMap from PostScript-like CMap program data
    pub fn parse(data: &[u8]) -> Result<Self> {
        let text = std::str::from_utf8(data)
            .map_err(|e| Error::syntax(format!("Invalid UTF-8 in CMap: {}", e)))?;

        let mut cmap = Self::new("");
        let mut tokens = CMapTokenizer::new(text);
        let mut token = tokens.next()?;

        while token.is_some() {
            let t = token
                .take()
                .ok_or_else(|| Error::syntax("Unexpected end of CMap"))?;

            match t {
                CMapToken::Name(name) => {
                    let next = tokens.next()?;
                    match next {
                        Some(CMapToken::Name(val)) => {
                            let def_next = tokens.next()?;
                            if matches!(def_next, Some(CMapToken::Def)) {
                                match name.as_str() {
                                    "CMapName" => cmap.name = val,
                                    "WMode" => {
                                        cmap.wmode = val.parse().unwrap_or(0);
                                    }
                                    _ => {}
                                }
                            }
                            token = def_next;
                        }
                        Some(CMapToken::Int(n)) => {
                            let def_next = tokens.next()?;
                            if matches!(def_next, Some(CMapToken::Def)) {
                                if name == "WMode" {
                                    cmap.wmode = n as i32;
                                }
                            }
                            token = def_next;
                        }
                        other => token = other,
                    }
                }
                CMapToken::Int(n) => {
                    let next = tokens.next()?;
                    token = match &next {
                        Some(CMapToken::Keyword(kw)) if n >= 0 => match *kw {
                            "begincodespacerange" => parse_code_space_range_with_count(
                                &mut tokens,
                                &mut cmap,
                                n as usize,
                            )?,
                            "beginbfchar" => {
                                parse_bfchar_with_count(&mut tokens, &mut cmap, n as usize)?
                            }
                            "beginbfrange" => {
                                parse_bfrange_with_count(&mut tokens, &mut cmap, n as usize)?
                            }
                            "begincidchar" => {
                                parse_cidchar_with_count(&mut tokens, &mut cmap, n as usize)?
                            }
                            "begincidrange" => {
                                parse_cidrange_with_count(&mut tokens, &mut cmap, n as usize)?
                            }
                            _ => next.clone(),
                        },
                        _ => next,
                    };
                    if token.is_some() {
                        continue;
                    }
                }
                CMapToken::Keyword("usecmap") => {
                    let name_tok = tokens.next()?;
                    if let Some(CMapToken::Name(usecmap_name)) = name_tok {
                        cmap.parent_cmap = Some(usecmap_name);
                    }
                    token = tokens.next()?;
                }
                _ => token = tokens.next()?,
            }
        }

        if cmap.name.is_empty() {
            cmap.name = "Unknown".to_string();
        }

        Ok(cmap)
    }

    /// Map a character code to a Unicode string
    pub fn lookup_unicode(&self, code: u32) -> Option<String> {
        for (src, dst) in &self.bf_char_entries {
            if *src == code {
                return Some(decode_unicode_bytes(dst));
            }
        }
        self.lookup_unicode_bfrange(code)
    }

    /// Map a character code to a CID
    pub fn lookup_cid(&self, code: u32) -> Option<u16> {
        for (src, cid) in &self.cid_char_entries {
            if *src == code {
                return Some(*cid);
            }
        }

        for (lo, hi, cid_start) in &self.cid_range_entries {
            if code >= *lo && code <= *hi {
                let offset_u32 = code - lo;
                let Some(offset) = u16::try_from(offset_u32).ok() else {
                    continue;
                };
                if let Some(cid) = cid_start.checked_add(offset) {
                    return Some(cid);
                }
            }
        }

        None
    }

    /// Decode one character code from byte data at offset
    pub fn decode_code(&self, data: &[u8], offset: usize) -> Option<(u32, usize)> {
        for range in &self.code_space_ranges {
            let byte_len = range.byte_len;
            if offset + byte_len > data.len() {
                continue;
            }
            let slice = &data[offset..offset + byte_len];
            let code = hex_to_u32(slice);
            let lo = hex_to_u32(&range.lo);
            let hi = hex_to_u32(&range.hi);
            if code >= lo && code <= hi {
                return Some((code, byte_len));
            }
        }
        None
    }

    /// Check if vertical writing mode
    pub fn is_vertical(&self) -> bool {
        self.wmode == 1
    }
}

// ============================================================================
// CMap Parser - Tokenizer
// ============================================================================

#[derive(Debug, Clone)]
enum CMapToken {
    Name(String),
    Int(i64),
    HexString(String),
    Keyword(&'static str),
    Def,
}

struct CMapTokenizer<'a> {
    input: &'a str,
    pos: usize,
}

impl<'a> CMapTokenizer<'a> {
    fn new(input: &'a str) -> Self {
        Self { input, pos: 0 }
    }

    fn skip_whitespace_and_comments(&mut self) {
        let bytes = self.input.as_bytes();
        while self.pos < bytes.len() {
            let b = bytes[self.pos];
            if b == b'%' {
                while self.pos < bytes.len() && bytes[self.pos] != b'\n' {
                    self.pos += 1;
                }
                continue;
            }
            if b.is_ascii_whitespace() {
                self.pos += 1;
            } else {
                break;
            }
        }
    }

    fn next(&mut self) -> Result<Option<CMapToken>> {
        self.skip_whitespace_and_comments();
        if self.pos >= self.input.len() {
            return Ok(None);
        }

        let bytes = self.input.as_bytes();
        let start = self.pos;

        if bytes[self.pos] == b'<' {
            self.pos += 1;
            if self.pos < bytes.len() && bytes[self.pos] == b'<' {
                self.pos += 1;
                let mut depth: i32 = 1;
                while self.pos + 1 < bytes.len() && depth > 0 {
                    if bytes[self.pos] == b'<' && bytes[self.pos + 1] == b'<' {
                        depth += 1;
                        self.pos += 2;
                    } else if bytes[self.pos] == b'>' && bytes[self.pos + 1] == b'>' {
                        depth -= 1;
                        self.pos += 2;
                        if depth == 0 {
                            break;
                        }
                    } else {
                        self.pos += 1;
                    }
                }
                if depth != 0 {
                    return Err(Error::syntax(
                        "Malformed CMap: unclosed dictionary (<< without matching >>)",
                    ));
                }
                return self.next();
            }
            while self.pos < bytes.len() && bytes[self.pos] != b'>' {
                self.pos += 1;
            }
            if self.pos < bytes.len() {
                self.pos += 1;
            }
            let s = &self.input[start..self.pos];
            return Ok(Some(CMapToken::HexString(s.to_string())));
        }

        if bytes[self.pos] == b'/' {
            self.pos += 1;
            while self.pos < bytes.len()
                && !bytes[self.pos].is_ascii_whitespace()
                && bytes[self.pos] != b'<'
                && bytes[self.pos] != b'['
                && bytes[self.pos] != b']'
            {
                self.pos += 1;
            }
            let name = self.input[start + 1..self.pos].to_string();
            return Ok(Some(CMapToken::Name(name)));
        }

        if bytes[self.pos].is_ascii_digit()
            || (bytes[self.pos] == b'-'
                && self.pos + 1 < bytes.len()
                && bytes[self.pos + 1].is_ascii_digit())
        {
            let neg = bytes[self.pos] == b'-';
            if neg {
                self.pos += 1;
            }
            while self.pos < bytes.len() && bytes[self.pos].is_ascii_digit() {
                self.pos += 1;
            }
            let s = &self.input[start..self.pos];
            if let Ok(n) = s.parse::<i64>() {
                return Ok(Some(CMapToken::Int(n)));
            }
        }

        let word_start = self.pos;
        while self.pos < bytes.len()
            && !bytes[self.pos].is_ascii_whitespace()
            && bytes[self.pos] != b'<'
            && bytes[self.pos] != b'/'
            && bytes[self.pos] != b'%'
        {
            self.pos += 1;
        }
        let word = &self.input[word_start..self.pos];

        let kw = match word {
            "def" => CMapToken::Def,
            "begincodespacerange" => CMapToken::Keyword("begincodespacerange"),
            "endcodespacerange" => CMapToken::Keyword("endcodespacerange"),
            "beginbfchar" => CMapToken::Keyword("beginbfchar"),
            "endbfchar" => CMapToken::Keyword("endbfchar"),
            "beginbfrange" => CMapToken::Keyword("beginbfrange"),
            "endbfrange" => CMapToken::Keyword("endbfrange"),
            "begincidchar" => CMapToken::Keyword("begincidchar"),
            "endcidchar" => CMapToken::Keyword("endcidchar"),
            "begincidrange" => CMapToken::Keyword("begincidrange"),
            "endcidrange" => CMapToken::Keyword("endcidrange"),
            "usecmap" => CMapToken::Keyword("usecmap"),
            _ => CMapToken::Name(word.to_string()),
        };
        Ok(Some(kw))
    }
}

// ============================================================================
// CMap Parser - Section Parsers
// ============================================================================

fn parse_code_space_range_with_count<'a>(
    tokens: &mut CMapTokenizer<'a>,
    cmap: &mut CMap,
    count: usize,
) -> Result<Option<CMapToken>> {
    for _ in 0..count {
        let lo_tok = tokens
            .next()?
            .ok_or_else(|| Error::syntax("Expected codespace lo"))?;
        let hi_tok = tokens
            .next()?
            .ok_or_else(|| Error::syntax("Expected codespace hi"))?;

        let lo = match lo_tok {
            CMapToken::HexString(s) => parse_hex_string(&s),
            _ => return Err(Error::syntax("Expected hex string for codespace lo")),
        };
        let hi = match hi_tok {
            CMapToken::HexString(s) => parse_hex_string(&s),
            _ => return Err(Error::syntax("Expected hex string for codespace hi")),
        };

        if lo.len() != hi.len() {
            return Err(Error::syntax("Codespace lo and hi must have same length"));
        }

        cmap.code_space_ranges.push(CodeSpaceRange {
            lo: lo.clone(),
            hi: hi.clone(),
            byte_len: lo.len(),
        });
    }

    let end = tokens.next()?;
    if !matches!(end, Some(CMapToken::Keyword("endcodespacerange"))) {
        return Err(Error::syntax("Expected endcodespacerange"));
    }
    Ok(tokens.next()?)
}

fn parse_bfchar_with_count<'a>(
    tokens: &mut CMapTokenizer<'a>,
    cmap: &mut CMap,
    count: usize,
) -> Result<Option<CMapToken>> {
    for _ in 0..count {
        let src_tok = tokens
            .next()?
            .ok_or_else(|| Error::syntax("Expected bfchar src"))?;
        let dst_tok = tokens
            .next()?
            .ok_or_else(|| Error::syntax("Expected bfchar dst"))?;

        let src = match src_tok {
            CMapToken::HexString(s) => hex_to_u32(&parse_hex_string(&s)),
            _ => return Err(Error::syntax("Expected hex string for bfchar src")),
        };
        let dst = match dst_tok {
            CMapToken::HexString(s) => parse_hex_string(&s),
            _ => return Err(Error::syntax("Expected hex string for bfchar dst")),
        };

        cmap.bf_char_entries.push((src, dst));
    }

    let end = tokens.next()?;
    if !matches!(end, Some(CMapToken::Keyword("endbfchar"))) {
        return Err(Error::syntax("Expected endbfchar"));
    }
    Ok(tokens.next()?)
}

fn parse_bfrange_with_count<'a>(
    tokens: &mut CMapTokenizer<'a>,
    cmap: &mut CMap,
    count: usize,
) -> Result<Option<CMapToken>> {
    for _ in 0..count {
        let lo_tok = tokens
            .next()?
            .ok_or_else(|| Error::syntax("Expected bfrange lo"))?;
        let hi_tok = tokens
            .next()?
            .ok_or_else(|| Error::syntax("Expected bfrange hi"))?;
        let dst_tok = tokens
            .next()?
            .ok_or_else(|| Error::syntax("Expected bfrange dst"))?;

        let lo = match lo_tok {
            CMapToken::HexString(s) => hex_to_u32(&parse_hex_string(&s)),
            _ => return Err(Error::syntax("Expected hex string for bfrange lo")),
        };
        let hi = match hi_tok {
            CMapToken::HexString(s) => hex_to_u32(&parse_hex_string(&s)),
            _ => return Err(Error::syntax("Expected hex string for bfrange hi")),
        };
        let dst = match dst_tok {
            CMapToken::HexString(s) => parse_hex_string(&s),
            _ => return Err(Error::syntax("Expected hex string for bfrange dst")),
        };

        cmap.bf_range_entries.push((lo, hi, dst));
    }

    let end = tokens.next()?;
    if !matches!(end, Some(CMapToken::Keyword("endbfrange"))) {
        return Err(Error::syntax("Expected endbfrange"));
    }
    Ok(tokens.next()?)
}

fn parse_cidchar_with_count<'a>(
    tokens: &mut CMapTokenizer<'a>,
    cmap: &mut CMap,
    count: usize,
) -> Result<Option<CMapToken>> {
    for _ in 0..count {
        let src_tok = tokens
            .next()?
            .ok_or_else(|| Error::syntax("Expected cidchar src"))?;
        let cid_tok = tokens
            .next()?
            .ok_or_else(|| Error::syntax("Expected cidchar cid"))?;

        let src = match src_tok {
            CMapToken::HexString(s) => hex_to_u32(&parse_hex_string(&s)),
            _ => return Err(Error::syntax("Expected hex string for cidchar src")),
        };
        let cid = match cid_tok {
            CMapToken::Int(n) if n >= 0 && n <= 0xFFFF => n as u16,
            CMapToken::HexString(s) => hex_to_u32(&parse_hex_string(&s)) as u16,
            _ => return Err(Error::syntax("Expected integer or hex for cidchar cid")),
        };

        cmap.cid_char_entries.push((src, cid));
    }

    let end = tokens.next()?;
    if !matches!(end, Some(CMapToken::Keyword("endcidchar"))) {
        return Err(Error::syntax("Expected endcidchar"));
    }
    Ok(tokens.next()?)
}

fn parse_cidrange_with_count<'a>(
    tokens: &mut CMapTokenizer<'a>,
    cmap: &mut CMap,
    count: usize,
) -> Result<Option<CMapToken>> {
    for _ in 0..count {
        let lo_tok = tokens
            .next()?
            .ok_or_else(|| Error::syntax("Expected cidrange lo"))?;
        let hi_tok = tokens
            .next()?
            .ok_or_else(|| Error::syntax("Expected cidrange hi"))?;
        let cid_tok = tokens
            .next()?
            .ok_or_else(|| Error::syntax("Expected cidrange cid"))?;

        let lo = match lo_tok {
            CMapToken::HexString(s) => hex_to_u32(&parse_hex_string(&s)),
            _ => return Err(Error::syntax("Expected hex string for cidrange lo")),
        };
        let hi = match hi_tok {
            CMapToken::HexString(s) => hex_to_u32(&parse_hex_string(&s)),
            _ => return Err(Error::syntax("Expected hex string for cidrange hi")),
        };
        let cid = match cid_tok {
            CMapToken::Int(n) if n >= 0 && n <= 0xFFFF => n as u16,
            CMapToken::HexString(s) => u16::try_from(hex_to_u32(&parse_hex_string(&s)))
                .map_err(|_| Error::syntax("CID value out of range for cidrange"))?,
            _ => return Err(Error::syntax("Expected integer or hex for cidrange cid")),
        };

        let range_len = hi.checked_sub(lo).and_then(|d| d.checked_add(1));
        let Some(range_len) = range_len else {
            return Err(Error::syntax("cidrange: hi must be >= lo"));
        };
        let Ok(range_len_u16) = u16::try_from(range_len) else {
            return Err(Error::syntax("cidrange: range length exceeds u16::MAX"));
        };
        let max_offset = range_len_u16.checked_sub(1).unwrap_or(0);
        if cid.checked_add(max_offset).is_none() {
            return Err(Error::syntax(
                "cidrange: cid_start + (hi - lo) would overflow u16",
            ));
        }

        cmap.cid_range_entries.push((lo, hi, cid));
    }

    let end = tokens.next()?;
    if !matches!(end, Some(CMapToken::Keyword("endcidrange"))) {
        return Err(Error::syntax("Expected endcidrange"));
    }
    Ok(tokens.next()?)
}

// ============================================================================
// Predefined CMaps
// ============================================================================

/// Return predefined CMap by name. Only Identity-H and Identity-V are built-in.
pub fn get_predefined_cmap(name: &str) -> Option<CMap> {
    match name {
        "Identity-H" => {
            let mut cmap = CMap::new("Identity-H");
            cmap.wmode = 0;
            cmap.code_space_ranges.push(CodeSpaceRange {
                lo: vec![0x00, 0x00],
                hi: vec![0xFF, 0xFF],
                byte_len: 2,
            });
            Some(cmap)
        }
        "Identity-V" => {
            let mut cmap = CMap::new("Identity-V");
            cmap.wmode = 1;
            cmap.code_space_ranges.push(CodeSpaceRange {
                lo: vec![0x00, 0x00],
                hi: vec![0xFF, 0xFF],
                byte_len: 2,
            });
            Some(cmap)
        }
        _ => None,
    }
}

impl CMap {
    fn lookup_unicode_bfrange(&self, code: u32) -> Option<String> {
        for (lo, hi, dst_start) in &self.bf_range_entries {
            if code >= *lo && code <= *hi {
                let offset = code - lo;
                let mut result = dst_start.clone();
                if result.len() == 2 {
                    let base = ((result[0] as u32) << 8 | (result[1] as u32)) + offset;
                    result[0] = (base >> 8) as u8;
                    result[1] = (base & 0xFF) as u8;
                } else if result.len() == 4 {
                    let base = ((result[0] as u32) << 24
                        | (result[1] as u32) << 16
                        | (result[2] as u32) << 8
                        | (result[3] as u32))
                        + offset;
                    result[0] = (base >> 24) as u8;
                    result[1] = (base >> 16) as u8;
                    result[2] = (base >> 8) as u8;
                    result[3] = (base & 0xFF) as u8;
                }
                return Some(decode_unicode_bytes(&result));
            }
        }
        None
    }
}

// ============================================================================
// Tests
// ============================================================================

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

    const SAMPLE_TOUNICODE_CMAP: &str = r#"
/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def
/CMapName /Adobe-Identity-UCS def
/CMapType 2 def
/WMode 0 def
1 begincodespacerange
<0000> <FFFF>
endcodespacerange
3 beginbfchar
<0048> <0048>
<0065> <0065>
<006C> <006C>
endbfchar
2 beginbfrange
<0041> <005A> <0041>
<0061> <007A> <0061>
endbfrange
"#;

    #[test]
    fn test_parse_hex_string() {
        assert_eq!(parse_hex_string("004F"), vec![0x00, 0x4F]);
        assert_eq!(parse_hex_string("<004F>"), vec![0x00, 0x4F]);
        assert_eq!(parse_hex_string("004F0066"), vec![0x00, 0x4F, 0x00, 0x66]);
        assert_eq!(parse_hex_string("41"), vec![0x41]);
        assert_eq!(parse_hex_string(""), Vec::<u8>::new());
        assert_eq!(parse_hex_string("4"), vec![0x40]);
        assert_eq!(parse_hex_string("A"), vec![0xA0]);
    }

    #[test]
    fn test_hex_to_u32() {
        assert_eq!(hex_to_u32(&[0x00, 0x4F]), 0x004F);
        assert_eq!(hex_to_u32(&[0x41]), 0x41);
        assert_eq!(hex_to_u32(&[0xFF, 0xFF]), 0xFFFF);
        assert_eq!(hex_to_u32(&[]), 0);
    }

    #[test]
    fn test_parse_complete_tounicode_cmap() {
        let cmap = CMap::parse(SAMPLE_TOUNICODE_CMAP.as_bytes()).unwrap();
        assert_eq!(cmap.name, "Adobe-Identity-UCS");
        assert_eq!(cmap.wmode, 0);
        assert!(!cmap.is_vertical());
        assert_eq!(cmap.code_space_ranges.len(), 1);
        assert_eq!(cmap.code_space_ranges[0].byte_len, 2);
        assert_eq!(cmap.bf_char_entries.len(), 3);
        assert_eq!(cmap.bf_range_entries.len(), 2);
    }

    #[test]
    fn test_lookup_unicode_single_chars() {
        let cmap = CMap::parse(SAMPLE_TOUNICODE_CMAP.as_bytes()).unwrap();
        assert_eq!(cmap.lookup_unicode(0x0048), Some("H".to_string()));
        assert_eq!(cmap.lookup_unicode(0x0065), Some("e".to_string()));
        assert_eq!(cmap.lookup_unicode(0x006C), Some("l".to_string()));
    }

    #[test]
    fn test_lookup_unicode_ranges() {
        let cmap = CMap::parse(SAMPLE_TOUNICODE_CMAP.as_bytes()).unwrap();
        assert_eq!(cmap.lookup_unicode(0x0041), Some("A".to_string()));
        assert_eq!(cmap.lookup_unicode(0x0042), Some("B".to_string()));
        assert_eq!(cmap.lookup_unicode(0x005A), Some("Z".to_string()));
        assert_eq!(cmap.lookup_unicode(0x0061), Some("a".to_string()));
        assert_eq!(cmap.lookup_unicode(0x007A), Some("z".to_string()));
    }

    #[test]
    fn test_lookup_unicode_not_found() {
        let cmap = CMap::parse(SAMPLE_TOUNICODE_CMAP.as_bytes()).unwrap();
        assert_eq!(cmap.lookup_unicode(0x0000), None);
        assert_eq!(cmap.lookup_unicode(0x00FF), None);
    }

    const SAMPLE_CID_CMAP: &str = r#"
/CMapName /Test-CID-CMap def
/WMode 1 def
1 begincodespacerange
<00> <FF>
endcodespacerange
2 begincidchar
<41> 100
<42> 101
endcidchar
1 begincidrange
<80> <82> 200
endcidrange
"#;

    #[test]
    fn test_lookup_cid_single() {
        let cmap = CMap::parse(SAMPLE_CID_CMAP.as_bytes()).unwrap();
        assert_eq!(cmap.lookup_cid(0x41), Some(100));
        assert_eq!(cmap.lookup_cid(0x42), Some(101));
    }

    #[test]
    fn test_lookup_cid_range() {
        let cmap = CMap::parse(SAMPLE_CID_CMAP.as_bytes()).unwrap();
        assert_eq!(cmap.lookup_cid(0x80), Some(200));
        assert_eq!(cmap.lookup_cid(0x81), Some(201));
        assert_eq!(cmap.lookup_cid(0x82), Some(202));
    }

    #[test]
    fn test_lookup_cid_not_found() {
        let cmap = CMap::parse(SAMPLE_CID_CMAP.as_bytes()).unwrap();
        assert_eq!(cmap.lookup_cid(0x00), None);
        assert_eq!(cmap.lookup_cid(0xFF), None);
    }

    #[test]
    fn test_decode_code() {
        let cmap = CMap::parse(SAMPLE_TOUNICODE_CMAP.as_bytes()).unwrap();
        let data = [0x00u8, 0x48, 0x00, 0x65];
        assert_eq!(cmap.decode_code(&data, 0), Some((0x0048, 2)));
        assert_eq!(cmap.decode_code(&data, 2), Some((0x0065, 2)));
    }

    #[test]
    fn test_decode_code_insufficient_data() {
        let cmap = CMap::parse(SAMPLE_TOUNICODE_CMAP.as_bytes()).unwrap();
        let data = [0x00u8];
        assert_eq!(cmap.decode_code(&data, 0), None);
    }

    #[test]
    fn test_identity_h_predefined() {
        let cmap = get_predefined_cmap("Identity-H").unwrap();
        assert_eq!(cmap.name, "Identity-H");
        assert_eq!(cmap.wmode, 0);
        assert!(!cmap.is_vertical());
        assert_eq!(cmap.code_space_ranges.len(), 1);
        assert_eq!(cmap.code_space_ranges[0].byte_len, 2);
    }

    #[test]
    fn test_identity_v_predefined() {
        let cmap = get_predefined_cmap("Identity-V").unwrap();
        assert_eq!(cmap.name, "Identity-V");
        assert_eq!(cmap.wmode, 1);
        assert!(cmap.is_vertical());
    }

    #[test]
    fn test_predefined_cmap_unknown() {
        assert!(get_predefined_cmap("Unknown-CMap").is_none());
        assert!(get_predefined_cmap("").is_none());
    }

    #[test]
    fn test_vertical_writing_mode() {
        let cmap = CMap::parse(SAMPLE_CID_CMAP.as_bytes()).unwrap();
        assert!(cmap.is_vertical());
    }

    #[test]
    fn test_cmap_new() {
        let cmap = CMap::new("Test-CMap");
        assert_eq!(cmap.name, "Test-CMap");
        assert_eq!(cmap.wmode, 0);
        assert!(cmap.bf_char_entries.is_empty());
        assert!(cmap.bf_range_entries.is_empty());
        assert!(cmap.cid_char_entries.is_empty());
        assert!(cmap.cid_range_entries.is_empty());
        assert!(cmap.code_space_ranges.is_empty());
    }

    #[test]
    fn test_empty_cmap_handling() {
        let cmap = CMap::new("Empty");
        assert_eq!(cmap.lookup_unicode(0x41), None);
        assert_eq!(cmap.lookup_cid(0x41), None);
        assert_eq!(cmap.decode_code(&[0x41], 0), None);
    }

    #[test]
    fn test_parse_invalid_utf8() {
        let data = [0xFF, 0xFE, 0xFD];
        assert!(CMap::parse(&data).is_err());
    }

    #[test]
    fn test_parse_malformed_missing_endbfchar() {
        let data = b"2 beginbfchar\n<0041> <0041>\n<0042> <0042>";
        assert!(CMap::parse(data).is_err());
    }

    #[test]
    fn test_parse_malformed_missing_dst() {
        let data = b"1 beginbfchar\n<0041>\nendbfchar";
        assert!(CMap::parse(data).is_err());
    }

    #[test]
    fn test_parse_malformed_unclosed_dict() {
        let data = b"/CMapName /Test def << /Registry (Adobe)";
        assert!(CMap::parse(data).is_err());
    }

    #[test]
    fn test_cidrange_single_element() {
        let data = r#"
/CMapName /Single-CID def
1 begincodespacerange
<41> <41>
endcodespacerange
1 begincidrange
<41> <41> 100
endcidrange
"#;
        let cmap = CMap::parse(data.as_bytes()).unwrap();
        assert_eq!(cmap.lookup_cid(0x41), Some(100));
    }

    #[test]
    fn test_usecmap_parsing() {
        let data = r#"
/CMapName /Child-CMap def
usecmap /Parent-CMap
"#;
        let cmap = CMap::parse(data.as_bytes()).unwrap();
        assert_eq!(cmap.parent_cmap, Some("Parent-CMap".to_string()));
    }

    #[test]
    fn test_cmap_entry_single() {
        let entry = CMapEntry::Single {
            src: 0x41,
            dst: vec![0x41],
        };
        assert!(matches!(entry, CMapEntry::Single { src: 0x41, .. }));
    }

    #[test]
    fn test_cmap_entry_range() {
        let entry = CMapEntry::Range {
            src_lo: 0x41,
            src_hi: 0x5A,
            dst_start: vec![0x00, 0x41],
        };
        assert!(matches!(
            entry,
            CMapEntry::Range {
                src_lo: 0x41,
                src_hi: 0x5A,
                ..
            }
        ));
    }

    #[test]
    fn test_code_space_range() {
        let range = CodeSpaceRange {
            lo: vec![0x00, 0x00],
            hi: vec![0xFF, 0xFF],
            byte_len: 2,
        };
        assert_eq!(range.byte_len, 2);
        assert_eq!(hex_to_u32(&range.lo), 0);
        assert_eq!(hex_to_u32(&range.hi), 0xFFFF);
    }
}
