//! PDF Content Stream Interpreter
//!
//! This module implements the PDF content stream interpreter, which parses
//! PDF operators from a content stream and calls the appropriate device methods.
//!
//! The interpreter maintains a graphics state stack and handles:
//! - Path construction and painting
//! - Text positioning and rendering
//! - Color and colorspace operations
//! - Graphics state save/restore
//! - Transformations
//! - Clipping
//! - Images
//! - Form XObjects
//! - Transparency groups

use crate::fitz::colorspace::Colorspace;
use crate::fitz::device::{BlendMode, Device};
use crate::fitz::font::{CharMap, Font, FontType};
use crate::fitz::geometry::{Matrix, Point, Rect};
use crate::fitz::image::{Image, ImageFormat};
use crate::fitz::path::{LineCap, LineJoin, Path, StrokeState};
use crate::fitz::text::{Text, TextItem, TextSpan};
use crate::pdf::lexer::{LexBuf, Lexer, Token};
use crate::pdf::object::{Dict, Name, Object};
use crate::pdf::parser::hex_to_bytes;
use std::sync::Arc;

/// PDF graphics state
#[derive(Debug, Clone)]
pub struct GraphicsState {
    /// Current transformation matrix
    pub ctm: Matrix,

    /// Current line width
    pub line_width: f32,

    /// Current line cap style (0=butt, 1=round, 2=square)
    pub line_cap: i32,

    /// Current line join style (0=miter, 1=round, 2=bevel)
    pub line_join: i32,

    /// Current miter limit
    pub miter_limit: f32,

    /// Current dash pattern
    pub dash_pattern: Vec<f32>,
    pub dash_phase: f32,

    /// Current fill colorspace
    pub fill_colorspace: Colorspace,

    /// Current fill color components
    pub fill_color: Vec<f32>,

    /// Current stroke colorspace
    pub stroke_colorspace: Colorspace,

    /// Current stroke color components
    pub stroke_color: Vec<f32>,

    /// Current fill alpha
    pub fill_alpha: f32,

    /// Current stroke alpha
    pub stroke_alpha: f32,

    /// Current blend mode
    pub blend_mode: String,

    /// Current font and size
    pub font: Option<String>,
    pub font_size: f32,

    /// Current text matrix
    pub text_matrix: Matrix,

    /// Current text line matrix
    pub text_line_matrix: Matrix,

    /// Current character spacing
    pub char_spacing: f32,

    /// Current word spacing
    pub word_spacing: f32,

    /// Current horizontal scaling
    pub horizontal_scaling: f32,

    /// Current text leading
    pub leading: f32,

    /// Current text rendering mode
    pub text_render_mode: i32,

    /// Current text rise
    pub text_rise: f32,
}

impl Default for GraphicsState {
    fn default() -> Self {
        Self {
            ctm: Matrix::IDENTITY,
            line_width: 1.0,
            line_cap: 0,
            line_join: 0,
            miter_limit: 10.0,
            dash_pattern: Vec::new(),
            dash_phase: 0.0,
            fill_colorspace: Colorspace::device_gray(),
            fill_color: vec![0.0],
            stroke_colorspace: Colorspace::device_gray(),
            stroke_color: vec![0.0],
            fill_alpha: 1.0,
            stroke_alpha: 1.0,
            blend_mode: "Normal".to_string(),
            font: None,
            font_size: 0.0,
            text_matrix: Matrix::IDENTITY,
            text_line_matrix: Matrix::IDENTITY,
            char_spacing: 0.0,
            word_spacing: 0.0,
            horizontal_scaling: 100.0,
            leading: 0.0,
            text_render_mode: 0,
            text_rise: 0.0,
        }
    }
}

/// Pending clipping operation, set by W/W* and applied on next paint or `n`
#[derive(Debug, Clone, Copy, PartialEq)]
enum PendingClip {
    NonZero,
    EvenOdd,
}

/// PDF content stream interpreter
pub struct Interpreter {
    /// Graphics state stack
    state_stack: Vec<GraphicsState>,

    /// Current path being constructed
    current_path: Option<Path>,

    /// Current point in path
    current_point: Option<Point>,

    /// Resource dictionary
    resources: Option<Dict>,

    /// Pending clip set by W/W*, applied at next path paint op
    pending_clip: Option<PendingClip>,

    /// Inline image state: dictionary collected between BI and ID
    inline_image_dict: Option<Dict>,
}

impl Interpreter {
    /// Create a new interpreter
    pub fn new() -> Self {
        Self {
            state_stack: vec![GraphicsState::default()],
            current_path: None,
            current_point: None,
            resources: None,
            pending_clip: None,
            inline_image_dict: None,
        }
    }

    /// Set the resource dictionary
    pub fn set_resources(&mut self, resources: Dict) {
        self.resources = Some(resources);
    }

    /// Look up a named resource from a sub-dictionary (e.g., /ExtGState, /XObject, /ColorSpace)
    fn lookup_resource(&self, category: &str, name: &str) -> Option<&Object> {
        let resources = self.resources.as_ref()?;
        let cat = resources.get(&Name::new(category))?;
        let cat_dict = cat.as_dict()?;
        cat_dict.get(&Name::new(name))
    }

    /// Resolve a font name from the resources dictionary into a usable Font.
    /// Returns None if the font is not found, is an indirect reference (unresolved),
    /// or cannot be parsed.
    fn resolve_font(&self, font_name: &str) -> Option<Arc<Font>> {
        let font_obj = self.lookup_resource("Font", font_name)?;
        let font_dict = match font_obj {
            Object::Dict(d) => d,
            Object::Ref(_) => return None, // Indirect ref - would need document resolver
            _ => return None,
        };

        let base_font = font_dict
            .get(&Name::new("BaseFont"))
            .and_then(|o| o.as_name())
            .map(|n| n.as_str().to_string())
            .unwrap_or_else(|| font_name.to_string());

        let subtype = font_dict
            .get(&Name::new("Subtype"))
            .and_then(|o| o.as_name())
            .map(|n| n.as_str().to_string())
            .unwrap_or_default();

        let font_type = match subtype.as_str() {
            "Type1" => FontType::Type1,
            "TrueType" => FontType::TrueType,
            "Type0" => FontType::CIDFontType2,
            "Type3" => FontType::Type3,
            _ => FontType::Unknown,
        };

        let mut font = Font::with_type(&base_font, font_type);

        // Extract widths from /FirstChar, /LastChar, /Widths array
        if let (Some(first), Some(last), Some(widths_obj)) = (
            font_dict
                .get(&Name::new("FirstChar"))
                .and_then(|o| o.as_int()),
            font_dict
                .get(&Name::new("LastChar"))
                .and_then(|o| o.as_int()),
            font_dict
                .get(&Name::new("Widths"))
                .and_then(|o| o.as_array()),
        ) {
            for (i, w) in widths_obj.iter().enumerate() {
                if let Some(width) = w.as_real().or_else(|| w.as_int().map(|i| i as f64)) {
                    let char_code = first as u32 + i as u32;
                    font.set_glyph_advance(char_code as u16, width as f32 / 1000.0);
                }
            }
        }

        // Add identity charmap for character codes in FirstChar..LastChar range
        // so char code maps to itself as glyph ID (for simple fonts)
        if let (Some(first), Some(last)) = (
            font_dict
                .get(&Name::new("FirstChar"))
                .and_then(|o| o.as_int()),
            font_dict
                .get(&Name::new("LastChar"))
                .and_then(|o| o.as_int()),
        ) {
            let mut charmap = CharMap::new();
            for code in first..=last {
                if code >= 0 {
                    charmap.add_mapping(code as u32, code as u16);
                }
            }
            font.set_charmap(charmap);
        }

        Some(Arc::new(font))
    }

    /// Resolve a colorspace name from operands.
    /// Handles device names (DeviceGray, DeviceRGB, DeviceCMYK) directly,
    /// and looks up named colorspaces from the /ColorSpace resource dict.
    fn resolve_colorspace(&self, operands: &[Object]) -> Option<Colorspace> {
        let name_obj = operands.first()?;
        let cs_name = match name_obj {
            Object::Name(n) => n.as_str(),
            _ => return None,
        };
        match cs_name {
            "DeviceGray" | "G" => Some(Colorspace::device_gray()),
            "DeviceRGB" | "RGB" => Some(Colorspace::device_rgb()),
            "DeviceCMYK" | "CMYK" => Some(Colorspace::device_cmyk()),
            _ => {
                if let Some(obj) = self.lookup_resource("ColorSpace", cs_name) {
                    Self::colorspace_from_object(obj)
                } else {
                    None
                }
            }
        }
    }

    /// Build a Colorspace from a PDF colorspace object (array or name).
    fn colorspace_from_object(obj: &Object) -> Option<Colorspace> {
        match obj {
            Object::Name(n) => match n.as_str() {
                "DeviceGray" | "G" => Some(Colorspace::device_gray()),
                "DeviceRGB" | "RGB" => Some(Colorspace::device_rgb()),
                "DeviceCMYK" | "CMYK" => Some(Colorspace::device_cmyk()),
                _ => None,
            },
            Object::Array(arr) if !arr.is_empty() => {
                let cs_type = arr[0].as_name()?;
                match cs_type.as_str() {
                    "ICCBased" => {
                        if arr.len() > 1 {
                            if let Object::Stream { dict, .. } = &arr[1] {
                                let n = if let Some(n_val) =
                                    dict.get(&Name::new("N")).and_then(|o| o.as_int())
                                {
                                    n_val as u8
                                } else if let Some(alt) = dict
                                    .get(&Name::new("Alternate"))
                                    .and_then(|o| Self::colorspace_from_object(o))
                                {
                                    alt.n()
                                } else {
                                    eprintln!(
                                        "ICCBased colorspace missing /N parameter, defaulting to 3"
                                    );
                                    3
                                };
                                return Some(Colorspace::new_custom("ICCBased", n));
                            }
                        }
                        Some(Colorspace::device_rgb())
                    }
                    "CalGray" => Some(Colorspace::device_gray()),
                    "CalRGB" => Some(Colorspace::device_rgb()),
                    "Lab" => Some(Colorspace::new_custom("Lab", 3)),
                    "Indexed" | "I" => {
                        if arr.len() > 1 {
                            if let Some(base) = Self::colorspace_from_object(&arr[1]) {
                                return Some(Colorspace::new_custom(
                                    &format!("Indexed({})", base.name()),
                                    1,
                                ));
                            }
                        }
                        Some(Colorspace::new_custom("Indexed", 1))
                    }
                    "Separation" => Some(Colorspace::new_custom("Separation", 1)),
                    "DeviceN" => {
                        let n = if arr.len() > 1 {
                            arr[1].as_array().map_or(1, |names| names.len() as u8)
                        } else {
                            1
                        };
                        Some(Colorspace::new_custom("DeviceN", n))
                    }
                    "Pattern" => None,
                    _ => None,
                }
            }
            _ => None,
        }
    }

    /// Get the current graphics state
    fn state(&self) -> &GraphicsState {
        self.state_stack.last().unwrap()
    }

    /// Get the current graphics state (mutable)
    fn state_mut(&mut self) -> &mut GraphicsState {
        self.state_stack.last_mut().unwrap()
    }

    /// Push a new graphics state (q operator)
    fn push_state(&mut self) {
        let current = self.state().clone();
        self.state_stack.push(current);
    }

    /// Pop a graphics state (Q operator)
    fn pop_state(&mut self) {
        if self.state_stack.len() > 1 {
            self.state_stack.pop();
        }
    }

    /// Apply any pending clip to the device, then clear it.
    /// Called before path painting ops (S, f, B, etc.) and `n`.
    fn apply_pending_clip<D: Device>(&mut self, device: &mut D) {
        if let Some(clip) = self.pending_clip.take() {
            if let Some(ref path) = self.current_path {
                let even_odd = clip == PendingClip::EvenOdd;
                let ctm = self.state().ctm;
                let scissor = Rect::INFINITE;
                device.clip_path(path, even_odd, &ctm, scissor);
            }
        }
    }

    /// Interpret a content stream and call device methods
    pub fn interpret<D: Device>(&mut self, stream: &[u8], device: &mut D) -> Result<(), String> {
        let mut lexer = Lexer::new(stream);
        let mut buf = LexBuf::new();
        let mut operands: Vec<Object> = Vec::new();

        loop {
            match lexer.lex(&mut buf) {
                Ok(Token::Eof) => break,
                Ok(Token::Keyword) => {
                    let op = buf.as_str();
                    if op == "BI" {
                        operands.clear();
                        self.handle_inline_image(&mut lexer, &mut buf, device);
                        continue;
                    }
                    self.process_operator(op, &operands, device)?;
                    operands.clear();
                }
                Ok(Token::Int) => {
                    operands.push(Object::Int(buf.as_int()));
                }
                Ok(Token::Real) => {
                    operands.push(Object::Real(buf.as_float()));
                }
                Ok(Token::String) => {
                    operands.push(Object::String(crate::pdf::object::PdfString::new(
                        buf.as_str().as_bytes().to_vec(),
                    )));
                }
                Ok(Token::HexString) => {
                    operands.push(Object::String(crate::pdf::object::PdfString::new(
                        hex_to_bytes(buf.as_str()),
                    )));
                }
                Ok(Token::Name) => {
                    operands.push(Object::Name(crate::pdf::object::Name::new(buf.as_str())));
                }
                Ok(Token::True) => {
                    operands.push(Object::Bool(true));
                }
                Ok(Token::False) => {
                    operands.push(Object::Bool(false));
                }
                Ok(Token::Null) => {
                    operands.push(Object::Null);
                }
                Ok(Token::OpenArray) => {
                    // Parse array
                    let array = self.parse_array(&mut lexer, &mut buf)?;
                    operands.push(Object::Array(array));
                }
                Ok(Token::OpenDict) => {
                    // Parse dictionary
                    let dict = self.parse_dict(&mut lexer, &mut buf)?;
                    operands.push(Object::Dict(dict));
                }
                Ok(_) => {
                    // Skip other tokens
                }
                Err(e) => return Err(format!("Lexer error: {}", e)),
            }
        }

        Ok(())
    }

    /// Parse an array from the token stream
    fn parse_array(&self, lexer: &mut Lexer, buf: &mut LexBuf) -> Result<Vec<Object>, String> {
        let mut array = Vec::new();

        loop {
            match lexer.lex(buf) {
                Ok(Token::CloseArray) => break,
                Ok(Token::Int) => array.push(Object::Int(buf.as_int())),
                Ok(Token::Real) => array.push(Object::Real(buf.as_float())),
                Ok(Token::Name) => {
                    array.push(Object::Name(crate::pdf::object::Name::new(buf.as_str())))
                }
                Ok(Token::String) => array.push(Object::String(
                    crate::pdf::object::PdfString::new(buf.as_str().as_bytes().to_vec()),
                )),
                Ok(Token::HexString) => array.push(Object::String(
                    crate::pdf::object::PdfString::new(hex_to_bytes(buf.as_str())),
                )),
                Ok(Token::True) => array.push(Object::Bool(true)),
                Ok(Token::False) => array.push(Object::Bool(false)),
                Ok(Token::Null) => array.push(Object::Null),
                Ok(Token::Eof) => return Err("Unexpected end of stream in array".to_string()),
                Err(e) => return Err(format!("Error parsing array: {}", e)),
                _ => {} // Skip other tokens
            }
        }

        Ok(array)
    }

    /// Parse a dictionary from the token stream
    fn parse_dict(&self, lexer: &mut Lexer, buf: &mut LexBuf) -> Result<Dict, String> {
        let mut dict = Dict::new();

        loop {
            match lexer.lex(buf) {
                Ok(Token::CloseDict) => break,
                Ok(Token::Name) => {
                    let key = crate::pdf::object::Name::new(buf.as_str());

                    // Read value
                    match lexer.lex(buf) {
                        Ok(Token::Int) => {
                            dict.insert(key, Object::Int(buf.as_int()));
                        }
                        Ok(Token::Real) => {
                            dict.insert(key, Object::Real(buf.as_float()));
                        }
                        Ok(Token::Name) => {
                            dict.insert(
                                key,
                                Object::Name(crate::pdf::object::Name::new(buf.as_str())),
                            );
                        }
                        Ok(Token::String) => {
                            dict.insert(
                                key,
                                Object::String(crate::pdf::object::PdfString::new(
                                    buf.as_str().as_bytes().to_vec(),
                                )),
                            );
                        }
                        Ok(Token::HexString) => {
                            dict.insert(
                                key,
                                Object::String(crate::pdf::object::PdfString::new(hex_to_bytes(
                                    buf.as_str(),
                                ))),
                            );
                        }
                        Ok(Token::True) => {
                            dict.insert(key, Object::Bool(true));
                        }
                        Ok(Token::False) => {
                            dict.insert(key, Object::Bool(false));
                        }
                        Ok(Token::Null) => {
                            dict.insert(key, Object::Null);
                        }
                        _ => return Err(format!("Invalid value for key '{}'", key.as_str())),
                    }
                }
                Ok(Token::Eof) => return Err("Unexpected end of stream in dictionary".to_string()),
                Err(e) => return Err(format!("Error parsing dictionary: {}", e)),
                _ => {} // Skip other tokens
            }
        }

        Ok(dict)
    }

    /// Process a single PDF operator
    fn process_operator<D: Device>(
        &mut self,
        op: &str,
        operands: &[Object],
        device: &mut D,
    ) -> Result<(), String> {
        match op {
            // Graphics state operators
            "q" => self.op_save_state(),
            "Q" => self.op_restore_state(),
            "cm" => self.op_concat_matrix(operands)?,
            "w" => self.op_set_line_width(operands)?,
            "J" => self.op_set_line_cap(operands)?,
            "j" => self.op_set_line_join(operands)?,
            "M" => self.op_set_miter_limit(operands)?,
            "d" => self.op_set_dash(operands)?,
            "gs" => self.op_set_gstate(operands)?,

            // Path construction operators
            "m" => self.op_move_to(operands)?,
            "l" => self.op_line_to(operands)?,
            "c" => self.op_curve_to(operands)?,
            "v" => self.op_curve_to_v(operands)?,
            "y" => self.op_curve_to_y(operands)?,
            "h" => self.op_close_path(),
            "re" => self.op_rectangle(operands)?,

            // Path painting operators
            "S" => self.op_stroke(device)?,
            "s" => self.op_close_and_stroke(device)?,
            "f" | "F" => self.op_fill(device)?,
            "f*" => self.op_fill_even_odd(device)?,
            "B" => self.op_fill_and_stroke(device)?,
            "B*" => self.op_fill_and_stroke_even_odd(device)?,
            "b" => self.op_close_fill_and_stroke(device)?,
            "b*" => self.op_close_fill_and_stroke_even_odd(device)?,
            "n" => self.op_end_path(device),

            // Clipping path operators
            "W" => self.op_clip(),
            "W*" => self.op_clip_even_odd(),

            // Color operators
            "CS" => self.op_set_stroke_colorspace(operands)?,
            "cs" => self.op_set_fill_colorspace(operands)?,
            "SC" | "SCN" => self.op_set_stroke_color(operands)?,
            "sc" | "scn" => self.op_set_fill_color(operands)?,
            "G" => self.op_set_stroke_gray(operands)?,
            "g" => self.op_set_fill_gray(operands)?,
            "RG" => self.op_set_stroke_rgb(operands)?,
            "rg" => self.op_set_fill_rgb(operands)?,
            "K" => self.op_set_stroke_cmyk(operands)?,
            "k" => self.op_set_fill_cmyk(operands)?,

            // Text object operators
            "BT" => self.op_begin_text(),
            "ET" => self.op_end_text(),

            // Text positioning operators
            "Td" => self.op_text_move(operands)?,
            "TD" => self.op_text_move_set_leading(operands)?,
            "Tm" => self.op_text_set_matrix(operands)?,
            "T*" => self.op_text_next_line(),

            // Text state operators
            "Tc" => self.op_set_char_spacing(operands)?,
            "Tw" => self.op_set_word_spacing(operands)?,
            "Tz" => self.op_set_horizontal_scaling(operands)?,
            "TL" => self.op_set_leading(operands)?,
            "Tf" => self.op_set_font(operands)?,
            "Tr" => self.op_set_text_render_mode(operands)?,
            "Ts" => self.op_set_text_rise(operands)?,

            // Text showing operators
            "Tj" => self.op_show_text(operands, device)?,
            "TJ" => self.op_show_text_adjusted(operands, device)?,
            "'" => self.op_show_text_next_line(operands, device)?,
            "\"" => self.op_show_text_next_line_with_spacing(operands, device)?,

            // XObject operators
            "Do" => self.op_paint_xobject(operands, device)?,

            // BI/ID/EI handled directly in interpret() loop
            "BI" | "ID" | "EI" => {}

            // Marked content operators
            "MP" => self.op_marked_content_point(operands)?,
            "DP" => self.op_marked_content_point_with_props(operands)?,
            "BMC" => self.op_begin_marked_content(operands)?,
            "BDC" => self.op_begin_marked_content_with_props(operands)?,
            "EMC" => self.op_end_marked_content()?,

            // Compatibility operators
            "BX" => self.op_begin_compat(),
            "EX" => self.op_end_compat(),

            // Shading operator
            "sh" => self.op_shade(operands, device)?,

            _ => {
                // Unknown operator - log but don't fail
                // This allows forward compatibility with newer PDF versions
            }
        }

        Ok(())
    }

    // ========================================================================
    // Graphics State Operators
    // ========================================================================

    fn op_save_state(&mut self) {
        self.push_state();
    }

    fn op_restore_state(&mut self) {
        self.pop_state();
    }

    fn op_concat_matrix(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 6 {
            return Err(format!(
                "cm operator requires 6 operands, got {}",
                operands.len()
            ));
        }

        let a = get_f32(&operands[0])?;
        let b = get_f32(&operands[1])?;
        let c = get_f32(&operands[2])?;
        let d = get_f32(&operands[3])?;
        let e = get_f32(&operands[4])?;
        let f = get_f32(&operands[5])?;

        let matrix = Matrix::new(a, b, c, d, e, f);
        let state = self.state_mut();
        state.ctm = state.ctm.concat(&matrix);

        Ok(())
    }

    fn op_set_line_width(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 1 {
            return Err("w operator requires 1 operand".to_string());
        }

        let width = get_f32(&operands[0])?;
        self.state_mut().line_width = width;

        Ok(())
    }

    fn op_set_line_cap(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 1 {
            return Err("J operator requires 1 operand".to_string());
        }

        let cap = get_i32(&operands[0])?;
        self.state_mut().line_cap = cap;

        Ok(())
    }

    fn op_set_line_join(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 1 {
            return Err("j operator requires 1 operand".to_string());
        }

        let join = get_i32(&operands[0])?;
        self.state_mut().line_join = join;

        Ok(())
    }

    fn op_set_miter_limit(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 1 {
            return Err("M operator requires 1 operand".to_string());
        }

        let limit = get_f32(&operands[0])?;
        self.state_mut().miter_limit = limit;

        Ok(())
    }

    fn op_set_dash(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 2 {
            return Err("d operator requires 2 operands".to_string());
        }

        let pattern = match &operands[0] {
            Object::Array(arr) => arr.iter().filter_map(|obj| get_f32(obj).ok()).collect(),
            _ => return Err("Invalid dash pattern".to_string()),
        };

        let phase = get_f32(&operands[1])?;

        let state = self.state_mut();
        state.dash_pattern = pattern;
        state.dash_phase = phase;

        Ok(())
    }

    fn op_set_gstate(&mut self, operands: &[Object]) -> Result<(), String> {
        let gs_name = operands
            .first()
            .and_then(|o| o.as_name())
            .map(|n| n.as_str().to_string());
        let gs_name = match gs_name {
            Some(n) => n,
            None => return Ok(()),
        };

        let gs_dict = self
            .lookup_resource("ExtGState", &gs_name)
            .and_then(|o| o.as_dict())
            .cloned();

        if let Some(dict) = gs_dict {
            let state = self.state_mut();

            if let Some(ca) = dict.get(&Name::new("CA")).and_then(|o| o.as_real()) {
                state.stroke_alpha = ca as f32;
            }
            if let Some(ca) = dict.get(&Name::new("ca")).and_then(|o| o.as_real()) {
                state.fill_alpha = ca as f32;
            }
            if let Some(bm) = dict.get(&Name::new("BM")).and_then(|o| o.as_name()) {
                state.blend_mode = bm.as_str().to_string();
            }
            if let Some(lw) = dict.get(&Name::new("LW")).and_then(|o| o.as_real()) {
                state.line_width = lw as f32;
            }
            if let Some(lc) = dict.get(&Name::new("LC")).and_then(|o| o.as_int()) {
                state.line_cap = lc as i32;
            }
            if let Some(lj) = dict.get(&Name::new("LJ")).and_then(|o| o.as_int()) {
                state.line_join = lj as i32;
            }
            if let Some(ml) = dict.get(&Name::new("ML")).and_then(|o| o.as_real()) {
                state.miter_limit = ml as f32;
            }
        }
        Ok(())
    }

    // ========================================================================
    // Path Construction Operators
    // ========================================================================

    fn op_move_to(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 2 {
            return Err("m operator requires 2 operands".to_string());
        }

        let x = get_f32(&operands[0])?;
        let y = get_f32(&operands[1])?;

        if self.current_path.is_none() {
            self.current_path = Some(Path::new());
        }

        if let Some(ref mut path) = self.current_path {
            path.move_to(Point::new(x, y));
        }

        self.current_point = Some(Point::new(x, y));

        Ok(())
    }

    fn op_line_to(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 2 {
            return Err("l operator requires 2 operands".to_string());
        }

        let x = get_f32(&operands[0])?;
        let y = get_f32(&operands[1])?;

        if let Some(ref mut path) = self.current_path {
            path.line_to(Point::new(x, y));
        }

        self.current_point = Some(Point::new(x, y));

        Ok(())
    }

    fn op_curve_to(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 6 {
            return Err("c operator requires 6 operands".to_string());
        }

        let x1 = get_f32(&operands[0])?;
        let y1 = get_f32(&operands[1])?;
        let x2 = get_f32(&operands[2])?;
        let y2 = get_f32(&operands[3])?;
        let x3 = get_f32(&operands[4])?;
        let y3 = get_f32(&operands[5])?;

        if let Some(ref mut path) = self.current_path {
            path.curve_to(Point::new(x1, y1), Point::new(x2, y2), Point::new(x3, y3));
        }

        self.current_point = Some(Point::new(x3, y3));

        Ok(())
    }

    fn op_curve_to_v(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 4 {
            return Err("v operator requires 4 operands".to_string());
        }

        let current = self
            .current_point
            .ok_or("No current point for v operator")?;

        let x2 = get_f32(&operands[0])?;
        let y2 = get_f32(&operands[1])?;
        let x3 = get_f32(&operands[2])?;
        let y3 = get_f32(&operands[3])?;

        if let Some(ref mut path) = self.current_path {
            path.curve_to(current, Point::new(x2, y2), Point::new(x3, y3));
        }

        self.current_point = Some(Point::new(x3, y3));

        Ok(())
    }

    fn op_curve_to_y(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 4 {
            return Err("y operator requires 4 operands".to_string());
        }

        let x1 = get_f32(&operands[0])?;
        let y1 = get_f32(&operands[1])?;
        let x3 = get_f32(&operands[2])?;
        let y3 = get_f32(&operands[3])?;

        if let Some(ref mut path) = self.current_path {
            path.curve_to(Point::new(x1, y1), Point::new(x3, y3), Point::new(x3, y3));
        }

        self.current_point = Some(Point::new(x3, y3));

        Ok(())
    }

    fn op_close_path(&mut self) {
        if let Some(ref mut path) = self.current_path {
            path.close();
        }
    }

    fn op_rectangle(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 4 {
            return Err("re operator requires 4 operands".to_string());
        }

        let x = get_f32(&operands[0])?;
        let y = get_f32(&operands[1])?;
        let w = get_f32(&operands[2])?;
        let h = get_f32(&operands[3])?;

        if self.current_path.is_none() {
            self.current_path = Some(Path::new());
        }

        if let Some(ref mut path) = self.current_path {
            path.move_to(Point::new(x, y));
            path.line_to(Point::new(x + w, y));
            path.line_to(Point::new(x + w, y + h));
            path.line_to(Point::new(x, y + h));
            path.close();
        }

        self.current_point = Some(Point::new(x, y));

        Ok(())
    }

    // ========================================================================
    // Path Painting Operators
    // ========================================================================

    fn op_stroke<D: Device>(&mut self, device: &mut D) -> Result<(), String> {
        self.apply_pending_clip(device);
        if let Some(path) = self.current_path.take() {
            let state = self.state();

            // Create stroke state
            let line_cap = line_cap_from_i32(state.line_cap);
            let stroke_state = crate::fitz::path::StrokeState {
                linewidth: state.line_width,
                miterlimit: state.miter_limit,
                start_cap: line_cap,
                dash_cap: line_cap,
                end_cap: line_cap,
                linejoin: line_join_from_i32(state.line_join),
                dash_phase: state.dash_phase,
                dash_pattern: state.dash_pattern.clone(),
            };

            // Call device stroke method
            device.stroke_path(
                &path,
                &stroke_state,
                &state.ctm,
                &state.stroke_colorspace,
                &state.stroke_color,
                state.stroke_alpha,
            );
        }

        self.current_point = None;
        Ok(())
    }

    fn op_close_and_stroke<D: Device>(&mut self, device: &mut D) -> Result<(), String> {
        self.op_close_path();
        self.op_stroke(device)
    }

    fn op_fill<D: Device>(&mut self, device: &mut D) -> Result<(), String> {
        self.apply_pending_clip(device);
        if let Some(path) = self.current_path.take() {
            let state = self.state();

            // Call device fill method (non-zero winding rule)
            device.fill_path(
                &path,
                false, // even_odd = false (non-zero winding)
                &state.ctm,
                &state.fill_colorspace,
                &state.fill_color,
                state.fill_alpha,
            );
        }

        self.current_point = None;
        Ok(())
    }

    fn op_fill_even_odd<D: Device>(&mut self, device: &mut D) -> Result<(), String> {
        self.apply_pending_clip(device);
        if let Some(path) = self.current_path.take() {
            let state = self.state();

            // Call device fill method (even-odd rule)
            device.fill_path(
                &path,
                true, // even_odd = true
                &state.ctm,
                &state.fill_colorspace,
                &state.fill_color,
                state.fill_alpha,
            );
        }

        self.current_point = None;
        Ok(())
    }

    fn op_fill_and_stroke<D: Device>(&mut self, device: &mut D) -> Result<(), String> {
        self.apply_pending_clip(device);
        if let Some(ref path) = self.current_path {
            let state = self.state();

            // Fill
            device.fill_path(
                path,
                false,
                &state.ctm,
                &state.fill_colorspace,
                &state.fill_color,
                state.fill_alpha,
            );

            // Stroke
            let line_cap = line_cap_from_i32(state.line_cap);
            let stroke_state = crate::fitz::path::StrokeState {
                linewidth: state.line_width,
                miterlimit: state.miter_limit,
                start_cap: line_cap,
                dash_cap: line_cap,
                end_cap: line_cap,
                linejoin: line_join_from_i32(state.line_join),
                dash_phase: state.dash_phase,
                dash_pattern: state.dash_pattern.clone(),
            };

            device.stroke_path(
                path,
                &stroke_state,
                &state.ctm,
                &state.stroke_colorspace,
                &state.stroke_color,
                state.stroke_alpha,
            );
        }

        self.current_path = None;
        self.current_point = None;
        Ok(())
    }

    fn op_fill_and_stroke_even_odd<D: Device>(&mut self, device: &mut D) -> Result<(), String> {
        self.apply_pending_clip(device);
        if let Some(ref path) = self.current_path {
            let state = self.state();

            // Fill with even-odd
            device.fill_path(
                path,
                true,
                &state.ctm,
                &state.fill_colorspace,
                &state.fill_color,
                state.fill_alpha,
            );

            // Stroke
            let line_cap = line_cap_from_i32(state.line_cap);
            let stroke_state = crate::fitz::path::StrokeState {
                linewidth: state.line_width,
                miterlimit: state.miter_limit,
                start_cap: line_cap,
                dash_cap: line_cap,
                end_cap: line_cap,
                linejoin: line_join_from_i32(state.line_join),
                dash_phase: state.dash_phase,
                dash_pattern: state.dash_pattern.clone(),
            };

            device.stroke_path(
                path,
                &stroke_state,
                &state.ctm,
                &state.stroke_colorspace,
                &state.stroke_color,
                state.stroke_alpha,
            );
        }

        self.current_path = None;
        self.current_point = None;
        Ok(())
    }

    fn op_close_fill_and_stroke<D: Device>(&mut self, device: &mut D) -> Result<(), String> {
        self.op_close_path();
        self.op_fill_and_stroke(device)
    }

    fn op_close_fill_and_stroke_even_odd<D: Device>(
        &mut self,
        device: &mut D,
    ) -> Result<(), String> {
        self.op_close_path();
        self.op_fill_and_stroke_even_odd(device)
    }

    fn op_end_path<D: Device>(&mut self, device: &mut D) {
        self.apply_pending_clip(device);
        self.current_path = None;
        self.current_point = None;
    }

    // ========================================================================
    // Clipping Path Operators
    // ========================================================================

    fn op_clip(&mut self) {
        self.pending_clip = Some(PendingClip::NonZero);
    }

    fn op_clip_even_odd(&mut self) {
        self.pending_clip = Some(PendingClip::EvenOdd);
    }

    // ========================================================================
    // Color Operators
    // ========================================================================

    fn op_set_stroke_colorspace(&mut self, operands: &[Object]) -> Result<(), String> {
        if let Some(cs) = self.resolve_colorspace(operands) {
            let n = cs.n() as usize;
            self.state_mut().stroke_colorspace = cs;
            self.state_mut().stroke_color = vec![0.0; n];
        }
        Ok(())
    }

    fn op_set_fill_colorspace(&mut self, operands: &[Object]) -> Result<(), String> {
        if let Some(cs) = self.resolve_colorspace(operands) {
            let n = cs.n() as usize;
            self.state_mut().fill_colorspace = cs;
            self.state_mut().fill_color = vec![0.0; n];
        }
        Ok(())
    }

    fn op_set_stroke_color(&mut self, operands: &[Object]) -> Result<(), String> {
        let color: Vec<f32> = operands
            .iter()
            .filter_map(|obj| get_f32(obj).ok())
            .collect();

        if !color.is_empty() {
            self.state_mut().stroke_color = color;
        }

        Ok(())
    }

    fn op_set_fill_color(&mut self, operands: &[Object]) -> Result<(), String> {
        let color: Vec<f32> = operands
            .iter()
            .filter_map(|obj| get_f32(obj).ok())
            .collect();

        if !color.is_empty() {
            self.state_mut().fill_color = color;
        }

        Ok(())
    }

    fn op_set_stroke_gray(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 1 {
            return Err("G operator requires 1 operand".to_string());
        }

        let gray = get_f32(&operands[0])?;
        let state = self.state_mut();
        state.stroke_colorspace = Colorspace::device_gray();
        state.stroke_color = vec![gray];

        Ok(())
    }

    fn op_set_fill_gray(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 1 {
            return Err("g operator requires 1 operand".to_string());
        }

        let gray = get_f32(&operands[0])?;
        let state = self.state_mut();
        state.fill_colorspace = Colorspace::device_gray();
        state.fill_color = vec![gray];

        Ok(())
    }

    fn op_set_stroke_rgb(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 3 {
            return Err("RG operator requires 3 operands".to_string());
        }

        let r = get_f32(&operands[0])?;
        let g = get_f32(&operands[1])?;
        let b = get_f32(&operands[2])?;

        let state = self.state_mut();
        state.stroke_colorspace = Colorspace::device_rgb();
        state.stroke_color = vec![r, g, b];

        Ok(())
    }

    fn op_set_fill_rgb(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 3 {
            return Err("rg operator requires 3 operands".to_string());
        }

        let r = get_f32(&operands[0])?;
        let g = get_f32(&operands[1])?;
        let b = get_f32(&operands[2])?;

        let state = self.state_mut();
        state.fill_colorspace = Colorspace::device_rgb();
        state.fill_color = vec![r, g, b];

        Ok(())
    }

    fn op_set_stroke_cmyk(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 4 {
            return Err("K operator requires 4 operands".to_string());
        }

        let c = get_f32(&operands[0])?;
        let m = get_f32(&operands[1])?;
        let y = get_f32(&operands[2])?;
        let k = get_f32(&operands[3])?;

        let state = self.state_mut();
        state.stroke_colorspace = Colorspace::device_cmyk();
        state.stroke_color = vec![c, m, y, k];

        Ok(())
    }

    fn op_set_fill_cmyk(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 4 {
            return Err("k operator requires 4 operands".to_string());
        }

        let c = get_f32(&operands[0])?;
        let m = get_f32(&operands[1])?;
        let y = get_f32(&operands[2])?;
        let k = get_f32(&operands[3])?;

        let state = self.state_mut();
        state.fill_colorspace = Colorspace::device_cmyk();
        state.fill_color = vec![c, m, y, k];

        Ok(())
    }

    // ========================================================================
    // Text Object Operators
    // ========================================================================

    fn op_begin_text(&mut self) {
        let state = self.state_mut();
        state.text_matrix = Matrix::IDENTITY;
        state.text_line_matrix = Matrix::IDENTITY;
    }

    fn op_end_text(&mut self) {
        // Text object complete
    }

    // ========================================================================
    // Text Positioning Operators
    // ========================================================================

    fn op_text_move(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 2 {
            return Err("Td operator requires 2 operands".to_string());
        }

        let tx = get_f32(&operands[0])?;
        let ty = get_f32(&operands[1])?;

        let state = self.state_mut();
        let translate = Matrix::translate(tx, ty);
        state.text_line_matrix = state.text_line_matrix.concat(&translate);
        state.text_matrix = state.text_line_matrix;

        Ok(())
    }

    fn op_text_move_set_leading(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 2 {
            return Err("TD operator requires 2 operands".to_string());
        }

        let ty = get_f32(&operands[1])?;
        self.state_mut().leading = -ty;

        self.op_text_move(operands)
    }

    fn op_text_set_matrix(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 6 {
            return Err("Tm operator requires 6 operands".to_string());
        }

        let a = get_f32(&operands[0])?;
        let b = get_f32(&operands[1])?;
        let c = get_f32(&operands[2])?;
        let d = get_f32(&operands[3])?;
        let e = get_f32(&operands[4])?;
        let f = get_f32(&operands[5])?;

        let matrix = Matrix::new(a, b, c, d, e, f);
        let state = self.state_mut();
        state.text_matrix = matrix;
        state.text_line_matrix = matrix;

        Ok(())
    }

    fn op_text_next_line(&mut self) {
        let leading = self.state().leading;
        let _ = self.op_text_move(&[Object::Real(0.0), Object::Real(-leading as f64)]);
    }

    // ========================================================================
    // Text State Operators
    // ========================================================================

    fn op_set_char_spacing(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 1 {
            return Err("Tc operator requires 1 operand".to_string());
        }

        let spacing = get_f32(&operands[0])?;
        self.state_mut().char_spacing = spacing;

        Ok(())
    }

    fn op_set_word_spacing(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 1 {
            return Err("Tw operator requires 1 operand".to_string());
        }

        let spacing = get_f32(&operands[0])?;
        self.state_mut().word_spacing = spacing;

        Ok(())
    }

    fn op_set_horizontal_scaling(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 1 {
            return Err("Tz operator requires 1 operand".to_string());
        }

        let scaling = get_f32(&operands[0])?;
        self.state_mut().horizontal_scaling = scaling;

        Ok(())
    }

    fn op_set_leading(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 1 {
            return Err("TL operator requires 1 operand".to_string());
        }

        let leading = get_f32(&operands[0])?;
        self.state_mut().leading = leading;

        Ok(())
    }

    fn op_set_font(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 2 {
            return Err("Tf operator requires 2 operands".to_string());
        }

        let font = match &operands[0] {
            Object::Name(n) => n.as_str().to_string(),
            _ => return Err("Invalid font name".to_string()),
        };
        let size = get_f32(&operands[1])?;

        let state = self.state_mut();
        state.font = Some(font);
        state.font_size = size;

        Ok(())
    }

    fn op_set_text_render_mode(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 1 {
            return Err("Tr operator requires 1 operand".to_string());
        }

        let mode = get_i32(&operands[0])?;
        self.state_mut().text_render_mode = mode;

        Ok(())
    }

    fn op_set_text_rise(&mut self, operands: &[Object]) -> Result<(), String> {
        if operands.len() != 1 {
            return Err("Ts operator requires 1 operand".to_string());
        }

        let rise = get_f32(&operands[0])?;
        self.state_mut().text_rise = rise;

        Ok(())
    }

    // ========================================================================
    // Text Showing Operators
    // ========================================================================

    fn op_show_text<D: Device>(
        &mut self,
        operands: &[Object],
        device: &mut D,
    ) -> Result<(), String> {
        if operands.len() != 1 {
            return Err("Tj operator requires 1 operand".to_string());
        }

        let text_bytes = match &operands[0] {
            Object::String(s) => s.as_bytes().to_vec(),
            _ => return Err("Invalid text string".to_string()),
        };

        let font_name = match &self.state().font {
            Some(name) => name.clone(),
            None => return Ok(()),
        };
        let font_size = self.state().font_size;

        let font = match self.resolve_font(&font_name) {
            Some(f) => f,
            None => {
                let f = Font::new(&font_name);
                Arc::new(f)
            }
        };

        let h_scale = self.state().horizontal_scaling / 100.0;
        let rise = self.state().text_rise;

        let trm = Matrix::new(font_size * h_scale, 0.0, 0.0, font_size, 0.0, rise)
            .concat(&self.state().text_matrix)
            .concat(&self.state().ctm);

        let mut text = Text::new();
        let mut span = TextSpan::new(font.clone(), trm);
        let mut tx = 0.0f32;

        for &byte in &text_bytes {
            let code = byte as u32;
            let gid = font.charmap().lookup(code).unwrap_or(0) as i32;
            let width = font.glyph_advance(code as u16) * font_size;

            let item = TextItem::with_advance(tx, 0.0, width, gid, code as i32, code as i32);
            span.add_glyph(item);

            let char_space = if byte == b' ' {
                self.state().char_spacing + self.state().word_spacing
            } else {
                self.state().char_spacing
            };
            tx += (width + char_space) * h_scale;
        }

        text.add_span(span);

        let render_mode = self.state().text_render_mode;
        let fill_cs = self.state().fill_colorspace.clone();
        let fill_color = self.state().fill_color.clone();
        let fill_alpha = self.state().fill_alpha;
        let stroke_cs = self.state().stroke_colorspace.clone();
        let stroke_color = self.state().stroke_color.clone();
        let stroke_alpha = self.state().stroke_alpha;
        let ctm = self.state().ctm;

        match render_mode {
            0 | 4 => device.fill_text(&text, &ctm, &fill_cs, &fill_color, fill_alpha),
            1 | 5 => {
                let stroke = StrokeState::new();
                device.stroke_text(
                    &text,
                    &stroke,
                    &ctm,
                    &stroke_cs,
                    &stroke_color,
                    stroke_alpha,
                );
            }
            2 | 6 => {
                device.fill_text(&text, &ctm, &fill_cs, &fill_color, fill_alpha);
                let stroke = StrokeState::new();
                device.stroke_text(
                    &text,
                    &stroke,
                    &ctm,
                    &stroke_cs,
                    &stroke_color,
                    stroke_alpha,
                );
            }
            3 | 7 => device.ignore_text(&text, &ctm),
            _ => device.fill_text(&text, &ctm, &fill_cs, &fill_color, fill_alpha),
        }

        self.state_mut().text_matrix = Matrix::translate(tx, 0.0).concat(&self.state().text_matrix);

        Ok(())
    }

    fn op_show_text_adjusted<D: Device>(
        &mut self,
        operands: &[Object],
        device: &mut D,
    ) -> Result<(), String> {
        if operands.len() != 1 {
            return Err("TJ operator requires 1 operand".to_string());
        }

        let array = match &operands[0] {
            Object::Array(arr) => arr.clone(),
            _ => return Err("Invalid text array".to_string()),
        };

        for item in &array {
            if let Object::String(s) = item {
                self.op_show_text(&[Object::String(s.clone())], device)?;
            } else if let Ok(offset) = get_f32(item) {
                let font_size = self.state().font_size;
                let h_scale = self.state().horizontal_scaling / 100.0;
                let tx = (-offset / 1000.0) * font_size * h_scale;
                let tm = self.state().text_matrix;
                self.state_mut().text_matrix = Matrix::translate(tx, 0.0).concat(&tm);
            }
        }

        Ok(())
    }

    fn op_show_text_next_line<D: Device>(
        &mut self,
        operands: &[Object],
        device: &mut D,
    ) -> Result<(), String> {
        self.op_text_next_line();
        self.op_show_text(operands, device)
    }

    fn op_show_text_next_line_with_spacing<D: Device>(
        &mut self,
        operands: &[Object],
        device: &mut D,
    ) -> Result<(), String> {
        if operands.len() != 3 {
            return Err("\" operator requires 3 operands".to_string());
        }

        self.op_set_word_spacing(&operands[0..1])?;
        self.op_set_char_spacing(&operands[1..2])?;
        self.op_show_text_next_line(&operands[2..3], device)
    }

    // ========================================================================
    // XObject Operators
    // ========================================================================

    fn op_paint_xobject<D: Device>(
        &mut self,
        operands: &[Object],
        device: &mut D,
    ) -> Result<(), String> {
        let xobj_name = operands
            .first()
            .and_then(|o| o.as_name())
            .map(|n| n.as_str().to_string());
        let xobj_name = match xobj_name {
            Some(n) => n,
            None => return Ok(()),
        };

        let xobj = self.lookup_resource("XObject", &xobj_name).cloned();
        let xobj = match xobj {
            Some(o) => o,
            None => return Ok(()),
        };

        match &xobj {
            Object::Stream { dict, data } => {
                let subtype = dict
                    .get(&Name::new("Subtype"))
                    .and_then(|o| o.as_name())
                    .map(|n| n.as_str().to_string())
                    .unwrap_or_default();

                match subtype.as_str() {
                    "Image" => {
                        self.paint_image_xobject(dict, data, device);
                    }
                    "Form" => {
                        self.paint_form_xobject(dict, data, device)?;
                    }
                    _ => {}
                }
            }
            Object::Dict(dict) => {
                let subtype = dict
                    .get(&Name::new("Subtype"))
                    .and_then(|o| o.as_name())
                    .map(|n| n.as_str().to_string())
                    .unwrap_or_default();
                if subtype == "Form" {
                    self.paint_form_xobject(dict, &[], device)?;
                }
            }
            _ => {}
        }
        Ok(())
    }

    fn paint_image_xobject<D: Device>(&self, dict: &Dict, data: &[u8], device: &mut D) {
        let width = dict
            .get(&Name::new("Width"))
            .and_then(|o| o.as_int())
            .unwrap_or(0) as i32;
        let height = dict
            .get(&Name::new("Height"))
            .and_then(|o| o.as_int())
            .unwrap_or(0) as i32;

        let is_image_mask = dict
            .get(&Name::new("ImageMask"))
            .and_then(|o| o.as_bool())
            .unwrap_or(false);

        let bpc = if is_image_mask {
            1
        } else {
            dict.get(&Name::new("BitsPerComponent"))
                .and_then(|o| o.as_int())
                .unwrap_or(8) as u8
        };

        let cs = if is_image_mask {
            Colorspace::device_gray()
        } else {
            dict.get(&Name::new("ColorSpace"))
                .and_then(|o| Self::colorspace_from_object(o))
                .unwrap_or_else(Colorspace::device_rgb)
        };

        let filter = dict
            .get(&Name::new("Filter"))
            .and_then(|o| match o {
                Object::Name(n) => Some(n.as_str().to_string()),
                Object::Array(arr) => arr
                    .last()
                    .and_then(|o| o.as_name())
                    .map(|n| n.as_str().to_string()),
                _ => None,
            })
            .unwrap_or_default();

        let format = match filter.as_str() {
            "DCTDecode" => ImageFormat::Jpeg,
            "JPXDecode" => ImageFormat::Jpeg2000,
            "JBIG2Decode" => ImageFormat::Jbig2,
            "CCITTFaxDecode" => ImageFormat::Ccitt,
            "FlateDecode" => ImageFormat::Flate,
            "LZWDecode" => ImageFormat::Lzw,
            "RunLengthDecode" => ImageFormat::RunLength,
            _ => {
                if data.is_empty() {
                    return;
                }
                ImageFormat::Raw
            }
        };

        match Image::from_compressed(width, height, bpc, Some(cs), format, data.to_vec()) {
            Ok(image) => {
                let state = self.state();
                if is_image_mask {
                    let fill_cs = &state.fill_colorspace;
                    let fill_color = &state.fill_color;
                    device.fill_image_mask(
                        &image,
                        &state.ctm,
                        fill_cs,
                        fill_color,
                        state.fill_alpha,
                    );
                } else {
                    device.fill_image(&image, &state.ctm, state.fill_alpha);
                }
            }
            Err(e) => {
                eprintln!(
                    "Failed to create image XObject: {}x{} bpc={} — {}",
                    width, height, bpc, e
                );
            }
        }
    }

    fn paint_form_xobject<D: Device>(
        &mut self,
        dict: &Dict,
        stream_data: &[u8],
        device: &mut D,
    ) -> Result<(), String> {
        self.push_state();

        if let Some(matrix_arr) = dict.get(&Name::new("Matrix")).and_then(|o| o.as_array()) {
            if matrix_arr.len() == 6 {
                let vals: Vec<f32> = matrix_arr
                    .iter()
                    .filter_map(|o| o.as_real().map(|r| r as f32))
                    .collect();
                if vals.len() == 6 {
                    let form_matrix =
                        Matrix::new(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]);
                    let ctm = self.state().ctm;
                    self.state_mut().ctm = ctm.concat(&form_matrix);
                }
            }
        }

        let mut clipped = false;
        if let Some(bbox_arr) = dict.get(&Name::new("BBox")).and_then(|o| o.as_array()) {
            if bbox_arr.len() == 4 {
                let vals: Vec<f32> = bbox_arr
                    .iter()
                    .filter_map(|o| o.as_real().map(|r| r as f32))
                    .collect();
                if vals.len() == 4 {
                    let bbox_rect = Rect::new(vals[0], vals[1], vals[2], vals[3]);
                    let mut clip_path = Path::new();
                    clip_path.rect(bbox_rect);
                    let ctm = self.state().ctm;
                    device.clip_path(&clip_path, false, &ctm, Rect::INFINITE);
                    clipped = true;
                }
            }
        }

        let saved_resources = self.resources.clone();
        if let Some(form_resources) = dict.get(&Name::new("Resources")).and_then(|o| o.as_dict()) {
            self.resources = Some(form_resources.clone());
        }

        let decoded = self.decode_stream(dict, stream_data);
        let _ = self.interpret(&decoded, device);

        self.resources = saved_resources;
        self.pop_state();
        if clipped {
            device.pop_clip();
        }
        Ok(())
    }

    /// Decode a stream using the filter(s) specified in its dictionary.
    /// Supports both single filter names and filter arrays.
    fn decode_stream(&self, dict: &Dict, raw: &[u8]) -> Vec<u8> {
        if raw.is_empty() {
            return Vec::new();
        }

        let filters: Vec<String> = match dict.get(&Name::new("Filter")) {
            Some(Object::Name(n)) => vec![n.as_str().to_string()],
            Some(Object::Array(arr)) => arr
                .iter()
                .filter_map(|o| o.as_name().map(|n| n.as_str().to_string()))
                .collect(),
            _ => return raw.to_vec(),
        };

        let mut data = raw.to_vec();
        for filter_name in &filters {
            data = Self::apply_filter(filter_name, &data);
        }
        data
    }

    fn apply_filter(filter_name: &str, data: &[u8]) -> Vec<u8> {
        match filter_name {
            "FlateDecode" => {
                use crate::pdf::filter::decode_flate;
                decode_flate(data, None).unwrap_or_else(|_| data.to_vec())
            }
            "LZWDecode" => {
                use crate::pdf::filter::decode_lzw;
                decode_lzw(data, None).unwrap_or_else(|_| data.to_vec())
            }
            "ASCII85Decode" => {
                use crate::pdf::filter::decode_ascii85;
                decode_ascii85(data).unwrap_or_else(|_| data.to_vec())
            }
            "ASCIIHexDecode" => {
                use crate::pdf::filter::decode_ascii_hex;
                decode_ascii_hex(data).unwrap_or_else(|_| data.to_vec())
            }
            _ => data.to_vec(),
        }
    }

    // ========================================================================
    // Image Operators
    // ========================================================================

    /// Handle the complete BI .. ID <data> EI inline image sequence.
    /// Called directly from the interpret loop when BI is encountered.
    fn handle_inline_image<D: Device>(
        &mut self,
        lexer: &mut Lexer,
        buf: &mut LexBuf,
        device: &mut D,
    ) {
        let mut dict = Dict::new();

        loop {
            match lexer.lex(buf) {
                Ok(Token::Keyword) if buf.as_str() == "ID" => break,
                Ok(Token::Eof) => return,
                Ok(Token::Name) => {
                    let key = Name::new(buf.as_str());
                    if let Ok(val_tok) = lexer.lex(buf) {
                        let val = match val_tok {
                            Token::Int => Object::Int(buf.as_int()),
                            Token::Real => Object::Real(buf.as_float()),
                            Token::Name => Object::Name(Name::new(buf.as_str())),
                            Token::True => Object::Bool(true),
                            Token::False => Object::Bool(false),
                            _ => Object::Null,
                        };
                        dict.insert(key, val);
                    }
                }
                _ => {}
            }
        }

        let width = Self::inline_img_int(&dict, "W")
            .or_else(|| Self::inline_img_int(&dict, "Width"))
            .unwrap_or(0);
        let height = Self::inline_img_int(&dict, "H")
            .or_else(|| Self::inline_img_int(&dict, "Height"))
            .unwrap_or(0);
        let bpc = Self::inline_img_int(&dict, "BPC")
            .or_else(|| Self::inline_img_int(&dict, "BitsPerComponent"))
            .unwrap_or(8) as u8;

        if width == 0 || height == 0 {
            return;
        }

        let cs_name = dict
            .get(&Name::new("CS"))
            .or_else(|| dict.get(&Name::new("ColorSpace")))
            .and_then(|o| o.as_name())
            .map(|n| n.as_str().to_string())
            .unwrap_or_default();

        let cs = match cs_name.as_str() {
            "G" | "DeviceGray" => Colorspace::device_gray(),
            "RGB" | "DeviceRGB" => Colorspace::device_rgb(),
            "CMYK" | "DeviceCMYK" => Colorspace::device_cmyk(),
            _ => Colorspace::device_rgb(),
        };

        let filter = dict
            .get(&Name::new("F"))
            .or_else(|| dict.get(&Name::new("Filter")))
            .and_then(|o| o.as_name())
            .map(|n| n.as_str().to_string())
            .unwrap_or_default();

        let is_compressed = !filter.is_empty();
        let expected_raw_len = if !is_compressed {
            let components = cs.n() as usize;
            let row_bits = width as usize * components * bpc as usize;
            let row_bytes = (row_bits + 7) / 8;
            row_bytes * height as usize
        } else {
            0
        };

        let remaining = lexer.remaining();
        let start = if remaining.first().map_or(false, |b| b.is_ascii_whitespace()) {
            1
        } else {
            0
        };
        let data_slice = &remaining[start..];

        let end_pos =
            Self::find_ei_marker(data_slice, expected_raw_len).unwrap_or(data_slice.len());
        let image_data = data_slice[..end_pos].to_vec();

        let skip = start + end_pos + 2; // skip past "EI"
        lexer.advance_by(skip.min(remaining.len()));

        let format = match filter.as_str() {
            "DCT" | "DCTDecode" => ImageFormat::Jpeg,
            "Fl" | "FlateDecode" => ImageFormat::Flate,
            "LZW" | "LZWDecode" => ImageFormat::Lzw,
            "RL" | "RunLengthDecode" => ImageFormat::RunLength,
            "AHx" | "ASCIIHexDecode" => ImageFormat::Raw,
            "A85" | "ASCII85Decode" => ImageFormat::Raw,
            "CCF" | "CCITTFaxDecode" => ImageFormat::Ccitt,
            _ => ImageFormat::Raw,
        };

        match Image::from_compressed(
            width as i32,
            height as i32,
            bpc,
            Some(cs),
            format,
            image_data,
        ) {
            Ok(image) => {
                let state = self.state();
                device.fill_image(&image, &state.ctm, state.fill_alpha);
            }
            Err(e) => {
                eprintln!(
                    "Failed to create inline image: {}x{} bpc={} — {}",
                    width, height, bpc, e
                );
            }
        }
    }

    fn inline_img_int(dict: &Dict, key: &str) -> Option<i64> {
        dict.get(&Name::new(key)).and_then(|o| o.as_int())
    }

    fn find_ei_marker(data: &[u8], min_offset: usize) -> Option<usize> {
        let search_start = if min_offset > 0 {
            min_offset.saturating_sub(1)
        } else {
            0
        };
        for i in search_start..data.len().saturating_sub(1) {
            if data[i] == b'E' && data[i + 1] == b'I' {
                let ws_before = i == 0 || data[i - 1].is_ascii_whitespace();
                let ws_after = i + 2 >= data.len() || data[i + 2].is_ascii_whitespace();
                if ws_before && ws_after {
                    return Some(i);
                }
            }
        }
        None
    }

    // ========================================================================
    // Marked Content Operators
    // ========================================================================

    fn op_marked_content_point(&mut self, _operands: &[Object]) -> Result<(), String> {
        Ok(())
    }

    fn op_marked_content_point_with_props(&mut self, _operands: &[Object]) -> Result<(), String> {
        Ok(())
    }

    fn op_begin_marked_content(&mut self, _operands: &[Object]) -> Result<(), String> {
        Ok(())
    }

    fn op_begin_marked_content_with_props(&mut self, _operands: &[Object]) -> Result<(), String> {
        Ok(())
    }

    fn op_end_marked_content(&mut self) -> Result<(), String> {
        Ok(())
    }

    // ========================================================================
    // Compatibility Operators
    // ========================================================================

    fn op_begin_compat(&mut self) {
        // Begin compatibility section
    }

    fn op_end_compat(&mut self) {
        // End compatibility section
    }

    // ========================================================================
    // Shading Operator
    // ========================================================================

    fn op_shade<D: Device>(&mut self, operands: &[Object], device: &mut D) -> Result<(), String> {
        let sh_name = operands
            .first()
            .and_then(|o| o.as_name())
            .map(|n| n.as_str().to_string());
        let sh_name = match sh_name {
            Some(n) => n,
            None => return Ok(()),
        };

        let sh_dict = self
            .lookup_resource("Shading", &sh_name)
            .and_then(|o| match o {
                Object::Dict(d) => Some(d.clone()),
                Object::Stream { dict, .. } => Some(dict.clone()),
                _ => None,
            });

        if let Some(dict) = sh_dict {
            if let Some(bg) = dict
                .get(&Name::new("Background"))
                .and_then(|o| o.as_array())
            {
                let color: Vec<f32> = bg
                    .iter()
                    .filter_map(|o| o.as_real().map(|r| r as f32))
                    .collect();
                let cs = dict
                    .get(&Name::new("ColorSpace"))
                    .and_then(|o| Self::colorspace_from_object(o))
                    .unwrap_or_else(Colorspace::device_rgb);

                let bbox_rect = Rect::INFINITE;
                let mut path = Path::new();
                path.rect(bbox_rect);

                let state = self.state();
                device.fill_path(&path, false, &state.ctm, &cs, &color, state.fill_alpha);
            }
        }
        Ok(())
    }
}

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

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

/// Get f32 value from Object
fn get_f32(obj: &Object) -> Result<f32, String> {
    match obj {
        Object::Int(i) => Ok(*i as f32),
        Object::Real(f) => Ok(*f as f32),
        _ => Err("Expected number".to_string()),
    }
}

/// Get i32 value from Object
fn get_i32(obj: &Object) -> Result<i32, String> {
    match obj {
        Object::Int(i) => Ok(*i as i32),
        Object::Real(f) => Ok(*f as i32),
        _ => Err("Expected integer".to_string()),
    }
}

/// Convert PDF line cap value to LineCap enum
fn line_cap_from_i32(cap: i32) -> LineCap {
    match cap {
        0 => LineCap::Butt,
        1 => LineCap::Round,
        2 => LineCap::Square,
        _ => LineCap::Butt, // Default to butt
    }
}

/// Convert PDF line join value to LineJoin enum
fn line_join_from_i32(join: i32) -> LineJoin {
    match join {
        0 => LineJoin::Miter,
        1 => LineJoin::Round,
        2 => LineJoin::Bevel,
        _ => LineJoin::Miter, // Default to miter
    }
}

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

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

    #[test]
    fn test_interpreter_creation() {
        let interp = Interpreter::new();
        assert_eq!(interp.state_stack.len(), 1);
    }

    #[test]
    fn test_state_stack() {
        let mut interp = Interpreter::new();
        interp.push_state();
        assert_eq!(interp.state_stack.len(), 2);
        interp.pop_state();
        assert_eq!(interp.state_stack.len(), 1);
    }

    #[test]
    fn test_matrix_concat() {
        let mut interp = Interpreter::new();
        let operands = vec![
            Object::Real(2.0),
            Object::Real(0.0),
            Object::Real(0.0),
            Object::Real(2.0),
            Object::Real(0.0),
            Object::Real(0.0),
        ];

        interp.op_concat_matrix(&operands).unwrap();

        let state = interp.state();
        assert_eq!(state.ctm.a, 2.0);
        assert_eq!(state.ctm.d, 2.0);
    }

    #[test]
    fn test_get_f32() {
        assert_eq!(get_f32(&Object::Int(42)).unwrap(), 42.0);
        assert_eq!(get_f32(&Object::Real(3.5)).unwrap(), 3.5f32);
        assert!(get_f32(&Object::Null).is_err());
    }
}
