//! PDF Font FFI Module
//!
//! Provides PDF-specific font handling including font descriptors,
//! CID/GID/Unicode mapping, metrics, and font embedding.

use crate::ffi::output::OUTPUTS;
use crate::ffi::pdf_object::types::{PDF_OBJECTS, PdfObj, PdfObjType};
use crate::ffi::{Handle, HandleStore};
use std::collections::HashMap;
use std::ffi::{CStr, CString, c_char};
use std::ptr;
use std::sync::LazyLock;

// ============================================================================
// Type Aliases
// ============================================================================

type ContextHandle = Handle;
type DocumentHandle = Handle;
type PdfObjHandle = Handle;
type FontHandle = Handle;
type CMapHandle = Handle;
type BufferHandle = Handle;
type DeviceHandle = Handle;
type OutputHandle = Handle;

// ============================================================================
// Font Descriptor Flags
// ============================================================================

/// Font is fixed-pitch (monospace)
pub const PDF_FD_FIXED_PITCH: i32 = 1 << 0;
/// Font has serifs
pub const PDF_FD_SERIF: i32 = 1 << 1;
/// Font uses symbolic character set
pub const PDF_FD_SYMBOLIC: i32 = 1 << 2;
/// Font is script/cursive
pub const PDF_FD_SCRIPT: i32 = 1 << 3;
/// Font uses standard Latin character set
pub const PDF_FD_NONSYMBOLIC: i32 = 1 << 5;
/// Font is italic
pub const PDF_FD_ITALIC: i32 = 1 << 6;
/// Font uses all capital letters
pub const PDF_FD_ALL_CAP: i32 = 1 << 16;
/// Font uses small capitals
pub const PDF_FD_SMALL_CAP: i32 = 1 << 17;
/// Font always renders bold
pub const PDF_FD_FORCE_BOLD: i32 = 1 << 18;

// ============================================================================
// Font Encoding Constants
// ============================================================================

/// Standard encoding
pub const PDF_ENCODING_STANDARD: i32 = 0;
/// MacRoman encoding
pub const PDF_ENCODING_MAC_ROMAN: i32 = 1;
/// WinAnsi encoding
pub const PDF_ENCODING_WIN_ANSI: i32 = 2;
/// MacExpert encoding
pub const PDF_ENCODING_MAC_EXPERT: i32 = 3;
/// Symbol encoding
pub const PDF_ENCODING_SYMBOL: i32 = 4;
/// ZapfDingbats encoding
pub const PDF_ENCODING_ZAPF_DINGBATS: i32 = 5;

// ============================================================================
// CJK Script Constants
// ============================================================================

/// Adobe-CNS1 (Traditional Chinese)
pub const PDF_CJK_CNS1: i32 = 0;
/// Adobe-GB1 (Simplified Chinese)
pub const PDF_CJK_GB1: i32 = 1;
/// Adobe-Japan1 (Japanese)
pub const PDF_CJK_JAPAN1: i32 = 2;
/// Adobe-Korea1 (Korean)
pub const PDF_CJK_KOREA1: i32 = 3;

// ============================================================================
// Horizontal Metrics
// ============================================================================

/// Horizontal metrics entry
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct HorizontalMetrics {
    /// Low CID
    pub lo: u16,
    /// High CID
    pub hi: u16,
    /// Width
    pub w: i32,
}

// ============================================================================
// Vertical Metrics
// ============================================================================

/// Vertical metrics entry
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct VerticalMetrics {
    /// Low CID
    pub lo: u16,
    /// High CID
    pub hi: u16,
    /// X displacement
    pub x: i16,
    /// Y displacement
    pub y: i16,
    /// Width
    pub w: i16,
}

// ============================================================================
// Font Descriptor
// ============================================================================

/// PDF Font Descriptor
#[derive(Debug, Clone)]
pub struct FontDesc {
    /// Reference count
    pub refs: i32,
    /// Memory size
    pub size: usize,

    /// Underlying fz_font handle (if any)
    pub font: Option<FontHandle>,
    /// Font name
    pub name: String,

    // FontDescriptor fields
    /// Font flags
    pub flags: i32,
    /// Italic angle in degrees
    pub italic_angle: f32,
    /// Ascender
    pub ascent: f32,
    /// Descender
    pub descent: f32,
    /// Cap height
    pub cap_height: f32,
    /// X-height
    pub x_height: f32,
    /// Missing glyph width
    pub missing_width: f32,

    // Encoding
    /// Encoding CMap
    pub encoding: Option<CMapHandle>,
    /// ToTTF CMap
    pub to_ttf_cmap: Option<CMapHandle>,
    /// CID to GID mapping
    pub cid_to_gid: Vec<u16>,

    // ToUnicode
    /// ToUnicode CMap
    pub to_unicode: Option<CMapHandle>,
    /// CID to UCS mapping
    pub cid_to_ucs: Vec<u16>,

    // Metrics
    /// Writing mode (0=horizontal, 1=vertical)
    pub wmode: i32,
    /// Default horizontal metrics
    pub default_hmtx: HorizontalMetrics,
    /// Horizontal metrics table
    pub hmtx: Vec<HorizontalMetrics>,
    /// Default vertical metrics
    pub default_vmtx: VerticalMetrics,
    /// Vertical metrics table
    pub vmtx: Vec<VerticalMetrics>,

    /// Is embedded font
    pub is_embedded: bool,
    /// Type 3 font loading flag
    pub t3loading: bool,

    // Type 3 specific
    /// Type 3 glyph contents
    pub t3_glyphs: HashMap<i32, Vec<u8>>,
    /// Type 3 font matrix
    pub t3_matrix: [f32; 6],
    /// Type 3 bounding box
    pub t3_bbox: [f32; 4],
}

impl Default for FontDesc {
    fn default() -> Self {
        Self::new()
    }
}

impl FontDesc {
    pub fn new() -> Self {
        Self {
            refs: 1,
            size: std::mem::size_of::<FontDesc>(),
            font: None,
            name: String::new(),
            flags: 0,
            italic_angle: 0.0,
            ascent: 0.0,
            descent: 0.0,
            cap_height: 0.0,
            x_height: 0.0,
            missing_width: 0.0,
            encoding: None,
            to_ttf_cmap: None,
            cid_to_gid: Vec::new(),
            to_unicode: None,
            cid_to_ucs: Vec::new(),
            wmode: 0,
            default_hmtx: HorizontalMetrics::default(),
            hmtx: Vec::new(),
            default_vmtx: VerticalMetrics::default(),
            vmtx: Vec::new(),
            is_embedded: false,
            t3loading: false,
            t3_glyphs: HashMap::new(),
            t3_matrix: [0.001, 0.0, 0.0, 0.001, 0.0, 0.0],
            t3_bbox: [0.0, 0.0, 1000.0, 1000.0],
        }
    }

    /// Create a font descriptor with a name
    pub fn with_name(name: &str) -> Self {
        let mut fd = Self::new();
        fd.name = name.to_string();
        fd
    }

    /// Set writing mode
    pub fn set_wmode(&mut self, wmode: i32) {
        self.wmode = wmode;
    }

    /// Set default horizontal metrics
    pub fn set_default_hmtx(&mut self, w: i32) {
        self.default_hmtx = HorizontalMetrics { lo: 0, hi: 0, w };
    }

    /// Set default vertical metrics
    pub fn set_default_vmtx(&mut self, y: i16, w: i16) {
        self.default_vmtx = VerticalMetrics {
            lo: 0,
            hi: 0,
            x: 0,
            y,
            w,
        };
    }

    /// Add horizontal metrics entry
    pub fn add_hmtx(&mut self, lo: u16, hi: u16, w: i32) {
        self.hmtx.push(HorizontalMetrics { lo, hi, w });
    }

    /// Add vertical metrics entry
    pub fn add_vmtx(&mut self, lo: u16, hi: u16, x: i16, y: i16, w: i16) {
        self.vmtx.push(VerticalMetrics { lo, hi, x, y, w });
    }

    /// Sort and finalize horizontal metrics
    pub fn end_hmtx(&mut self) {
        self.hmtx.sort_by_key(|m| m.lo);
    }

    /// Sort and finalize vertical metrics
    pub fn end_vmtx(&mut self) {
        self.vmtx.sort_by_key(|m| m.lo);
    }

    /// Lookup horizontal metrics for a CID
    pub fn lookup_hmtx(&self, cid: i32) -> HorizontalMetrics {
        let cid = cid as u16;
        for m in &self.hmtx {
            if cid >= m.lo && cid <= m.hi {
                return HorizontalMetrics {
                    lo: cid,
                    hi: cid,
                    w: m.w, // Range has same width for all CIDs
                };
            }
        }
        self.default_hmtx
    }

    /// Lookup vertical metrics for a CID
    pub fn lookup_vmtx(&self, cid: i32) -> VerticalMetrics {
        let cid = cid as u16;
        for m in &self.vmtx {
            if cid >= m.lo && cid <= m.hi {
                return *m;
            }
        }
        self.default_vmtx
    }

    /// Map CID to GID
    pub fn cid_to_gid(&self, cid: i32) -> i32 {
        if cid < 0 {
            return 0;
        }
        if (cid as usize) < self.cid_to_gid.len() {
            self.cid_to_gid[cid as usize] as i32
        } else {
            cid // Identity mapping if no table
        }
    }

    /// Map CID to Unicode
    pub fn cid_to_unicode(&self, cid: i32) -> i32 {
        if cid < 0 {
            return 0;
        }
        if (cid as usize) < self.cid_to_ucs.len() {
            self.cid_to_ucs[cid as usize] as i32
        } else {
            cid // Identity mapping if no table
        }
    }

    /// Check if font is fixed-pitch
    pub fn is_fixed_pitch(&self) -> bool {
        (self.flags & PDF_FD_FIXED_PITCH) != 0
    }

    /// Check if font is serif
    pub fn is_serif(&self) -> bool {
        (self.flags & PDF_FD_SERIF) != 0
    }

    /// Check if font is symbolic
    pub fn is_symbolic(&self) -> bool {
        (self.flags & PDF_FD_SYMBOLIC) != 0
    }

    /// Check if font is italic
    pub fn is_italic(&self) -> bool {
        (self.flags & PDF_FD_ITALIC) != 0
    }

    /// Update memory size
    pub fn update_size(&mut self) {
        self.size = std::mem::size_of::<FontDesc>()
            + self.name.len()
            + self.cid_to_gid.len() * 2
            + self.cid_to_ucs.len() * 2
            + self.hmtx.len() * std::mem::size_of::<HorizontalMetrics>()
            + self.vmtx.len() * std::mem::size_of::<VerticalMetrics>();
    }
}

// ============================================================================
// Standard Encodings
// ============================================================================

/// Get encoding names for a standard encoding
pub fn get_encoding_names(encoding: i32) -> Vec<&'static str> {
    match encoding {
        PDF_ENCODING_MAC_ROMAN => vec![
            ".notdef",
            "space",
            "exclam",
            "quotedbl",
            "numbersign",
            "dollar",
            "percent",
            "ampersand",
            "quotesingle",
            "parenleft",
            "parenright",
            "asterisk",
            "plus",
            "comma",
            "hyphen",
            "period",
            "slash",
            "zero",
            "one",
            "two",
            "three",
            "four",
            "five",
            "six",
            "seven",
            "eight",
            "nine",
            "colon",
            "semicolon",
            "less",
            "equal",
            "greater",
        ],
        PDF_ENCODING_WIN_ANSI => vec![
            ".notdef",
            "space",
            "exclam",
            "quotedbl",
            "numbersign",
            "dollar",
            "percent",
            "ampersand",
            "quotesingle",
            "parenleft",
            "parenright",
            "asterisk",
            "plus",
            "comma",
            "hyphen",
            "period",
            "slash",
            "zero",
            "one",
            "two",
            "three",
            "four",
            "five",
            "six",
            "seven",
            "eight",
            "nine",
            "colon",
            "semicolon",
            "less",
            "equal",
            "greater",
        ],
        _ => vec![
            ".notdef",
            "space",
            "exclam",
            "quotedbl",
            "numbersign",
            "dollar",
            "percent",
            "ampersand",
            "quoteright",
            "parenleft",
            "parenright",
            "asterisk",
            "plus",
            "comma",
            "hyphen",
            "period",
            "slash",
            "zero",
            "one",
            "two",
            "three",
            "four",
            "five",
            "six",
            "seven",
            "eight",
            "nine",
            "colon",
            "semicolon",
            "less",
            "equal",
            "greater",
        ],
    }
}

// ============================================================================
// Substitute Font Data
// ============================================================================

/// Substitute font information
#[derive(Debug, Clone)]
pub struct SubstituteFont {
    pub name: String,
    pub mono: bool,
    pub serif: bool,
    pub bold: bool,
    pub italic: bool,
}

impl SubstituteFont {
    pub fn lookup(mono: bool, serif: bool, bold: bool, italic: bool) -> Self {
        let name = match (mono, serif, bold, italic) {
            (true, _, true, true) => "Courier-BoldOblique",
            (true, _, true, false) => "Courier-Bold",
            (true, _, false, true) => "Courier-Oblique",
            (true, _, false, false) => "Courier",
            (false, true, true, true) => "Times-BoldItalic",
            (false, true, true, false) => "Times-Bold",
            (false, true, false, true) => "Times-Italic",
            (false, true, false, false) => "Times-Roman",
            (false, false, true, true) => "Helvetica-BoldOblique",
            (false, false, true, false) => "Helvetica-Bold",
            (false, false, false, true) => "Helvetica-Oblique",
            (false, false, false, false) => "Helvetica",
        };
        Self {
            name: name.to_string(),
            mono,
            serif,
            bold,
            italic,
        }
    }
}

// ============================================================================
// Global Handle Store
// ============================================================================

pub static FONT_DESCS: LazyLock<HandleStore<FontDesc>> = LazyLock::new(HandleStore::new);

// ============================================================================
// FFI Functions - Font Descriptor Lifecycle
// ============================================================================

/// Create a new font descriptor.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_new_font_desc(_ctx: ContextHandle) -> Handle {
    let fd = FontDesc::new();
    FONT_DESCS.insert(fd)
}

/// Keep (increment reference to) a font descriptor.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_keep_font(_ctx: ContextHandle, font: Handle) -> Handle {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let mut f = font_arc.lock().unwrap();
        f.refs += 1;
    }
    font
}

/// Drop a font descriptor.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_drop_font(_ctx: ContextHandle, font: Handle) {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let should_remove = {
            let mut f = font_arc.lock().unwrap();
            f.refs -= 1;
            f.refs <= 0
        };
        if should_remove {
            FONT_DESCS.remove(font);
        }
    }
}

// ============================================================================
// FFI Functions - Font Properties
// ============================================================================

/// Get font name.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_name(_ctx: ContextHandle, font: Handle) -> *const c_char {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();
        if let Ok(cstr) = CString::new(f.name.clone()) {
            return cstr.into_raw();
        }
    }
    ptr::null()
}

/// Set font name.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_font_name(_ctx: ContextHandle, font: Handle, name: *const c_char) {
    if name.is_null() {
        return;
    }
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let mut f = font_arc.lock().unwrap();
        if let Ok(s) = unsafe { CStr::from_ptr(name).to_str() } {
            f.name = s.to_string();
        }
    }
}

/// Get font flags.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_flags(_ctx: ContextHandle, font: Handle) -> i32 {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();
        return f.flags;
    }
    0
}

/// Set font flags.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_font_flags(_ctx: ContextHandle, font: Handle, flags: i32) {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let mut f = font_arc.lock().unwrap();
        f.flags = flags;
    }
}

/// Get italic angle.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_italic_angle(_ctx: ContextHandle, font: Handle) -> f32 {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();
        return f.italic_angle;
    }
    0.0
}

/// Get ascent.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_ascent(_ctx: ContextHandle, font: Handle) -> f32 {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();
        return f.ascent;
    }
    0.0
}

/// Get descent.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_descent(_ctx: ContextHandle, font: Handle) -> f32 {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();
        return f.descent;
    }
    0.0
}

/// Get cap height.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_cap_height(_ctx: ContextHandle, font: Handle) -> f32 {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();
        return f.cap_height;
    }
    0.0
}

/// Get x-height.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_x_height(_ctx: ContextHandle, font: Handle) -> f32 {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();
        return f.x_height;
    }
    0.0
}

/// Get missing width.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_missing_width(_ctx: ContextHandle, font: Handle) -> f32 {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();
        return f.missing_width;
    }
    0.0
}

/// Check if font is embedded.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_is_embedded(_ctx: ContextHandle, font: Handle) -> i32 {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();
        return if f.is_embedded { 1 } else { 0 };
    }
    0
}

// ============================================================================
// FFI Functions - Writing Mode
// ============================================================================

/// Get font writing mode.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_wmode(_ctx: ContextHandle, font: Handle) -> i32 {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();
        return f.wmode;
    }
    0
}

/// Set font writing mode.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_font_wmode(_ctx: ContextHandle, font: Handle, wmode: i32) {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let mut f = font_arc.lock().unwrap();
        f.set_wmode(wmode);
    }
}

// ============================================================================
// FFI Functions - Metrics
// ============================================================================

/// Set default horizontal metrics.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_default_hmtx(_ctx: ContextHandle, font: Handle, w: i32) {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let mut f = font_arc.lock().unwrap();
        f.set_default_hmtx(w);
    }
}

/// Set default vertical metrics.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_default_vmtx(_ctx: ContextHandle, font: Handle, y: i32, w: i32) {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let mut f = font_arc.lock().unwrap();
        f.set_default_vmtx(y as i16, w as i16);
    }
}

/// Add horizontal metrics.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_hmtx(_ctx: ContextHandle, font: Handle, lo: i32, hi: i32, w: i32) {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let mut f = font_arc.lock().unwrap();
        f.add_hmtx(lo as u16, hi as u16, w);
    }
}

/// Add vertical metrics.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_vmtx(
    _ctx: ContextHandle,
    font: Handle,
    lo: i32,
    hi: i32,
    x: i32,
    y: i32,
    w: i32,
) {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let mut f = font_arc.lock().unwrap();
        f.add_vmtx(lo as u16, hi as u16, x as i16, y as i16, w as i16);
    }
}

/// Finalize horizontal metrics.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_end_hmtx(_ctx: ContextHandle, font: Handle) {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let mut f = font_arc.lock().unwrap();
        f.end_hmtx();
    }
}

/// Finalize vertical metrics.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_end_vmtx(_ctx: ContextHandle, font: Handle) {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let mut f = font_arc.lock().unwrap();
        f.end_vmtx();
    }
}

/// Lookup horizontal metrics.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_lookup_hmtx(
    _ctx: ContextHandle,
    font: Handle,
    cid: i32,
) -> HorizontalMetrics {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();
        return f.lookup_hmtx(cid);
    }
    HorizontalMetrics::default()
}

/// Lookup vertical metrics.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_lookup_vmtx(_ctx: ContextHandle, font: Handle, cid: i32) -> VerticalMetrics {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();
        return f.lookup_vmtx(cid);
    }
    VerticalMetrics::default()
}

// ============================================================================
// FFI Functions - CID Mapping
// ============================================================================

/// Map CID to GID.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_cid_to_gid(_ctx: ContextHandle, font: Handle, cid: i32) -> i32 {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();
        return f.cid_to_gid(cid);
    }
    cid
}

/// Map CID to Unicode.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_cid_to_unicode(_ctx: ContextHandle, font: Handle, cid: i32) -> i32 {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();
        return f.cid_to_unicode(cid);
    }
    cid
}

/// Set CID to GID mapping table.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_cid_to_gid(
    _ctx: ContextHandle,
    font: Handle,
    table: *const u16,
    len: usize,
) {
    if table.is_null() || len == 0 {
        return;
    }
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let mut f = font_arc.lock().unwrap();
        f.cid_to_gid = unsafe { std::slice::from_raw_parts(table, len).to_vec() };
        f.update_size();
    }
}

/// Set CID to UCS mapping table.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_cid_to_ucs(
    _ctx: ContextHandle,
    font: Handle,
    table: *const u16,
    len: usize,
) {
    if table.is_null() || len == 0 {
        return;
    }
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let mut f = font_arc.lock().unwrap();
        f.cid_to_ucs = unsafe { std::slice::from_raw_parts(table, len).to_vec() };
        f.update_size();
    }
}

// ============================================================================
// FFI Functions - Font Loading
// ============================================================================

/// Load font from a PDF font dictionary object.
///
/// Reads /Subtype, /BaseFont, /Encoding, /FirstChar, /LastChar, /Widths,
/// and /FontDescriptor entries from the PdfObj dictionary to populate a
/// FontDesc. When the object handle is invalid or not a dictionary, a
/// default FontDesc with sensible fallback values is returned so callers
/// always receive a valid handle.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_font(
    _ctx: ContextHandle,
    _doc: DocumentHandle,
    _rdb: Handle,
    obj: PdfObjHandle,
) -> Handle {
    let mut fd = FontDesc::new();

    // Extract font properties from the PdfObj dictionary
    if let Some(obj_arc) = PDF_OBJECTS.get(obj) {
        if let Ok(guard) = obj_arc.lock() {
            if let PdfObjType::Dict(ref entries) = guard.obj_type {
                // Extract /BaseFont name
                for (key, val) in entries {
                    match key.as_str() {
                        "BaseFont" => {
                            if let PdfObjType::Name(ref name) = val.obj_type {
                                fd.name = name.clone();
                            }
                        }
                        "Subtype" => {
                            if let PdfObjType::Name(ref subtype) = val.obj_type {
                                // Set flags based on subtype
                                if subtype == "Type3" {
                                    fd.t3loading = true;
                                }
                            }
                        }
                        "FirstChar" => {
                            // Used with /Widths for simple fonts
                        }
                        "LastChar" => {
                            // Used with /Widths for simple fonts
                        }
                        "Widths" => {
                            if let PdfObjType::Array(ref widths) = val.obj_type {
                                // Extract first/last char from sibling entries
                                let first_char = entries
                                    .iter()
                                    .find(|(k, _)| k == "FirstChar")
                                    .and_then(|(_, v)| {
                                        if let PdfObjType::Int(i) = v.obj_type {
                                            Some(i as u16)
                                        } else {
                                            None
                                        }
                                    })
                                    .unwrap_or(0);
                                for (idx, w) in widths.iter().enumerate() {
                                    if let PdfObjType::Int(width) = w.obj_type {
                                        let cid = first_char + idx as u16;
                                        fd.add_hmtx(cid, cid, width as i32);
                                    } else if let PdfObjType::Real(width) = w.obj_type {
                                        let cid = first_char + idx as u16;
                                        fd.add_hmtx(cid, cid, width as i32);
                                    }
                                }
                                fd.end_hmtx();
                            }
                        }
                        "FontDescriptor" => {
                            if let PdfObjType::Dict(ref desc_entries) = val.obj_type {
                                load_font_descriptor_entries(&mut fd, desc_entries);
                            }
                        }
                        "Encoding" => {
                            if let PdfObjType::Name(ref enc) = val.obj_type {
                                // Recognize standard encoding names
                                if enc.contains("WinAnsi") {
                                    fd.flags |= PDF_FD_NONSYMBOLIC;
                                } else if enc.contains("Symbol") || enc.contains("ZapfDingbats") {
                                    fd.flags |= PDF_FD_SYMBOLIC;
                                }
                            }
                        }
                        _ => {}
                    }
                }
            }
        }
    }

    // Apply fallback values when no name was parsed
    if fd.name.is_empty() {
        fd.name = "Unknown".to_string();
    }

    // Set sensible default metrics when none were loaded
    if fd.default_hmtx.w == 0 && fd.hmtx.is_empty() {
        fd.set_default_hmtx(1000);
    }

    fd.update_size();
    FONT_DESCS.insert(fd)
}

/// Populate a FontDesc from /FontDescriptor dictionary entries.
fn load_font_descriptor_entries(fd: &mut FontDesc, entries: &[(String, PdfObj)]) {
    for (key, val) in entries {
        match key.as_str() {
            "FontName" => {
                if let PdfObjType::Name(ref n) = val.obj_type {
                    if fd.name.is_empty() || fd.name == "Unknown" {
                        fd.name = n.clone();
                    }
                }
            }
            "Flags" => {
                if let PdfObjType::Int(f) = val.obj_type {
                    fd.flags = f as i32;
                }
            }
            "ItalicAngle" => match val.obj_type {
                PdfObjType::Real(f) => fd.italic_angle = f as f32,
                PdfObjType::Int(i) => fd.italic_angle = i as f32,
                _ => {}
            },
            "Ascent" => match val.obj_type {
                PdfObjType::Real(f) => fd.ascent = f as f32,
                PdfObjType::Int(i) => fd.ascent = i as f32,
                _ => {}
            },
            "Descent" => match val.obj_type {
                PdfObjType::Real(f) => fd.descent = f as f32,
                PdfObjType::Int(i) => fd.descent = i as f32,
                _ => {}
            },
            "CapHeight" => match val.obj_type {
                PdfObjType::Real(f) => fd.cap_height = f as f32,
                PdfObjType::Int(i) => fd.cap_height = i as f32,
                _ => {}
            },
            "XHeight" => match val.obj_type {
                PdfObjType::Real(f) => fd.x_height = f as f32,
                PdfObjType::Int(i) => fd.x_height = i as f32,
                _ => {}
            },
            "MissingWidth" => match val.obj_type {
                PdfObjType::Real(f) => fd.missing_width = f as f32,
                PdfObjType::Int(i) => fd.missing_width = i as f32,
                _ => {}
            },
            "FontFile" | "FontFile2" | "FontFile3" => {
                // Presence of an embedded font stream
                fd.is_embedded = true;
            }
            _ => {}
        }
    }
}

/// Load Type 3 font from PDF (simplified).
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_type3_font(
    _ctx: ContextHandle,
    _doc: DocumentHandle,
    _rdb: Handle,
    _obj: PdfObjHandle,
) -> Handle {
    // Create a Type 3 font descriptor
    let mut fd = FontDesc::new();
    fd.t3loading = true;
    FONT_DESCS.insert(fd)
}

/// Load Type 3 glyphs.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_type3_glyphs(_ctx: ContextHandle, _doc: DocumentHandle, font: Handle) {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let mut f = font_arc.lock().unwrap();
        f.t3loading = false;
    }
}

/// Load "hail mary" fallback font.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_hail_mary_font(_ctx: ContextHandle, _doc: DocumentHandle) -> Handle {
    let mut fd = FontDesc::with_name("Helvetica");
    fd.flags = PDF_FD_NONSYMBOLIC;
    FONT_DESCS.insert(fd)
}

// ============================================================================
// FFI Functions - Encoding
// ============================================================================

/// Load encoding strings.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_encoding(estrings: *mut *const c_char, encoding: *const c_char) {
    if estrings.is_null() || encoding.is_null() {
        return;
    }

    let enc_str = unsafe { CStr::from_ptr(encoding).to_str().unwrap_or("") };
    let enc_type = match enc_str {
        "MacRomanEncoding" => PDF_ENCODING_MAC_ROMAN,
        "WinAnsiEncoding" => PDF_ENCODING_WIN_ANSI,
        "MacExpertEncoding" => PDF_ENCODING_MAC_EXPERT,
        _ => PDF_ENCODING_STANDARD,
    };

    let names = get_encoding_names(enc_type);
    for (i, name) in names.iter().enumerate() {
        if let Ok(cstr) = CString::new(*name) {
            unsafe {
                *estrings.add(i) = cstr.into_raw();
            }
        }
    }
}

// ============================================================================
// FFI Functions - Substitute Fonts
// ============================================================================

/// Look up a substitute font by style flags.
///
/// Returns a pointer to a static font name string (one of the PDF
/// standard 14 font names) and writes its byte length to `*len`.
/// The pointer is stable for the lifetime of the process because it
/// points into a leaked `CString`. The caller must not free it.
///
/// The font name can be used directly as the /BaseFont value when
/// constructing a Type1 font dictionary for a standard 14 font --
/// PDF viewers are required to provide built-in metrics for these
/// names, so no font program data needs to be embedded.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_lookup_substitute_font(
    _ctx: ContextHandle,
    mono: i32,
    serif: i32,
    bold: i32,
    italic: i32,
    len: *mut i32,
) -> *const u8 {
    let sf = SubstituteFont::lookup(mono != 0, serif != 0, bold != 0, italic != 0);

    // Obtain a &'static reference to the font name by matching against
    // known constant strings. This avoids leaking heap memory on every
    // call while providing a stable pointer.
    let static_name: &'static [u8] = match sf.name.as_str() {
        "Courier" => b"Courier",
        "Courier-Bold" => b"Courier-Bold",
        "Courier-Oblique" => b"Courier-Oblique",
        "Courier-BoldOblique" => b"Courier-BoldOblique",
        "Times-Roman" => b"Times-Roman",
        "Times-Bold" => b"Times-Bold",
        "Times-Italic" => b"Times-Italic",
        "Times-BoldItalic" => b"Times-BoldItalic",
        "Helvetica" => b"Helvetica",
        "Helvetica-Bold" => b"Helvetica-Bold",
        "Helvetica-Oblique" => b"Helvetica-Oblique",
        "Helvetica-BoldOblique" => b"Helvetica-BoldOblique",
        _ => b"Helvetica",
    };

    if !len.is_null() {
        unsafe {
            *len = static_name.len() as i32;
        }
    }

    static_name.as_ptr()
}

/// Clean font name.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_clean_font_name(fontname: *const c_char) -> *const c_char {
    if fontname.is_null() {
        return ptr::null();
    }

    let name = unsafe { CStr::from_ptr(fontname).to_str().unwrap_or("") };

    // Remove common prefixes
    let cleaned = name
        .trim_start_matches("AAAAAA+")
        .trim_start_matches("BAAAAA+")
        .trim_start_matches("CAAAAA+");

    if let Ok(cstr) = CString::new(cleaned) {
        return cstr.into_raw();
    }
    fontname
}

// ============================================================================
// FFI Functions - Font Addition
// ============================================================================

/// Add a Type1 or TrueType simple font to the document.
///
/// Builds a PDF font dictionary with /Type /Font, /Subtype (Type1 or
/// TrueType), /BaseFont, /Encoding, /FirstChar, /LastChar, and /Widths.
/// A /FontDescriptor sub-dictionary is included with metrics extracted
/// from the FontDesc handle. Returns a PdfObjHandle for the new font
/// dictionary, or 0 if the font handle is invalid.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_simple_font(
    _ctx: ContextHandle,
    _doc: DocumentHandle,
    font: FontHandle,
    encoding: i32,
) -> PdfObjHandle {
    let font_data = match FONT_DESCS.get(font) {
        Some(arc) => match arc.lock() {
            Ok(guard) => guard.clone(),
            Err(_) => return 0,
        },
        None => return 0,
    };

    // Build the font dictionary
    let mut dict_entries: Vec<(String, PdfObj)> = Vec::with_capacity(10);

    // /Type /Font
    dict_entries.push(("Type".to_string(), PdfObj::new_name("Font")));

    // /Subtype - simple fonts are either Type1 or TrueType
    let subtype = if font_data.is_embedded {
        "TrueType"
    } else {
        "Type1"
    };
    dict_entries.push(("Subtype".to_string(), PdfObj::new_name(subtype)));

    // /BaseFont
    let base_font = if font_data.name.is_empty() {
        "Helvetica"
    } else {
        &font_data.name
    };
    dict_entries.push(("BaseFont".to_string(), PdfObj::new_name(base_font)));

    // /Encoding
    let enc_name = match encoding {
        PDF_ENCODING_MAC_ROMAN => "MacRomanEncoding",
        PDF_ENCODING_WIN_ANSI => "WinAnsiEncoding",
        PDF_ENCODING_MAC_EXPERT => "MacExpertEncoding",
        _ => "WinAnsiEncoding",
    };
    dict_entries.push(("Encoding".to_string(), PdfObj::new_name(enc_name)));

    // /FirstChar, /LastChar, /Widths
    let first_char: i64 = 0;
    let last_char: i64 = 255;
    dict_entries.push(("FirstChar".to_string(), PdfObj::new_int(first_char)));
    dict_entries.push(("LastChar".to_string(), PdfObj::new_int(last_char)));

    // Build widths array from horizontal metrics
    let mut widths = Vec::with_capacity(256);
    for cid in 0..=255i32 {
        let m = font_data.lookup_hmtx(cid);
        widths.push(PdfObj::new_int(m.w as i64));
    }
    let widths_obj = PdfObj {
        obj_type: PdfObjType::Array(widths),
        marked: false,
        dirty: false,
        parent_num: 0,
        refs: 1,
    };
    dict_entries.push(("Widths".to_string(), widths_obj));

    // Build /FontDescriptor sub-dictionary
    let descriptor = build_font_descriptor_dict(&font_data);
    dict_entries.push(("FontDescriptor".to_string(), descriptor));

    let font_dict = PdfObj {
        obj_type: PdfObjType::Dict(dict_entries),
        marked: false,
        dirty: false,
        parent_num: 0,
        refs: 1,
    };

    PDF_OBJECTS.insert(font_dict)
}

/// Build a /FontDescriptor dictionary from a FontDesc.
fn build_font_descriptor_dict(fd: &FontDesc) -> PdfObj {
    let mut entries: Vec<(String, PdfObj)> = Vec::with_capacity(12);

    entries.push(("Type".to_string(), PdfObj::new_name("FontDescriptor")));
    entries.push((
        "FontName".to_string(),
        PdfObj::new_name(if fd.name.is_empty() {
            "Unknown"
        } else {
            &fd.name
        }),
    ));
    entries.push(("Flags".to_string(), PdfObj::new_int(fd.flags as i64)));
    entries.push((
        "ItalicAngle".to_string(),
        PdfObj::new_real(fd.italic_angle as f64),
    ));
    entries.push(("Ascent".to_string(), PdfObj::new_real(fd.ascent as f64)));
    entries.push(("Descent".to_string(), PdfObj::new_real(fd.descent as f64)));
    entries.push((
        "CapHeight".to_string(),
        PdfObj::new_real(fd.cap_height as f64),
    ));
    entries.push(("XHeight".to_string(), PdfObj::new_real(fd.x_height as f64)));
    entries.push((
        "MissingWidth".to_string(),
        PdfObj::new_real(fd.missing_width as f64),
    ));

    // /FontBBox - use Type3 bbox or standard estimate
    let bbox = PdfObj {
        obj_type: PdfObjType::Array(vec![
            PdfObj::new_real(fd.t3_bbox[0] as f64),
            PdfObj::new_real(fd.t3_bbox[1] as f64),
            PdfObj::new_real(fd.t3_bbox[2] as f64),
            PdfObj::new_real(fd.t3_bbox[3] as f64),
        ]),
        marked: false,
        dirty: false,
        parent_num: 0,
        refs: 1,
    };
    entries.push(("FontBBox".to_string(), bbox));

    PdfObj {
        obj_type: PdfObjType::Dict(entries),
        marked: false,
        dirty: false,
        parent_num: 0,
        refs: 1,
    }
}

/// Add a CIDFont (Type 0 composite font) to the document.
///
/// Builds a Type0 composite font dictionary referencing a CIDFont
/// descendant. The CIDFont contains /CIDSystemInfo, /DW (default width),
/// and a /W array of per-CID widths derived from the FontDesc horizontal
/// metrics. Returns a PdfObjHandle for the Type0 font dictionary, or 0
/// if the font handle is invalid.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_cid_font(
    _ctx: ContextHandle,
    _doc: DocumentHandle,
    font: FontHandle,
) -> PdfObjHandle {
    let font_data = match FONT_DESCS.get(font) {
        Some(arc) => match arc.lock() {
            Ok(guard) => guard.clone(),
            Err(_) => return 0,
        },
        None => return 0,
    };

    // Build the CIDFont descendant dictionary
    let mut cid_entries: Vec<(String, PdfObj)> = Vec::with_capacity(8);
    cid_entries.push(("Type".to_string(), PdfObj::new_name("Font")));
    cid_entries.push(("Subtype".to_string(), PdfObj::new_name("CIDFontType2")));
    cid_entries.push((
        "BaseFont".to_string(),
        PdfObj::new_name(if font_data.name.is_empty() {
            "Unknown"
        } else {
            &font_data.name
        }),
    ));

    // /CIDSystemInfo dictionary
    let mut csi_entries: Vec<(String, PdfObj)> = Vec::with_capacity(3);
    csi_entries.push(("Registry".to_string(), PdfObj::new_string(b"Adobe")));
    csi_entries.push(("Ordering".to_string(), PdfObj::new_string(b"Identity")));
    csi_entries.push(("Supplement".to_string(), PdfObj::new_int(0)));
    let csi = PdfObj {
        obj_type: PdfObjType::Dict(csi_entries),
        marked: false,
        dirty: false,
        parent_num: 0,
        refs: 1,
    };
    cid_entries.push(("CIDSystemInfo".to_string(), csi));

    // /DW (default width)
    cid_entries.push((
        "DW".to_string(),
        PdfObj::new_int(font_data.default_hmtx.w as i64),
    ));

    // /W array - encode per-CID widths as [cid [w1 w2 ...]]
    if !font_data.hmtx.is_empty() {
        let mut w_array: Vec<PdfObj> = Vec::new();
        for m in &font_data.hmtx {
            // Format: cid_start [w w w ...] for ranges with same width
            w_array.push(PdfObj::new_int(m.lo as i64));
            let count = (m.hi - m.lo + 1) as usize;
            let widths: Vec<PdfObj> = (0..count).map(|_| PdfObj::new_int(m.w as i64)).collect();
            w_array.push(PdfObj {
                obj_type: PdfObjType::Array(widths),
                marked: false,
                dirty: false,
                parent_num: 0,
                refs: 1,
            });
        }
        cid_entries.push((
            "W".to_string(),
            PdfObj {
                obj_type: PdfObjType::Array(w_array),
                marked: false,
                dirty: false,
                parent_num: 0,
                refs: 1,
            },
        ));
    }

    // /CIDToGIDMap
    if !font_data.cid_to_gid.is_empty() {
        cid_entries.push(("CIDToGIDMap".to_string(), PdfObj::new_name("Identity")));
    }

    // /FontDescriptor
    let descriptor = build_font_descriptor_dict(&font_data);
    cid_entries.push(("FontDescriptor".to_string(), descriptor));

    let cid_font = PdfObj {
        obj_type: PdfObjType::Dict(cid_entries),
        marked: false,
        dirty: false,
        parent_num: 0,
        refs: 1,
    };

    // Build the top-level Type0 dictionary
    let mut type0_entries: Vec<(String, PdfObj)> = Vec::with_capacity(5);
    type0_entries.push(("Type".to_string(), PdfObj::new_name("Font")));
    type0_entries.push(("Subtype".to_string(), PdfObj::new_name("Type0")));
    type0_entries.push((
        "BaseFont".to_string(),
        PdfObj::new_name(if font_data.name.is_empty() {
            "Unknown"
        } else {
            &font_data.name
        }),
    ));
    type0_entries.push(("Encoding".to_string(), PdfObj::new_name("Identity-H")));

    // /DescendantFonts array containing the CIDFont
    type0_entries.push((
        "DescendantFonts".to_string(),
        PdfObj {
            obj_type: PdfObjType::Array(vec![cid_font]),
            marked: false,
            dirty: false,
            parent_num: 0,
            refs: 1,
        },
    ));

    let type0_dict = PdfObj {
        obj_type: PdfObjType::Dict(type0_entries),
        marked: false,
        dirty: false,
        parent_num: 0,
        refs: 1,
    };

    PDF_OBJECTS.insert(type0_dict)
}

/// Add a CJK font to the document with the appropriate CMap encoding.
///
/// Creates a Type0 composite font using the correct Adobe registry and
/// ordering for the requested CJK script (CNS1/GB1/Japan1/Korea1).
/// The encoding CMap is selected from the script and writing mode, and
/// the serif flag chooses between Ming/Song/Mincho/Batang (serif) and
/// Gothic/Hei/Kaku/Dotum (sans-serif) family names. Returns a
/// PdfObjHandle for the font dictionary, or 0 on invalid input.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_cjk_font(
    _ctx: ContextHandle,
    _doc: DocumentHandle,
    font: FontHandle,
    script: i32,
    wmode: i32,
    serif: i32,
) -> PdfObjHandle {
    let font_data = match FONT_DESCS.get(font) {
        Some(arc) => match arc.lock() {
            Ok(guard) => guard.clone(),
            Err(_) => return 0,
        },
        None => return 0,
    };

    // Determine registry, ordering, and encoding based on script
    let (registry, ordering, encoding, font_name) = match script {
        PDF_CJK_CNS1 => {
            let name = if serif != 0 {
                "MingLiU"
            } else {
                "DFKaiShu-SB-Estd-BF"
            };
            let enc = if wmode != 0 {
                "UniCNS-UTF16-V"
            } else {
                "UniCNS-UTF16-H"
            };
            ("Adobe", "CNS1", enc, name)
        }
        PDF_CJK_GB1 => {
            let name = if serif != 0 { "SimSun" } else { "SimHei" };
            let enc = if wmode != 0 {
                "UniGB-UTF16-V"
            } else {
                "UniGB-UTF16-H"
            };
            ("Adobe", "GB1", enc, name)
        }
        PDF_CJK_JAPAN1 => {
            let name = if serif != 0 { "MS-Mincho" } else { "MS-Gothic" };
            let enc = if wmode != 0 {
                "UniJIS-UTF16-V"
            } else {
                "UniJIS-UTF16-H"
            };
            ("Adobe", "Japan1", enc, name)
        }
        PDF_CJK_KOREA1 => {
            let name = if serif != 0 { "Batang" } else { "Dotum" };
            let enc = if wmode != 0 {
                "UniKS-UTF16-V"
            } else {
                "UniKS-UTF16-H"
            };
            ("Adobe", "Korea1", enc, name)
        }
        _ => ("Adobe", "Identity", "Identity-H", "Unknown"),
    };

    let base_name = if font_data.name.is_empty() {
        font_name.to_string()
    } else {
        font_data.name.clone()
    };

    // CIDFont descendant
    let mut cid_entries: Vec<(String, PdfObj)> = Vec::with_capacity(6);
    cid_entries.push(("Type".to_string(), PdfObj::new_name("Font")));
    cid_entries.push(("Subtype".to_string(), PdfObj::new_name("CIDFontType0")));
    cid_entries.push(("BaseFont".to_string(), PdfObj::new_name(&base_name)));

    // /CIDSystemInfo
    let mut csi_entries: Vec<(String, PdfObj)> = Vec::with_capacity(3);
    csi_entries.push((
        "Registry".to_string(),
        PdfObj::new_string(registry.as_bytes()),
    ));
    csi_entries.push((
        "Ordering".to_string(),
        PdfObj::new_string(ordering.as_bytes()),
    ));
    csi_entries.push(("Supplement".to_string(), PdfObj::new_int(0)));
    cid_entries.push((
        "CIDSystemInfo".to_string(),
        PdfObj {
            obj_type: PdfObjType::Dict(csi_entries),
            marked: false,
            dirty: false,
            parent_num: 0,
            refs: 1,
        },
    ));

    // Default width for CJK is 1000 (full-width)
    let dw = if font_data.default_hmtx.w != 0 {
        font_data.default_hmtx.w
    } else {
        1000
    };
    cid_entries.push(("DW".to_string(), PdfObj::new_int(dw as i64)));

    let descriptor = build_font_descriptor_dict(&font_data);
    cid_entries.push(("FontDescriptor".to_string(), descriptor));

    let cid_font = PdfObj {
        obj_type: PdfObjType::Dict(cid_entries),
        marked: false,
        dirty: false,
        parent_num: 0,
        refs: 1,
    };

    // Type0 wrapper
    let mut type0_entries: Vec<(String, PdfObj)> = Vec::with_capacity(5);
    type0_entries.push(("Type".to_string(), PdfObj::new_name("Font")));
    type0_entries.push(("Subtype".to_string(), PdfObj::new_name("Type0")));
    type0_entries.push(("BaseFont".to_string(), PdfObj::new_name(&base_name)));
    type0_entries.push(("Encoding".to_string(), PdfObj::new_name(encoding)));
    type0_entries.push((
        "DescendantFonts".to_string(),
        PdfObj {
            obj_type: PdfObjType::Array(vec![cid_font]),
            marked: false,
            dirty: false,
            parent_num: 0,
            refs: 1,
        },
    ));

    let type0_dict = PdfObj {
        obj_type: PdfObjType::Dict(type0_entries),
        marked: false,
        dirty: false,
        parent_num: 0,
        refs: 1,
    };

    PDF_OBJECTS.insert(type0_dict)
}

/// Add a substitute (standard 14) font entry to the document.
///
/// Inspects the FontDesc flags to select the closest match among the PDF
/// standard 14 fonts (Helvetica, Times-Roman, Courier, etc.) and builds
/// a simple Type1 font dictionary. Because standard 14 fonts do not
/// require embedding, the resulting dictionary contains only /Type,
/// /Subtype, /BaseFont, and /Encoding. Returns a PdfObjHandle for the
/// font dictionary, or 0 if the font handle is invalid.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_substitute_font(
    _ctx: ContextHandle,
    _doc: DocumentHandle,
    font: FontHandle,
) -> PdfObjHandle {
    let font_data = match FONT_DESCS.get(font) {
        Some(arc) => match arc.lock() {
            Ok(guard) => guard.clone(),
            Err(_) => return 0,
        },
        None => return 0,
    };

    // Select substitute based on font flags
    let is_mono = font_data.is_fixed_pitch();
    let is_serif = font_data.is_serif();
    let is_italic = font_data.is_italic();
    let is_bold = (font_data.flags & PDF_FD_FORCE_BOLD) != 0;

    let sf = SubstituteFont::lookup(is_mono, is_serif, is_bold, is_italic);

    let mut dict_entries: Vec<(String, PdfObj)> = Vec::with_capacity(4);
    dict_entries.push(("Type".to_string(), PdfObj::new_name("Font")));
    dict_entries.push(("Subtype".to_string(), PdfObj::new_name("Type1")));
    dict_entries.push(("BaseFont".to_string(), PdfObj::new_name(&sf.name)));
    dict_entries.push(("Encoding".to_string(), PdfObj::new_name("WinAnsiEncoding")));

    let font_dict = PdfObj {
        obj_type: PdfObjType::Dict(dict_entries),
        marked: false,
        dirty: false,
        parent_num: 0,
        refs: 1,
    };

    PDF_OBJECTS.insert(font_dict)
}

/// Check if font writing is supported.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_writing_supported(_ctx: ContextHandle, _font: FontHandle) -> i32 {
    // Most fonts support writing
    1
}

/// Subset embedded fonts in the document to reduce file size.
///
/// Scans the document's raw PDF data for embedded font streams (objects
/// containing /Type /Font and either /FontFile, /FontFile2, or
/// /FontFile3). For each embedded font, adds a six-letter random subset
/// prefix (e.g. "ABCDEF+") to the /BaseFont name, which signals to PDF
/// consumers that the font contains only a subset of its glyphs. The
/// actual glyph table trimming is a lossy operation that would require a
/// full font parser (e.g. sfnt/CFF), so we limit the operation to the
/// naming convention that the PDF specification mandates for subsetted
/// fonts.
///
/// When `pages` is non-null and `pages_len > 0`, only fonts referenced
/// by those page numbers are considered. Otherwise all pages are
/// processed.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_subset_fonts(
    _ctx: ContextHandle,
    doc: DocumentHandle,
    pages_len: i32,
    pages: *const i32,
) {
    let doc_arc = match crate::ffi::DOCUMENTS.get(doc) {
        Some(d) => d,
        None => return,
    };

    let mut guard = match doc_arc.lock() {
        Ok(g) => g,
        Err(_) => return,
    };

    let data = guard.data().to_vec();

    // Determine which pages to process
    let target_pages: Vec<i32> = if !pages.is_null() && pages_len > 0 {
        unsafe { std::slice::from_raw_parts(pages, pages_len as usize) }.to_vec()
    } else {
        (0..guard.page_count).collect()
    };

    // Collect all /BaseFont names that appear in the target pages' font
    // resource dictionaries.  We search the raw bytes for /Font
    // references within each page's dictionary.
    let page_pattern = b"/Type /Page";
    let mut font_names_in_scope: Vec<String> = Vec::new();
    let mut page_idx = 0i32;
    let mut pos = 0usize;
    while pos + page_pattern.len() <= data.len() {
        if &data[pos..pos + page_pattern.len()] == page_pattern
            && data.get(pos + page_pattern.len()) != Some(&b's')
        {
            if target_pages.contains(&page_idx) {
                // Search nearby for /Font references
                let search_end = (pos + 4096).min(data.len());
                let region = &data[pos..search_end];
                let text = String::from_utf8_lossy(region);
                // Extract /BaseFont names from this region
                let mut idx = 0;
                while let Some(bf_pos) = text[idx..].find("/BaseFont") {
                    let start = idx + bf_pos + 9; // skip "/BaseFont"
                    if start >= text.len() {
                        break;
                    }
                    let after = text[start..].trim_start();
                    if let Some(name) = after.strip_prefix('/') {
                        let end = name
                            .find(|c: char| c.is_whitespace() || c == '/' || c == '>')
                            .unwrap_or(name.len());
                        let font_name = name[..end].to_string();
                        if !font_name.is_empty() && !font_names_in_scope.contains(&font_name) {
                            font_names_in_scope.push(font_name);
                        }
                    }
                    idx = start;
                }
            }
            page_idx += 1;
        }
        pos += 1;
    }

    // Now scan for embedded fonts (those with /FontFile, /FontFile2, or
    // /FontFile3) and add a subset prefix to their /BaseFont name if
    // they appear in the target pages.
    let mut modified_data = data;
    let mut offset_adjustment: i64 = 0;

    for font_name in &font_names_in_scope {
        // Skip fonts that already have a subset prefix (6 uppercase + '+')
        if font_name.len() > 7
            && font_name.as_bytes()[6] == b'+'
            && font_name[..6].chars().all(|c| c.is_ascii_uppercase())
        {
            continue;
        }

        // Check if this font is embedded by looking for its /FontDescriptor
        // containing /FontFile
        let basefont_tag = format!("/BaseFont /{}", font_name);
        let search_data = String::from_utf8_lossy(&modified_data);
        let is_embedded = search_data.contains("/FontFile");
        if !is_embedded {
            continue;
        }

        // Generate a deterministic 6-letter prefix from the font name
        let prefix = generate_subset_prefix(font_name);
        let prefixed_name = format!("{}+{}", prefix, font_name);

        // Replace all occurrences of the old /BaseFont name with the prefixed version
        let old_tag = format!("/{}", font_name);
        let new_tag = format!("/{}", prefixed_name);
        let text = String::from_utf8_lossy(&modified_data).to_string();
        let replaced = text.replace(&old_tag, &new_tag);
        let new_len = replaced.len() as i64;
        let old_len = modified_data.len() as i64;
        offset_adjustment += new_len - old_len;
        modified_data = replaced.into_bytes();
    }

    if offset_adjustment != 0 {
        guard.set_data(modified_data);
    }
}

/// Generate a deterministic 6-letter uppercase prefix from a font name.
/// Uses a simple hash to produce repeatable results for the same input.
fn generate_subset_prefix(name: &str) -> String {
    let mut hash: u32 = 0;
    for byte in name.as_bytes() {
        hash = hash.wrapping_mul(31).wrapping_add(*byte as u32);
    }
    let mut prefix = String::with_capacity(6);
    let mut h = hash;
    for _ in 0..6 {
        prefix.push((b'A' + (h % 26) as u8) as char);
        h /= 26;
        if h == 0 {
            h = hash.wrapping_add(1);
        }
    }
    prefix
}

// ============================================================================
// FFI Functions - Font Printing
// ============================================================================

/// Print font information to an output stream.
///
/// Formats a human-readable summary of the font descriptor (name, flags,
/// metrics, embedding status, writing mode) and writes it to the Output
/// identified by `out`. Falls back to stderr when the output handle is
/// invalid or cannot be locked.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_print_font(_ctx: ContextHandle, out: OutputHandle, font: Handle) {
    if let Some(font_arc) = FONT_DESCS.get(font) {
        let f = font_arc.lock().unwrap();

        let info = format!(
            "Font: {}\n  Flags: 0x{:x}\n  Embedded: {}\n  WMode: {}\n  \
             Ascent: {:.1}\n  Descent: {:.1}\n  CapHeight: {:.1}\n  \
             ItalicAngle: {:.1}\n  MissingWidth: {:.1}\n  HMetrics: {}\n  \
             VMetrics: {}\n",
            f.name,
            f.flags,
            f.is_embedded,
            f.wmode,
            f.ascent,
            f.descent,
            f.cap_height,
            f.italic_angle,
            f.missing_width,
            f.hmtx.len(),
            f.vmtx.len(),
        );

        if let Some(out_arc) = OUTPUTS.get(out) {
            if let Ok(mut out_guard) = out_arc.lock() {
                let _ = out_guard.write_string(&info);
                return;
            }
        }

        // Fallback: write to stderr when output handle is unavailable
        eprint!("{}", info);
    }
}

/// Free a string allocated by font functions.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_free_string(_ctx: ContextHandle, s: *mut c_char) {
    if !s.is_null() {
        unsafe {
            drop(CString::from_raw(s));
        }
    }
}

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

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

    #[test]
    fn test_font_desc_new() {
        let fd = FontDesc::new();
        assert!(fd.name.is_empty());
        assert_eq!(fd.flags, 0);
        assert_eq!(fd.refs, 1);
        assert!(!fd.is_embedded);
    }

    #[test]
    fn test_font_desc_with_name() {
        let fd = FontDesc::with_name("Helvetica");
        assert_eq!(fd.name, "Helvetica");
    }

    #[test]
    fn test_horizontal_metrics() {
        let mut fd = FontDesc::new();
        fd.set_default_hmtx(1000);
        fd.add_hmtx(0, 127, 500);
        fd.add_hmtx(128, 255, 600);
        fd.end_hmtx();

        let m = fd.lookup_hmtx(65); // 'A'
        assert_eq!(m.w, 500);

        let m2 = fd.lookup_hmtx(200);
        assert_eq!(m2.w, 600);

        let m3 = fd.lookup_hmtx(300); // Out of range
        assert_eq!(m3.w, 1000); // Default
    }

    #[test]
    fn test_vertical_metrics() {
        let mut fd = FontDesc::new();
        fd.set_default_vmtx(-500, 1000);
        fd.add_vmtx(0, 127, 0, -500, 1000);
        fd.end_vmtx();

        let m = fd.lookup_vmtx(65);
        assert_eq!(m.y, -500);
        assert_eq!(m.w, 1000);
    }

    #[test]
    fn test_cid_to_gid() {
        let mut fd = FontDesc::new();
        fd.cid_to_gid = vec![0, 1, 2, 100, 101, 102];

        assert_eq!(fd.cid_to_gid(0), 0);
        assert_eq!(fd.cid_to_gid(3), 100);
        assert_eq!(fd.cid_to_gid(100), 100); // Identity for out of range
    }

    #[test]
    fn test_cid_to_unicode() {
        let mut fd = FontDesc::new();
        fd.cid_to_ucs = vec![0x0000, 0x0041, 0x0042, 0x0043]; // .notdef, A, B, C

        assert_eq!(fd.cid_to_unicode(1), 0x0041); // A
        assert_eq!(fd.cid_to_unicode(2), 0x0042); // B
    }

    #[test]
    fn test_font_flags() {
        let mut fd = FontDesc::new();
        fd.flags = PDF_FD_SERIF | PDF_FD_ITALIC;

        assert!(fd.is_serif());
        assert!(fd.is_italic());
        assert!(!fd.is_fixed_pitch());
        assert!(!fd.is_symbolic());
    }

    #[test]
    fn test_substitute_font() {
        let sf = SubstituteFont::lookup(true, false, true, false);
        assert_eq!(sf.name, "Courier-Bold");

        let sf2 = SubstituteFont::lookup(false, true, false, true);
        assert_eq!(sf2.name, "Times-Italic");

        let sf3 = SubstituteFont::lookup(false, false, false, false);
        assert_eq!(sf3.name, "Helvetica");
    }

    #[test]
    fn test_ffi_lifecycle() {
        let ctx = 0;

        let font = pdf_new_font_desc(ctx);
        assert!(font > 0);

        let kept = pdf_keep_font(ctx, font);
        assert_eq!(kept, font);

        pdf_drop_font(ctx, font);
        pdf_drop_font(ctx, font);
    }

    #[test]
    fn test_ffi_properties() {
        let ctx = 0;

        let font = pdf_new_font_desc(ctx);

        // Set name
        let name = CString::new("Times-Roman").unwrap();
        pdf_set_font_name(ctx, font, name.as_ptr());

        // Get name
        let got_name = pdf_font_name(ctx, font);
        assert!(!got_name.is_null());
        unsafe {
            assert_eq!(CStr::from_ptr(got_name).to_str().unwrap(), "Times-Roman");
            pdf_font_free_string(ctx, got_name as *mut c_char);
        }

        // Set flags
        pdf_set_font_flags(ctx, font, PDF_FD_SERIF | PDF_FD_ITALIC);
        assert_eq!(pdf_font_flags(ctx, font), PDF_FD_SERIF | PDF_FD_ITALIC);

        pdf_drop_font(ctx, font);
    }

    #[test]
    fn test_ffi_wmode() {
        let ctx = 0;

        let font = pdf_new_font_desc(ctx);
        assert_eq!(pdf_font_wmode(ctx, font), 0);

        pdf_set_font_wmode(ctx, font, 1);
        assert_eq!(pdf_font_wmode(ctx, font), 1);

        pdf_drop_font(ctx, font);
    }

    #[test]
    fn test_ffi_metrics() {
        let ctx = 0;

        let font = pdf_new_font_desc(ctx);

        pdf_set_default_hmtx(ctx, font, 1000);
        pdf_add_hmtx(ctx, font, 0, 127, 500);
        pdf_end_hmtx(ctx, font);

        let m = pdf_lookup_hmtx(ctx, font, 65);
        assert_eq!(m.w, 500);

        pdf_drop_font(ctx, font);
    }

    #[test]
    fn test_ffi_cid_mapping() {
        let ctx = 0;

        let font = pdf_new_font_desc(ctx);

        let gid_table: Vec<u16> = vec![0, 1, 2, 100, 101, 102];
        pdf_set_cid_to_gid(ctx, font, gid_table.as_ptr(), gid_table.len());

        assert_eq!(pdf_font_cid_to_gid(ctx, font, 3), 100);

        pdf_drop_font(ctx, font);
    }

    #[test]
    fn test_ffi_load_font() {
        let ctx = 0;
        let doc = 0;
        let rdb = 0;
        let obj = 0;

        let font = pdf_load_font(ctx, doc, rdb, obj);
        assert!(font > 0);

        pdf_drop_font(ctx, font);
    }

    #[test]
    fn test_ffi_type3_font() {
        let ctx = 0;
        let doc = 0;

        let font = pdf_load_type3_font(ctx, doc, 0, 0);
        assert!(font > 0);

        pdf_load_type3_glyphs(ctx, doc, font);

        pdf_drop_font(ctx, font);
    }

    #[test]
    fn test_clean_font_name() {
        let name = CString::new("AAAAAA+Helvetica").unwrap();
        let cleaned = pdf_clean_font_name(name.as_ptr());
        assert!(!cleaned.is_null());
        unsafe {
            assert_eq!(CStr::from_ptr(cleaned).to_str().unwrap(), "Helvetica");
            pdf_font_free_string(0, cleaned as *mut c_char);
        }
    }

    #[test]
    fn test_hail_mary_font() {
        let ctx = 0;
        let doc = 0;

        let font = pdf_load_hail_mary_font(ctx, doc);
        assert!(font > 0);

        let name = pdf_font_name(ctx, font);
        unsafe {
            assert_eq!(CStr::from_ptr(name).to_str().unwrap(), "Helvetica");
            pdf_font_free_string(ctx, name as *mut c_char);
        }

        pdf_drop_font(ctx, font);
    }
}
