//! PDF text rendering pipeline
//!
//! Provides text state tracking, glyph positioning, and text object construction
//! for PDF content stream interpretation.

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

/// PDF text rendering mode (Tr operator)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum TextRenderingMode {
    Fill = 0,
    Stroke = 1,
    FillStroke = 2,
    Invisible = 3,
    FillClip = 4,
    StrokeClip = 5,
    FillStrokeClip = 6,
    Clip = 7,
}

impl TextRenderingMode {
    /// Convert integer to TextRenderingMode (defaults to Fill for invalid values)
    pub fn from_i32(mode: i32) -> Self {
        match mode {
            0 => TextRenderingMode::Fill,
            1 => TextRenderingMode::Stroke,
            2 => TextRenderingMode::FillStroke,
            3 => TextRenderingMode::Invisible,
            4 => TextRenderingMode::FillClip,
            5 => TextRenderingMode::StrokeClip,
            6 => TextRenderingMode::FillStrokeClip,
            7 => TextRenderingMode::Clip,
            _ => TextRenderingMode::Fill,
        }
    }
}

/// Tracks all text state parameters for PDF text objects
#[derive(Debug, Clone)]
pub struct TextState {
    /// Current font resource name
    pub font_name: String,
    /// Current font size (Tf)
    pub font_size: f64,
    /// Character spacing (Tc, default 0)
    pub char_spacing: f64,
    /// Word spacing (Tw, default 0)
    pub word_spacing: f64,
    /// Horizontal scaling percentage (Tz, default 100)
    pub horizontal_scaling: f64,
    /// Leading (TL, default 0)
    pub leading: f64,
    /// Text rise (Ts, default 0)
    pub rise: f64,
    /// Rendering mode (Tr, default Fill)
    pub rendering_mode: TextRenderingMode,
    /// Current text matrix (Tm)
    pub text_matrix: [f64; 6],
    /// Start-of-line matrix
    pub text_line_matrix: [f64; 6],
    /// Knockout flag
    pub knockout: bool,
}

/// A positioned glyph from text showing operators
#[derive(Debug, Clone)]
pub struct GlyphPosition {
    /// Character code
    pub char_code: u32,
    /// Unicode string for this glyph (if known)
    pub unicode: Option<String>,
    /// X position in text space
    pub x: f64,
    /// Y position in text space
    pub y: f64,
    /// Advance width in text space
    pub width: f64,
    /// Font size at render time
    pub font_size: f64,
    /// Font resource name
    pub font_name: String,
}

/// Collection of glyphs from one BT..ET block
#[derive(Debug, Clone)]
pub struct TextObject {
    /// Positioned glyphs
    pub glyphs: Vec<GlyphPosition>,
    /// Rendering mode for the block
    pub rendering_mode: TextRenderingMode,
}

// ============================================================================
// Matrix Helpers
// ============================================================================

/// Returns identity matrix [1, 0, 0, 1, 0, 0]
pub fn identity_matrix() -> [f64; 6] {
    [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
}

/// Returns translation matrix for (tx, ty)
pub fn translate_matrix(tx: f64, ty: f64) -> [f64; 6] {
    [1.0, 0.0, 0.0, 1.0, tx, ty]
}

/// Multiply two 2D affine matrices. Format: [a, b, c, d, e, f]
/// Point transform: x' = a*x + c*y + e, y' = b*x + d*y + f
pub fn multiply_matrix(a: &[f64; 6], b: &[f64; 6]) -> [f64; 6] {
    [
        a[0] * b[0] + a[1] * b[2],
        a[0] * b[1] + a[1] * b[3],
        a[2] * b[0] + a[3] * b[2],
        a[2] * b[1] + a[3] * b[3],
        a[4] * b[0] + a[5] * b[2] + b[4],
        a[4] * b[1] + a[5] * b[3] + b[5],
    ]
}

/// Transform point (x, y) by matrix [a, b, c, d, e, f]
fn transform_point(m: &[f64; 6], x: f64, y: f64) -> (f64, f64) {
    let x_out = m[0] * x + m[2] * y + m[4];
    let y_out = m[1] * x + m[3] * y + m[5];
    (x_out, y_out)
}

// ============================================================================
// TextState Implementation
// ============================================================================

impl TextState {
    /// Create default text state
    pub fn new() -> Self {
        Self {
            font_name: String::new(),
            font_size: 0.0,
            char_spacing: 0.0,
            word_spacing: 0.0,
            horizontal_scaling: 100.0,
            leading: 0.0,
            rise: 0.0,
            rendering_mode: TextRenderingMode::Fill,
            text_matrix: identity_matrix(),
            text_line_matrix: identity_matrix(),
            knockout: true,
        }
    }

    /// Reset Tm and line matrix to identity (BT operator)
    pub fn reset_text_matrix(&mut self) {
        self.text_matrix = identity_matrix();
        self.text_line_matrix = identity_matrix();
    }

    /// Set font and size (Tf operator)
    pub fn set_font(&mut self, name: &str, size: f64) {
        self.font_name = name.to_string();
        self.font_size = size;
    }

    /// Set character spacing (Tc operator)
    pub fn set_char_spacing(&mut self, spacing: f64) {
        self.char_spacing = spacing;
    }

    /// Set word spacing (Tw operator)
    pub fn set_word_spacing(&mut self, spacing: f64) {
        self.word_spacing = spacing;
    }

    /// Set horizontal scaling percentage (Tz operator)
    pub fn set_horizontal_scaling(&mut self, scale: f64) {
        self.horizontal_scaling = scale;
    }

    /// Set leading (TL operator)
    pub fn set_leading(&mut self, leading: f64) {
        self.leading = leading;
    }

    /// Set text rise (Ts operator)
    pub fn set_rise(&mut self, rise: f64) {
        self.rise = rise;
    }

    /// Set rendering mode (Tr operator)
    pub fn set_rendering_mode(&mut self, mode: i32) {
        self.rendering_mode = TextRenderingMode::from_i32(mode);
    }

    /// Set text matrix (Tm operator)
    pub fn set_text_matrix(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
        self.text_matrix = [a, b, c, d, e, f];
        self.text_line_matrix = [a, b, c, d, e, f];
    }

    /// Move text position (Td operator): translate line matrix and set text matrix
    pub fn move_to(&mut self, tx: f64, ty: f64) {
        let t = translate_matrix(tx, ty);
        self.text_line_matrix = multiply_matrix(&self.text_line_matrix, &t);
        self.text_matrix = self.text_line_matrix;
    }

    /// Move to next line (T* operator): equivalent to move_to(0, -leading)
    pub fn move_to_next_line(&mut self) {
        self.move_to(0.0, -self.leading);
    }

    /// Move and set leading (TD operator): set leading = -ty, then move_to(tx, ty)
    pub fn move_to_and_set_leading(&mut self, tx: f64, ty: f64) {
        self.leading = -ty;
        self.move_to(tx, ty);
    }

    /// Process Tj operator: show text string, return positioned glyphs.
    ///
    /// # Parameters
    /// * `units_per_em` - Font design units per em square (default 1000 for Type1/CFF;
    ///   TrueType often uses 2048). Used to scale glyph widths to text space.
    ///
    /// # Unicode mapping limitation
    /// PDF character codes are NOT Unicode codepoints. This function currently
    /// treats each byte as a Unicode code point via `char::from_u32()`, which is
    /// only correct for Latin-1/WinAnsi-encoded text. For other encodings or
    /// fonts with ToUnicode CMap, a proper encoding/ToUnicode mapping must be
    /// applied by the caller before or after glyph extraction.
    ///
    /// TODO: Accept optional `encoding: Option<impl Fn(u32) -> Option<String>>`
    /// for proper character code to Unicode mapping.
    pub fn process_show_text<F>(
        &mut self,
        text: &[u8],
        glyph_width_fn: F,
        units_per_em: Option<f64>,
    ) -> Vec<GlyphPosition>
    where
        F: Fn(u32) -> f64,
    {
        let units = units_per_em.unwrap_or(1000.0);
        let mut glyphs = Vec::with_capacity(text.len());
        let scale = self.horizontal_scaling / 100.0;

        for &byte in text {
            let char_code = byte as u32;

            // Position from current text matrix (0, 0) in glyph space
            let (gx, gy) = transform_point(&self.text_matrix, 0.0, 0.0);
            let x = gx;
            let y = gy + self.rise;

            let width_units = glyph_width_fn(char_code);
            let advance = (width_units / units * self.font_size + self.char_spacing) * scale;
            let word_extra = if byte == 0x20 {
                self.word_spacing * scale
            } else {
                0.0
            };
            let tx = advance + word_extra;

            glyphs.push(GlyphPosition {
                char_code,
                unicode: char::from_u32(char_code).map(|c| c.to_string()),
                x,
                y,
                width: width_units / units * self.font_size,
                font_size: self.font_size,
                font_name: self.font_name.clone(),
            });

            // Advance text matrix
            let t = translate_matrix(tx, 0.0);
            self.text_matrix = multiply_matrix(&self.text_matrix, &t);
        }

        glyphs
    }

    /// Process TJ operator: mixed strings and position adjustments.
    ///
    /// Per PDF spec (ISO 32000-1 §9.4.3): TJ array numbers are displacement in
    /// thousandths of a unit of text space. A negative number moves text RIGHT
    /// (advances), a positive number moves text LEFT (kerns). We negate the
    /// offset before applying to match this convention.
    pub fn process_show_text_adjusted<F>(
        &mut self,
        elements: &[(Option<Vec<u8>>, Option<f64>)],
        glyph_width_fn: F,
        units_per_em: Option<f64>,
    ) -> Vec<GlyphPosition>
    where
        F: Fn(u32) -> f64,
    {
        let capacity: usize = elements
            .iter()
            .filter_map(|(s, _)| s.as_ref())
            .map(|s| s.len())
            .sum();
        let mut all_glyphs = Vec::with_capacity(capacity);

        for (string_opt, number_opt) in elements {
            if let Some(text) = string_opt {
                let glyphs = self.process_show_text(text, &glyph_width_fn, units_per_em);
                all_glyphs.extend(glyphs);
            }
            if let Some(offset) = *number_opt {
                // TJ offset: negate per PDF spec. Negative offset → advance right,
                // positive offset → kern left.
                let scale = self.horizontal_scaling / 100.0;
                let tx = -offset / 1000.0 * self.font_size * scale;
                let t = translate_matrix(tx, 0.0);
                self.text_matrix = multiply_matrix(&self.text_matrix, &t);
            }
        }

        all_glyphs
    }
}

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

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

    #[test]
    fn test_text_state_default_values() {
        let ts = TextState::new();
        assert_eq!(ts.font_name, "");
        assert_eq!(ts.font_size, 0.0);
        assert_eq!(ts.char_spacing, 0.0);
        assert_eq!(ts.word_spacing, 0.0);
        assert_eq!(ts.horizontal_scaling, 100.0);
        assert_eq!(ts.leading, 0.0);
        assert_eq!(ts.rise, 0.0);
        assert!(matches!(ts.rendering_mode, TextRenderingMode::Fill));
        assert_eq!(ts.text_matrix, identity_matrix());
        assert_eq!(ts.text_line_matrix, identity_matrix());
        assert!(ts.knockout);
    }

    #[test]
    fn test_tf_operator_sets_font_and_size() {
        let mut ts = TextState::new();
        ts.set_font("F1", 12.0);
        assert_eq!(ts.font_name, "F1");
        assert_eq!(ts.font_size, 12.0);
    }

    #[test]
    fn test_td_moves_text_position() {
        let mut ts = TextState::new();
        ts.reset_text_matrix();
        ts.move_to(10.0, 20.0);
        assert_eq!(ts.text_matrix[4], 10.0);
        assert_eq!(ts.text_matrix[5], 20.0);
        ts.move_to(5.0, -5.0);
        assert_eq!(ts.text_matrix[4], 15.0);
        assert_eq!(ts.text_matrix[5], 15.0);
    }

    #[test]
    fn test_td_sets_leading_and_moves() {
        let mut ts = TextState::new();
        ts.reset_text_matrix();
        ts.set_leading(14.0);
        ts.move_to_and_set_leading(0.0, -14.0);
        assert_eq!(ts.leading, 14.0);
        assert_eq!(ts.text_matrix[5], -14.0);
    }

    #[test]
    fn test_t_star_uses_leading() {
        let mut ts = TextState::new();
        ts.reset_text_matrix();
        ts.set_leading(12.0);
        ts.move_to(0.0, 100.0);
        ts.move_to_next_line();
        assert_eq!(ts.text_matrix[5], 100.0 - 12.0);
    }

    #[test]
    fn test_tm_sets_arbitrary_matrix() {
        let mut ts = TextState::new();
        ts.set_text_matrix(2.0, 0.0, 0.0, 2.0, 50.0, 100.0);
        assert_eq!(ts.text_matrix[0], 2.0);
        assert_eq!(ts.text_matrix[3], 2.0);
        assert_eq!(ts.text_matrix[4], 50.0);
        assert_eq!(ts.text_matrix[5], 100.0);
    }

    #[test]
    fn test_tc_operator() {
        let mut ts = TextState::new();
        ts.set_char_spacing(1.5);
        assert_eq!(ts.char_spacing, 1.5);
    }

    #[test]
    fn test_tw_operator() {
        let mut ts = TextState::new();
        ts.set_word_spacing(3.0);
        assert_eq!(ts.word_spacing, 3.0);
    }

    #[test]
    fn test_tz_operator() {
        let mut ts = TextState::new();
        ts.set_horizontal_scaling(150.0);
        assert_eq!(ts.horizontal_scaling, 150.0);
    }

    #[test]
    fn test_tl_operator() {
        let mut ts = TextState::new();
        ts.set_leading(14.0);
        assert_eq!(ts.leading, 14.0);
    }

    #[test]
    fn test_ts_operator() {
        let mut ts = TextState::new();
        ts.set_rise(2.0);
        assert_eq!(ts.rise, 2.0);
    }

    #[test]
    fn test_tr_operator() {
        let mut ts = TextState::new();
        ts.set_rendering_mode(1);
        assert!(matches!(ts.rendering_mode, TextRenderingMode::Stroke));
        ts.set_rendering_mode(7);
        assert!(matches!(ts.rendering_mode, TextRenderingMode::Clip));
    }

    #[test]
    fn test_process_show_text_positions_glyphs() {
        let mut ts = TextState::new();
        ts.set_font("F1", 12.0);
        ts.reset_text_matrix();
        ts.set_text_matrix(1.0, 0.0, 0.0, 1.0, 100.0, 200.0);

        let glyph_width_fn = |_| 500.0; // 500/1000 = 0.5 units per glyph
        let text = b"AB";
        let glyphs = ts.process_show_text(text, &glyph_width_fn, None);

        assert_eq!(glyphs.len(), 2);
        assert_eq!(glyphs[0].x, 100.0);
        assert_eq!(glyphs[0].y, 200.0);
        assert_eq!(glyphs[0].char_code, b'A' as u32);

        // Advance: (500/1000 * 12 + 0) * 1 = 6.0
        assert!((glyphs[1].x - 106.0).abs() < 1e-10);
    }

    #[test]
    fn test_process_show_text_adjusted_mixed() {
        let mut ts = TextState::new();
        ts.set_font("F1", 10.0);
        ts.reset_text_matrix();

        let glyph_width_fn = |_| 400.0;
        let elements: Vec<(Option<Vec<u8>>, Option<f64>)> = vec![
            (Some(b"AB".to_vec()), None),
            (None, Some(-50.0)),
            (Some(b"C".to_vec()), None),
        ];
        let glyphs = ts.process_show_text_adjusted(&elements, &glyph_width_fn, None);

        assert_eq!(glyphs.len(), 3);
        // After "AB": x advanced by 4+4=8. Offset -50: per PDF spec, negate → move right by 0.5. "C" at 8.5
        assert!((glyphs[2].x - 8.5).abs() < 1e-10);
    }

    #[test]
    fn test_word_spacing_only_for_space() {
        let mut ts = TextState::new();
        ts.set_font("F1", 12.0);
        ts.set_word_spacing(10.0);
        ts.reset_text_matrix();

        let glyph_width_fn = |c| if c == 0x20 { 250.0 } else { 500.0 };
        let text = b"A B";
        let glyphs = ts.process_show_text(text, &glyph_width_fn, None);

        assert_eq!(glyphs.len(), 3);
        // A: advance 6 + 0 = 6
        // space: advance 3 + 10 = 13 (word spacing)
        // B: advance 6 + 0 = 6
        let x_a = glyphs[0].x;
        let x_space = glyphs[1].x;
        let x_b = glyphs[2].x;
        assert!((x_space - x_a - 6.0).abs() < 1e-10);
        assert!((x_b - x_space - 13.0).abs() < 1e-10);
    }

    #[test]
    fn test_horizontal_scaling_affects_advance() {
        let mut ts = TextState::new();
        ts.set_font("F1", 10.0);
        ts.set_horizontal_scaling(200.0);
        ts.reset_text_matrix();

        let glyph_width_fn = |_| 500.0;
        let text = b"A";
        let glyphs = ts.process_show_text(text, &glyph_width_fn, None);
        assert_eq!(glyphs.len(), 1);
        // Advance = (500/1000 * 10 + 0) * 2 = 10
        assert!((glyphs[0].width - 5.0).abs() < 1e-10);
        // Next position would be 10 to the right
        assert_eq!(ts.text_matrix[4], 10.0);
    }

    #[test]
    fn test_text_rise_offsets_y() {
        let mut ts = TextState::new();
        ts.set_font("F1", 12.0);
        ts.set_rise(3.0);
        ts.reset_text_matrix();

        let glyph_width_fn = |_| 500.0;
        let text = b"A";
        let glyphs = ts.process_show_text(text, &glyph_width_fn, None);
        assert_eq!(glyphs[0].y, 3.0);
    }

    #[test]
    fn test_matrix_multiplication() {
        let a = [2.0, 0.0, 0.0, 2.0, 10.0, 20.0];
        let b = [1.0, 0.0, 0.0, 1.0, 5.0, 5.0];
        let c = multiply_matrix(&a, &b);
        // Scale then translate: (x,y) -> (2x+10, 2y+20) then (+5,+5)
        // = (2x+10+5, 2y+20+5) for identity part... Actually concat is "a then b"
        // So point p -> a(p) -> b(a(p)). b is translate(5,5): (x,y)->(x+5,y+5)
        // a is scale(2,2) translate(10,20): (x,y)->(2x+10, 2y+20)
        // b(a(p)) = b(2x+10, 2y+20) = (2x+15, 2y+25)
        // So result matrix should be [2,0,0,2,15,25]
        assert_eq!(c[0], 2.0);
        assert_eq!(c[3], 2.0);
        assert_eq!(c[4], 15.0);
        assert_eq!(c[5], 25.0);
    }

    #[test]
    fn test_identity_matrix() {
        let m = identity_matrix();
        assert_eq!(m, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
        let (x, y) = transform_point(&m, 5.0, 10.0);
        assert_eq!(x, 5.0);
        assert_eq!(y, 10.0);
    }

    #[test]
    fn test_translate_matrix() {
        let m = translate_matrix(7.0, 11.0);
        assert_eq!(m[4], 7.0);
        assert_eq!(m[5], 11.0);
        let (x, y) = transform_point(&m, 0.0, 0.0);
        assert_eq!(x, 7.0);
        assert_eq!(y, 11.0);
    }

    #[test]
    fn test_text_rendering_mode_from_integer() {
        assert!(matches!(
            TextRenderingMode::from_i32(0),
            TextRenderingMode::Fill
        ));
        assert!(matches!(
            TextRenderingMode::from_i32(1),
            TextRenderingMode::Stroke
        ));
        assert!(matches!(
            TextRenderingMode::from_i32(3),
            TextRenderingMode::Invisible
        ));
        assert!(matches!(
            TextRenderingMode::from_i32(99),
            TextRenderingMode::Fill
        ));
    }

    #[test]
    fn test_reset_text_matrix() {
        let mut ts = TextState::new();
        ts.set_text_matrix(2.0, 0.0, 0.0, 2.0, 50.0, 100.0);
        ts.reset_text_matrix();
        assert_eq!(ts.text_matrix, identity_matrix());
        assert_eq!(ts.text_line_matrix, identity_matrix());
    }

    #[test]
    fn test_units_per_em_configurable() {
        let mut ts = TextState::new();
        ts.set_font("F1", 12.0);
        ts.reset_text_matrix();

        // TrueType font: 2048 units per em, glyph width 1024 = half em
        let glyph_width_fn = |_| 1024.0;
        let text = b"A";
        let glyphs = ts.process_show_text(text, &glyph_width_fn, Some(2048.0));
        assert_eq!(glyphs.len(), 1);
        // width = 1024/2048 * 12 = 6.0 (vs 12.24 if we wrongly used 1000)
        assert!((glyphs[0].width - 6.0).abs() < 1e-10);
        assert_eq!(ts.text_matrix[4], 6.0);
    }

    #[test]
    fn test_process_show_text_adjusted_with_offset() {
        let mut ts = TextState::new();
        ts.set_font("F1", 10.0);
        ts.reset_text_matrix();

        let glyph_width_fn = |_| 400.0;
        // TJ: [ "A" -50 "B" ] - per PDF spec, offset -50 negated → move right by 0.5
        let elements: Vec<(Option<Vec<u8>>, Option<f64>)> = vec![
            (Some(b"A".to_vec()), None),
            (None, Some(-50.0)),
            (Some(b"B".to_vec()), None),
        ];
        let glyphs = ts.process_show_text_adjusted(&elements, &glyph_width_fn, None);

        assert_eq!(glyphs.len(), 2);
        // After "A": x advanced by 4.0. After offset -50: negate → move right by 0.5
        // So "B" starts at 4.0 + 0.5 = 4.5
        assert!((glyphs[1].x - 4.5).abs() < 1e-10);
    }
}
