//! Full PDF Document Model (QPDF equivalent)
//!
//! Core document model that holds all PDF objects in memory and provides
//! manipulation APIs for reading, modifying, and writing PDFs.

use super::error::{QpdfError, Result};
use crate::pdf::object::{Dict, Name, ObjRef, Object, PdfString};
use std::collections::HashMap;

// ============================================================================
// Encryption and Permissions
// ============================================================================

/// PDF encryption parameters
#[derive(Debug, Clone)]
pub struct EncryptionParams {
    pub r_value: i32,
    pub v_value: i32,
    pub key_length: i32,
    pub p_value: i32,
    pub encrypt_metadata: bool,
    pub owner_password_matched: bool,
    pub user_password_matched: bool,
}

/// Decoded PDF permissions
#[derive(Debug, Clone, Default)]
pub struct Permissions {
    pub accessibility: bool,
    pub extract: bool,
    pub print_low_res: bool,
    pub print_high_res: bool,
    pub modify_document: bool,
    pub modify_annotation: bool,
    pub modify_form: bool,
    pub modify_assembly: bool,
    pub modify_other: bool,
}

impl Permissions {
    /// Decode P value (32-bit permissions) into Permissions struct
    pub fn from_p_value(p: i32) -> Self {
        let p = p as u32;
        Self {
            accessibility: (p & (1 << 3)) != 0,
            extract: (p & (1 << 5)) != 0,
            print_low_res: (p & (1 << 3)) != 0,
            print_high_res: (p & (1 << 12)) != 0,
            modify_document: (p & (1 << 4)) != 0,
            modify_annotation: (p & (1 << 6)) != 0,
            modify_form: (p & (1 << 9)) != 0,
            modify_assembly: (p & (1 << 11)) != 0,
            modify_other: (p & (1 << 2)) != 0,
        }
    }
}

// ============================================================================
// Document
// ============================================================================

/// Full PDF document model - holds all objects and provides manipulation APIs
#[derive(Debug)]
pub struct Document {
    objects: HashMap<ObjRef, Object>,
    trailer: Object,
    root_ref: Option<ObjRef>,
    next_obj_num: i32,
    pdf_version: String,
    extension_level: i32,
    filename: Option<String>,
    encrypted: bool,
    encryption_key: Option<Vec<u8>>,
    encryption_params: Option<EncryptionParams>,
    page_cache: Option<Vec<ObjRef>>,
    warnings: Vec<String>,
    suppress_warnings: bool,
    linearized: bool,
}

impl Document {
    // ------------------------------------------------------------------------
    // Construction
    // ------------------------------------------------------------------------

    /// Create an empty PDF document
    pub fn new() -> Self {
        Self {
            objects: HashMap::new(),
            trailer: Object::Dict(HashMap::new()),
            root_ref: None,
            next_obj_num: 1,
            pdf_version: "1.7".to_string(),
            extension_level: 0,
            filename: None,
            encrypted: false,
            encryption_key: None,
            encryption_params: None,
            page_cache: None,
            warnings: Vec::new(),
            suppress_warnings: false,
            linearized: false,
        }
    }

    /// Create an empty PDF with catalog and pages structure
    pub fn empty_pdf() -> Self {
        let mut doc = Self::new();
        let mut pages_dict = Dict::new();
        pages_dict.insert(Name::new("Type"), Object::Name(Name::new("Pages")));
        pages_dict.insert(Name::new("Kids"), Object::Array(vec![]));
        pages_dict.insert(Name::new("Count"), Object::Int(0));

        let pages_ref = doc.make_indirect_object(Object::Dict(pages_dict));

        let mut catalog = Dict::new();
        catalog.insert(Name::new("Type"), Object::Name(Name::new("Catalog")));
        catalog.insert(Name::new("Pages"), Object::Ref(pages_ref));

        let root_ref = doc.make_indirect_object(Object::Dict(catalog));
        doc.root_ref = Some(root_ref);

        if let Object::Dict(trailer) = &mut doc.trailer {
            trailer.insert(Name::new("Root"), Object::Ref(root_ref));
            trailer.insert(Name::new("Size"), Object::Int(3));
        }

        doc
    }

    /// Load PDF from file (stub - full parsing is complex)
    pub fn from_file(path: &str, _password: Option<&str>) -> Result<Self> {
        let data = std::fs::read(path)
            .map_err(|e| QpdfError::Parse(format!("Failed to read file '{}': {}", path, e)))?;
        Self::from_bytes(data)
    }

    /// Load PDF from in-memory bytes. Password-based decryption is not yet supported.
    pub fn from_memory(data: &[u8], _password: Option<&str>) -> Result<Self> {
        Self::from_bytes(data.to_vec())
    }

    /// Parse a PDF from bytes - basic parsing: find xref, read trailer, load objects
    pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
        let len = data.len();
        if len < 8 {
            return Err(QpdfError::Parse("PDF too short".to_string()));
        }

        if !data.starts_with(b"%PDF-") {
            return Err(QpdfError::Parse("Invalid PDF header".to_string()));
        }

        let eof_pos = data
            .windows(5)
            .rposition(|w| w == b"%%EOF")
            .ok_or_else(|| QpdfError::Parse("%%EOF not found".to_string()))?;

        let before_eof = &data[..eof_pos];
        let startxref_pos = before_eof
            .windows(9)
            .rposition(|w| w == b"startxref")
            .ok_or_else(|| QpdfError::Parse("startxref not found".to_string()))?;

        let after_startxref = &data[startxref_pos + 9..];
        let xref_offset = parse_xref_offset(after_startxref)?;

        let mut doc = Self::new();
        parse_xref_and_objects(&data, xref_offset, &mut doc)?;

        Ok(doc)
    }

    // ------------------------------------------------------------------------
    // Object Management
    // ------------------------------------------------------------------------

    pub fn get_object(&self, obj_ref: ObjRef) -> Option<&Object> {
        self.objects.get(&obj_ref)
    }

    pub fn get_object_mut(&mut self, obj_ref: ObjRef) -> Option<&mut Object> {
        self.objects.get_mut(&obj_ref)
    }

    pub fn make_indirect_object(&mut self, obj: Object) -> ObjRef {
        let num = self.allocate_obj_num();
        let obj_ref = ObjRef::new(num, 0);
        self.objects.insert(obj_ref, obj);
        obj_ref
    }

    pub fn replace_object(&mut self, obj_ref: ObjRef, obj: Object) {
        self.objects.insert(obj_ref, obj);
        self.invalidate_page_cache();
    }

    pub fn swap_objects(&mut self, a: ObjRef, b: ObjRef) {
        let obj_a = self.objects.remove(&a);
        let obj_b = self.objects.remove(&b);
        if let Some(o) = obj_a {
            self.objects.insert(b, o);
        }
        if let Some(o) = obj_b {
            self.objects.insert(a, o);
        }
        self.invalidate_page_cache();
    }

    pub fn new_stream(&mut self, data: Vec<u8>) -> ObjRef {
        let mut dict = Dict::new();
        dict.insert(Name::new("Length"), Object::Int(data.len() as i64));
        self.new_stream_with_dict(dict, data)
    }

    pub fn new_stream_with_dict(&mut self, mut dict: Dict, data: Vec<u8>) -> ObjRef {
        dict.insert(Name::new("Length"), Object::Int(data.len() as i64));
        self.make_indirect_object(Object::Stream { dict, data })
    }

    pub fn get_all_objects(&self) -> Vec<(ObjRef, &Object)> {
        self.objects.iter().map(|(r, o)| (*r, o)).collect()
    }

    pub fn object_count(&self) -> usize {
        self.objects.len()
    }

    pub fn allocate_obj_num(&mut self) -> i32 {
        let num = self.next_obj_num;
        self.next_obj_num += 1;
        num
    }

    pub fn copy_foreign_object(
        &mut self,
        foreign_doc: &Document,
        obj_ref: ObjRef,
    ) -> Result<ObjRef> {
        let obj = foreign_doc
            .get_object(obj_ref)
            .ok_or_else(|| {
                QpdfError::NotFound(format!(
                    "Object {} {} not found",
                    obj_ref.num, obj_ref.generation
                ))
            })?
            .clone();
        Ok(self.make_indirect_object(obj))
    }

    // ------------------------------------------------------------------------
    // Document Structure
    // ------------------------------------------------------------------------

    pub fn get_trailer(&self) -> &Object {
        &self.trailer
    }

    pub fn set_trailer(&mut self, trailer: Object) {
        self.trailer = trailer;
    }

    pub fn get_root(&self) -> Result<&Object> {
        let root_ref = self
            .root_ref
            .ok_or_else(|| QpdfError::Structure("No root catalog".to_string()))?;
        self.objects
            .get(&root_ref)
            .ok_or_else(|| QpdfError::Structure("Root object not found".to_string()))
    }

    pub fn get_root_ref(&self) -> Option<ObjRef> {
        self.root_ref
    }

    pub fn pdf_version(&self) -> &str {
        &self.pdf_version
    }

    pub fn set_pdf_version(&mut self, version: &str) {
        self.pdf_version = version.to_string();
    }

    pub fn extension_level(&self) -> i32 {
        self.extension_level
    }

    pub fn filename(&self) -> Option<&str> {
        self.filename.as_deref()
    }

    // ------------------------------------------------------------------------
    // Page Management
    // ------------------------------------------------------------------------

    pub fn get_all_pages(&mut self) -> Result<Vec<ObjRef>> {
        self.build_page_cache()?;
        Ok(self.page_cache.as_ref().unwrap().clone())
    }

    pub fn page_count(&mut self) -> Result<usize> {
        self.build_page_cache()?;
        Ok(self.page_cache.as_ref().map(|c| c.len()).unwrap_or(0))
    }

    pub fn add_page(&mut self, page: Object, first: bool) -> Result<()> {
        let page_ref = self.make_indirect_object(page);
        let pages_ref = self
            .get_root()?
            .as_dict()
            .and_then(|d| d.get(&Name::new("Pages")))
            .and_then(|o| o.as_obj_ref())
            .ok_or_else(|| QpdfError::Structure("No Pages in catalog".to_string()))?;

        let pages_obj = self
            .get_object_mut(pages_ref)
            .ok_or_else(|| QpdfError::Structure("Pages object not found".to_string()))?;

        if let Object::Dict(d) = pages_obj {
            let kids = d
                .get_mut(&Name::new("Kids"))
                .and_then(|o| o.as_array_mut())
                .ok_or_else(|| QpdfError::Structure("Pages has no Kids array".to_string()))?;
            if first {
                kids.insert(0, Object::Ref(page_ref));
            } else {
                kids.push(Object::Ref(page_ref));
            }
            if let Some(count_obj) = d.get_mut(&Name::new("Count")) {
                if let Object::Int(c) = count_obj {
                    *c += 1;
                }
            }
        }

        self.invalidate_page_cache();
        Ok(())
    }

    pub fn add_page_at(&mut self, page: Object, before: bool, ref_page: ObjRef) -> Result<()> {
        let idx = self
            .find_page(ref_page)
            .ok_or_else(|| QpdfError::NotFound("Reference page not found".to_string()))?;
        let page_ref = self.make_indirect_object(page);

        let pages_ref = self
            .get_root()?
            .as_dict()
            .and_then(|d| d.get(&Name::new("Pages")))
            .and_then(|o| o.as_obj_ref())
            .ok_or_else(|| QpdfError::Structure("No Pages in catalog".to_string()))?;

        let pages_obj = self
            .get_object_mut(pages_ref)
            .ok_or_else(|| QpdfError::Structure("Pages object not found".to_string()))?;

        if let Object::Dict(d) = pages_obj {
            let kids = d
                .get_mut(&Name::new("Kids"))
                .and_then(|o| o.as_array_mut())
                .ok_or_else(|| QpdfError::Structure("Pages has no Kids array".to_string()))?;
            let insert_idx = if before { idx } else { idx + 1 };
            kids.insert(insert_idx, Object::Ref(page_ref));
            if let Some(count_obj) = d.get_mut(&Name::new("Count")) {
                if let Object::Int(c) = count_obj {
                    *c += 1;
                }
            }
        }

        self.invalidate_page_cache();
        Ok(())
    }

    pub fn remove_page(&mut self, page_ref: ObjRef) -> Result<()> {
        let idx = self
            .find_page(page_ref)
            .ok_or_else(|| QpdfError::NotFound("Page not found".to_string()))?;

        let pages_ref = self
            .get_root()?
            .as_dict()
            .and_then(|d| d.get(&Name::new("Pages")))
            .and_then(|o| o.as_obj_ref())
            .ok_or_else(|| QpdfError::Structure("No Pages in catalog".to_string()))?;

        let pages_obj = self
            .get_object_mut(pages_ref)
            .ok_or_else(|| QpdfError::Structure("Pages object not found".to_string()))?;

        if let Object::Dict(d) = pages_obj {
            let kids = d
                .get_mut(&Name::new("Kids"))
                .and_then(|o| o.as_array_mut())
                .ok_or_else(|| QpdfError::Structure("Pages has no Kids array".to_string()))?;
            kids.remove(idx);
            if let Some(count_obj) = d.get_mut(&Name::new("Count")) {
                if let Object::Int(c) = count_obj {
                    *c = (*c - 1).max(0);
                }
            }
        }

        self.invalidate_page_cache();
        Ok(())
    }

    pub fn find_page(&self, page_ref: ObjRef) -> Option<usize> {
        self.page_cache
            .as_ref()
            .and_then(|c| c.iter().position(|r| *r == page_ref))
    }

    fn build_page_cache(&mut self) -> Result<()> {
        if self.page_cache.is_some() {
            return Ok(());
        }

        let root = self.get_root()?;
        let pages_ref = root
            .as_dict()
            .and_then(|d| d.get(&Name::new("Pages")))
            .and_then(|o| o.as_obj_ref())
            .ok_or_else(|| QpdfError::Structure("No Pages in catalog".to_string()))?;

        let mut pages = Vec::new();
        collect_pages(self, pages_ref, &mut pages, 0)?;
        self.page_cache = Some(pages);
        Ok(())
    }

    fn invalidate_page_cache(&mut self) {
        self.page_cache = None;
    }

    // ------------------------------------------------------------------------
    // Encryption
    // ------------------------------------------------------------------------

    pub fn is_encrypted(&self) -> bool {
        self.encrypted
    }

    pub fn encryption_params(&self) -> Option<&EncryptionParams> {
        self.encryption_params.as_ref()
    }

    pub fn permissions(&self) -> Option<Permissions> {
        self.encryption_params
            .as_ref()
            .map(|p| Permissions::from_p_value(p.p_value))
    }

    pub fn remove_security_restrictions(&mut self) {
        self.encrypted = false;
        self.encryption_key = None;
        self.encryption_params = None;
        if let Object::Dict(trailer) = &mut self.trailer {
            trailer.remove(&Name::new("Encrypt"));
        }
    }

    // ------------------------------------------------------------------------
    // Linearization
    // ------------------------------------------------------------------------

    pub fn is_linearized(&self) -> bool {
        self.linearized
    }

    // ------------------------------------------------------------------------
    // Warnings
    // ------------------------------------------------------------------------

    pub fn warn(&mut self, msg: String) {
        if !self.suppress_warnings {
            self.warnings.push(msg);
        }
    }

    pub fn get_warnings(&self) -> &[String] {
        &self.warnings
    }

    pub fn any_warnings(&self) -> bool {
        !self.warnings.is_empty()
    }

    pub fn set_suppress_warnings(&mut self, suppress: bool) {
        self.suppress_warnings = suppress;
    }

    // ------------------------------------------------------------------------
    // Utility
    // ------------------------------------------------------------------------

    pub fn fix_dangling_references(&mut self) {
        let all_refs: Vec<ObjRef> = self.objects.keys().copied().collect();
        for obj in self.objects.values_mut() {
            remove_dangling_refs(obj, &all_refs);
        }
    }

    pub fn push_inherited_attributes_to_pages(&mut self) -> Result<()> {
        let pages = self.get_all_pages()?;
        let skip = ["Type", "Parent", "Kids", "Count"];
        for page_ref in pages {
            let parent_attrs: Vec<(Name, Object)> = {
                let page_dict = match self.get_object(page_ref) {
                    Some(Object::Dict(d)) => d,
                    _ => continue,
                };
                let parent_ref = match page_dict
                    .get(&Name::new("Parent"))
                    .and_then(Object::as_obj_ref)
                {
                    Some(r) => r,
                    None => continue,
                };
                match self.get_object(parent_ref) {
                    Some(Object::Dict(parent_dict)) => parent_dict
                        .iter()
                        .filter(|(k, _)| !skip.contains(&k.as_str()))
                        .map(|(k, v)| (k.clone(), v.clone()))
                        .collect(),
                    _ => continue,
                }
            };
            if let Some(Object::Dict(page_dict)) = self.get_object_mut(page_ref) {
                for (k, v) in parent_attrs {
                    if !page_dict.contains_key(&k) {
                        page_dict.insert(k, v);
                    }
                }
            }
        }
        Ok(())
    }
}

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

// ============================================================================
// Helpers
// ============================================================================

const MAX_PAGE_TREE_DEPTH: usize = 64;

fn collect_pages(
    doc: &Document,
    node_ref: ObjRef,
    out: &mut Vec<ObjRef>,
    depth: usize,
) -> Result<()> {
    if depth > MAX_PAGE_TREE_DEPTH {
        return Err(QpdfError::Structure(
            "Page tree too deeply nested".to_string(),
        ));
    }
    let obj = doc
        .get_object(node_ref)
        .ok_or_else(|| QpdfError::Structure("Page tree node not found".to_string()))?;

    if let Object::Dict(d) = obj {
        let type_name = d
            .get(&Name::new("Type"))
            .and_then(|o| o.as_name())
            .map(|n| n.as_str());

        match type_name {
            Some("Page") => {
                out.push(node_ref);
            }
            Some("Pages") => {
                if let Some(kids) = d.get(&Name::new("Kids")).and_then(|o| o.as_array()) {
                    for kid in kids {
                        if let Object::Ref(r) = kid {
                            collect_pages(doc, *r, out, depth + 1)?;
                        }
                    }
                }
            }
            _ => {}
        }
    }
    Ok(())
}

fn remove_dangling_refs(obj: &mut Object, valid_refs: &[ObjRef]) {
    match obj {
        Object::Ref(r) => {
            if !valid_refs.contains(r) {
                *obj = Object::Null;
            }
        }
        Object::Array(arr) => {
            for o in arr {
                remove_dangling_refs(o, valid_refs);
            }
        }
        Object::Dict(d) => {
            for v in d.values_mut() {
                remove_dangling_refs(v, valid_refs);
            }
        }
        Object::Stream { dict, .. } => {
            for v in dict.values_mut() {
                remove_dangling_refs(v, valid_refs);
            }
        }
        _ => {}
    }
}

// ============================================================================
// PDF Parsing (from_bytes)
// ============================================================================

fn parse_xref_offset(data: &[u8]) -> Result<u64> {
    let mut i = 0;
    while i < data.len() && (data[i].is_ascii_whitespace() || data[i] == b'\r' || data[i] == b'\n')
    {
        i += 1;
    }
    let start = i;
    while i < data.len() && data[i].is_ascii_digit() {
        i += 1;
    }
    let num_str = std::str::from_utf8(&data[start..i])
        .map_err(|_| QpdfError::Parse("Invalid xref offset".to_string()))?;
    num_str
        .parse::<u64>()
        .map_err(|_| QpdfError::Parse("Invalid xref offset number".to_string()))
}

#[derive(Debug)]
struct XrefEntry {
    offset: u64,
    generation: u16,
    is_free: bool,
}

fn parse_xref_and_objects(data: &[u8], xref_offset: u64, doc: &mut Document) -> Result<()> {
    let offset = xref_offset as usize;
    if offset >= data.len() {
        return Err(QpdfError::Parse("Xref offset out of bounds".to_string()));
    }

    let mut pos = offset;
    let mut entries: Vec<(i32, XrefEntry)> = Vec::new();
    const MAX_XREF_SCAN_BYTES: usize = 1_000_000;
    let scan_limit = (offset + MAX_XREF_SCAN_BYTES).min(data.len());

    while pos < scan_limit {
        if data[pos..].starts_with(b"xref") {
            pos += 4;
            skip_whitespace(&data, &mut pos);

            let (start, count) = parse_xref_subsection_header(&data, &mut pos)?;
            for i in 0..count {
                let entry = parse_xref_line(&data, &mut pos)?;
                entries.push((start + i, entry));
            }
        } else if data[pos..].starts_with(b"trailer") {
            pos += 7;
            skip_whitespace(&data, &mut pos);
            let trailer_dict = parse_trailer_dict(&data, &mut pos)?;
            doc.trailer = Object::Dict(trailer_dict.clone());

            if let Some(Object::Ref(root_ref)) = trailer_dict.get(&Name::new("Root")) {
                doc.root_ref = Some(*root_ref);
            }
            break;
        } else {
            pos += 1;
        }
    }

    for (_num, entry) in entries {
        if entry.is_free {
            continue;
        }
        let obj_offset = entry.offset as usize;
        if obj_offset >= data.len() {
            continue;
        }
        let mut pos = obj_offset;
        match parse_object_at(data, &mut pos) {
            Ok((obj_ref, obj)) => {
                doc.objects.insert(obj_ref, obj);
                if doc.next_obj_num <= obj_ref.num {
                    doc.next_obj_num = obj_ref.num + 1;
                }
            }
            Err(_) => {}
        }
    }

    Ok(())
}

fn skip_whitespace(data: &[u8], pos: &mut usize) {
    while *pos < data.len() {
        let b = data[*pos];
        if b == b'%' {
            *pos += 1;
            while *pos < data.len() && data[*pos] != b'\n' && data[*pos] != b'\r' {
                *pos += 1;
            }
        } else if b.is_ascii_whitespace() || b == b'\r' || b == b'\n' {
            *pos += 1;
        } else {
            break;
        }
    }
}

fn parse_xref_subsection_header(data: &[u8], pos: &mut usize) -> Result<(i32, i32)> {
    skip_whitespace(data, pos);
    let start = parse_int(data, pos)?;
    skip_whitespace(data, pos);
    let count = parse_int(data, pos)?;
    Ok((start, count))
}

fn parse_xref_line(data: &[u8], pos: &mut usize) -> Result<XrefEntry> {
    skip_whitespace(data, pos);
    let offset_str = parse_digits(data, pos, 10)?;
    skip_whitespace(data, pos);
    let gen_str = parse_digits(data, pos, 5)?;
    skip_whitespace(data, pos);
    let typ = if *pos < data.len() { data[*pos] } else { b' ' };
    *pos += 1;
    skip_whitespace(data, pos);

    let offset = offset_str.parse::<u64>().unwrap_or(0);
    let generation = gen_str.parse::<u16>().unwrap_or(0);
    let is_free = typ == b'f';

    Ok(XrefEntry {
        offset,
        generation,
        is_free,
    })
}

fn parse_digits(data: &[u8], pos: &mut usize, max_len: usize) -> Result<String> {
    let start = *pos;
    let mut count = 0;
    while *pos < data.len() && data[*pos].is_ascii_digit() && count < max_len {
        *pos += 1;
        count += 1;
    }
    Ok(std::str::from_utf8(&data[start..*pos])
        .unwrap_or("")
        .to_string())
}

fn parse_int(data: &[u8], pos: &mut usize) -> Result<i32> {
    skip_whitespace(data, pos);
    let start = *pos;
    if *pos < data.len() && (data[*pos] == b'+' || data[*pos] == b'-') {
        *pos += 1;
    }
    while *pos < data.len() && data[*pos].is_ascii_digit() {
        *pos += 1;
    }
    std::str::from_utf8(&data[start..*pos])
        .unwrap_or("0")
        .parse::<i32>()
        .map_err(|_| QpdfError::Parse("Invalid integer".to_string()))
}

fn parse_trailer_dict(data: &[u8], pos: &mut usize) -> Result<Dict> {
    skip_whitespace(data, pos);
    if *pos + 2 > data.len() || &data[*pos..*pos + 2] != b"<<" {
        return Err(QpdfError::Parse("Expected << in trailer".to_string()));
    }
    *pos += 2;
    let dict = parse_dict(data, pos)?;
    Ok(dict)
}

fn parse_dict(data: &[u8], pos: &mut usize) -> Result<Dict> {
    let mut dict = HashMap::new();
    loop {
        skip_whitespace(data, pos);
        if *pos + 2 <= data.len() && &data[*pos..*pos + 2] == b">>" {
            *pos += 2;
            break;
        }
        if *pos >= data.len() {
            break;
        }
        if data[*pos] != b'/' {
            break;
        }
        *pos += 1;
        let name = parse_name(data, pos)?;
        skip_whitespace(data, pos);
        let value = parse_value(data, pos)?;
        dict.insert(Name::new(&name), value);
    }
    Ok(dict)
}

fn parse_name(data: &[u8], pos: &mut usize) -> Result<String> {
    let mut s = String::new();
    while *pos < data.len() {
        let b = data[*pos];
        if b == b'#' && *pos + 2 <= data.len() {
            let hex = std::str::from_utf8(&data[*pos + 1..*pos + 3]).ok();
            if let Some(h) = hex {
                if let Ok(byte) = u8::from_str_radix(h, 16) {
                    s.push(byte as char);
                    *pos += 3;
                    continue;
                }
            }
        }
        if b.is_ascii_whitespace()
            || b == b'/'
            || b == b'['
            || b == b']'
            || b == b'<'
            || b == b'>'
            || b == b'('
            || b == b')'
            || b == b'{'
            || b == b'}'
            || b == b'%'
        {
            break;
        }
        s.push(b as char);
        *pos += 1;
    }
    Ok(s)
}

fn parse_value(data: &[u8], pos: &mut usize) -> Result<Object> {
    skip_whitespace(data, pos);
    if *pos >= data.len() {
        return Err(QpdfError::Parse("Unexpected EOF".to_string()));
    }
    let b = data[*pos];
    if b == b'/' {
        *pos += 1;
        let name = parse_name(data, pos)?;
        return Ok(Object::Name(Name::new(&name)));
    }
    if b == b'[' {
        *pos += 1;
        let mut arr = vec![];
        loop {
            skip_whitespace(data, pos);
            if *pos < data.len() && data[*pos] == b']' {
                *pos += 1;
                break;
            }
            arr.push(parse_value(data, pos)?);
        }
        return Ok(Object::Array(arr));
    }
    if b == b'<' && *pos + 1 < data.len() && data[*pos + 1] != b'<' {
        return Ok(parse_hex_string(data, pos)?);
    }
    if b == b'(' {
        return Ok(parse_literal_string(data, pos)?);
    }
    if b == b'n' && *pos + 4 <= data.len() && &data[*pos..*pos + 4] == b"null" {
        *pos += 4;
        return Ok(Object::Null);
    }
    if b == b't' && *pos + 4 <= data.len() && &data[*pos..*pos + 4] == b"true" {
        *pos += 4;
        return Ok(Object::Bool(true));
    }
    if b == b'f' && *pos + 5 <= data.len() && &data[*pos..*pos + 5] == b"false" {
        *pos += 5;
        return Ok(Object::Bool(false));
    }
    if b == b'<' && *pos + 2 <= data.len() && &data[*pos..*pos + 2] == b"<<" {
        *pos += 2;
        return Ok(Object::Dict(parse_dict(data, pos)?));
    }
    if b.is_ascii_digit() || (b == b'-' && *pos + 1 < data.len() && data[*pos + 1].is_ascii_digit())
    {
        let save = *pos;
        if let Ok(num1) = parse_int(data, pos) {
            skip_whitespace(data, pos);
            if *pos < data.len()
                && (data[*pos].is_ascii_digit() || data[*pos] == b'+' || data[*pos] == b'-')
            {
                if let Ok(generation) = parse_int(data, pos) {
                    skip_whitespace(data, pos);
                    if *pos < data.len() && data[*pos] == b'R' {
                        *pos += 1;
                        return Ok(Object::Ref(ObjRef::new(num1, generation)));
                    }
                }
            }
        }
        *pos = save;
    }
    if b == b'+' || b == b'-' || b == b'.' || b.is_ascii_digit() {
        let (num, is_real) = parse_number(data, pos)?;
        return Ok(if is_real {
            Object::Real(num)
        } else {
            Object::Int(num as i64)
        });
    }
    Err(QpdfError::Parse(format!(
        "Unexpected byte 0x{:02x} at pos {}",
        b, *pos
    )))
}

fn parse_number(data: &[u8], pos: &mut usize) -> Result<(f64, bool)> {
    let start = *pos;
    if *pos < data.len() && (data[*pos] == b'+' || data[*pos] == b'-') {
        *pos += 1;
    }
    let mut is_real = false;
    while *pos < data.len() {
        let b = data[*pos];
        if b == b'.' {
            is_real = true;
            *pos += 1;
        } else if b.is_ascii_digit() {
            *pos += 1;
        } else {
            break;
        }
    }
    let s = std::str::from_utf8(&data[start..*pos]).unwrap_or("0");
    let num: f64 = s.parse().unwrap_or(0.0);
    Ok((num, is_real))
}

fn parse_hex_string(data: &[u8], pos: &mut usize) -> Result<Object> {
    if *pos >= data.len() || data[*pos] != b'<' {
        return Err(QpdfError::Parse("Expected <".to_string()));
    }
    *pos += 1;
    let mut bytes = Vec::new();
    let mut hex_pair = String::new();
    while *pos < data.len() && data[*pos] != b'>' {
        let b = data[*pos];
        if b.is_ascii_hexdigit() {
            hex_pair.push(b as char);
            if hex_pair.len() == 2 {
                if let Ok(byte) = u8::from_str_radix(&hex_pair, 16) {
                    bytes.push(byte);
                }
                hex_pair.clear();
            }
        }
        *pos += 1;
    }
    if *pos < data.len() {
        *pos += 1;
    }
    if !hex_pair.is_empty() {
        let padded = format!("{:0<2}", hex_pair);
        if let Ok(byte) = u8::from_str_radix(&padded, 16) {
            bytes.push(byte);
        }
    }
    Ok(Object::String(PdfString::new(bytes)))
}

fn parse_literal_string(data: &[u8], pos: &mut usize) -> Result<Object> {
    if *pos >= data.len() || data[*pos] != b'(' {
        return Err(QpdfError::Parse("Expected (".to_string()));
    }
    *pos += 1;
    let mut bytes = Vec::new();
    let mut depth = 1;
    while *pos < data.len() && depth > 0 {
        let b = data[*pos];
        *pos += 1;
        match b {
            b'(' => {
                depth += 1;
                bytes.push(b);
            }
            b')' => {
                depth -= 1;
                if depth > 0 {
                    bytes.push(b);
                }
            }
            b'\\' => {
                if *pos < data.len() {
                    let n = data[*pos];
                    *pos += 1;
                    match n {
                        b'n' => bytes.push(b'\n'),
                        b'r' => bytes.push(b'\r'),
                        b't' => bytes.push(b'\t'),
                        b'b' => bytes.push(b'\x08'),
                        b'f' => bytes.push(b'\x0C'),
                        b'(' => bytes.push(b'('),
                        b')' => bytes.push(b')'),
                        b'\\' => bytes.push(b'\\'),
                        b'0'..=b'7' => {
                            let mut octal = (n - b'0') as u32;
                            for _ in 0..2 {
                                if *pos < data.len() && (b'0'..=b'7').contains(&data[*pos]) {
                                    octal = octal * 8 + (data[*pos] - b'0') as u32;
                                    *pos += 1;
                                }
                            }
                            bytes.push(octal as u8);
                        }
                        _ => bytes.push(n),
                    }
                }
            }
            _ => bytes.push(b),
        }
    }
    Ok(Object::String(PdfString::new(bytes)))
}

fn parse_object_at(data: &[u8], pos: &mut usize) -> Result<(ObjRef, Object)> {
    skip_whitespace(data, pos);
    let num = parse_int(data, pos)?;
    skip_whitespace(data, pos);
    let generation = parse_int(data, pos)?;
    skip_whitespace(data, pos);
    if *pos + 3 <= data.len() && &data[*pos..*pos + 3] == b"obj" {
        *pos += 3;
    }
    skip_whitespace(data, pos);

    let obj_ref = ObjRef::new(num, generation);
    let obj = parse_value(data, pos)?;

    let obj = if let Object::Dict(ref dict) = obj {
        skip_whitespace(data, pos);
        if *pos + 6 <= data.len() && &data[*pos..*pos + 6] == b"stream" {
            *pos += 6;
            if *pos < data.len() && data[*pos] == b'\r' {
                *pos += 1;
            }
            if *pos < data.len() && data[*pos] == b'\n' {
                *pos += 1;
            }
            let len = dict
                .get(&Name::new("Length"))
                .and_then(|o| o.as_int())
                .unwrap_or(0) as usize;
            let stream_start = *pos;
            *pos += len;
            let stream_data = data[stream_start..*pos].to_vec();
            skip_whitespace(data, pos);
            if *pos + 9 <= data.len() && &data[*pos..*pos + 9] == b"endstream" {
                *pos += 9;
            }
            Object::Stream {
                dict: dict.clone(),
                data: stream_data,
            }
        } else {
            obj
        }
    } else {
        obj
    };

    skip_whitespace(data, pos);
    if *pos + 6 <= data.len() && &data[*pos..*pos + 6] == b"endobj" {
        *pos += 6;
    }
    Ok((obj_ref, obj))
}

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

    #[test]
    fn test_document_new() {
        let doc = Document::new();
        assert_eq!(doc.object_count(), 0);
        assert_eq!(doc.pdf_version(), "1.7");
        assert!(!doc.is_encrypted());
    }

    #[test]
    fn test_empty_pdf() {
        let doc = Document::empty_pdf();
        assert!(doc.get_root_ref().is_some());
        assert!(doc.get_root().is_ok());
        let root = doc.get_root().unwrap();
        assert!(root.as_dict().unwrap().contains_key(&Name::new("Pages")));
    }

    #[test]
    fn test_make_indirect_object() {
        let mut doc = Document::new();
        let ref1 = doc.make_indirect_object(Object::Int(42));
        let ref2 = doc.make_indirect_object(Object::Bool(true));
        assert_eq!(doc.get_object(ref1), Some(&Object::Int(42)));
        assert_eq!(doc.get_object(ref2), Some(&Object::Bool(true)));
        assert_eq!(ref1.num, 1);
        assert_eq!(ref2.num, 2);
    }

    #[test]
    fn test_replace_object() {
        let mut doc = Document::new();
        let r = doc.make_indirect_object(Object::Int(1));
        doc.replace_object(r, Object::Int(2));
        assert_eq!(doc.get_object(r), Some(&Object::Int(2)));
    }

    #[test]
    fn test_new_stream() {
        let mut doc = Document::new();
        let r = doc.new_stream(b"hello".to_vec());
        let obj = doc.get_object(r).unwrap();
        if let Object::Stream { dict, data } = obj {
            assert_eq!(data, b"hello");
            assert_eq!(
                dict.get(&Name::new("Length")).and_then(|o| o.as_int()),
                Some(5)
            );
        } else {
            panic!("Expected stream");
        }
    }

    #[test]
    fn test_from_bytes_minimal() {
        let pdf = concat!(
            "%PDF-1.4\n",
            "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n",
            "2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n",
            "xref\n0 3\n",
            "0000000000 65535 f \n",
            "0000000009 00000 n \n",
            "0000000052 00000 n \n",
            "trailer\n<< /Size 3 /Root 1 0 R >>\n",
            "startxref\n95\n%%EOF"
        );
        let pdf_bytes = pdf.as_bytes().to_vec();
        let result = Document::from_bytes(pdf_bytes);
        if let Err(e) = &result {
            eprintln!("Parse error: {}", e);
        }
        assert!(result.is_ok(), "from_bytes should parse minimal PDF");
    }

    #[test]
    fn test_from_bytes_invalid_header() {
        let result = Document::from_bytes(b"not a pdf".to_vec());
        assert!(result.is_err());
    }

    #[test]
    fn test_permissions_from_p_value() {
        let p = Permissions::from_p_value(-4);
        assert!(p.accessibility || p.extract || p.print_low_res);
    }

    #[test]
    fn test_build_page_cache() {
        let mut doc = Document::empty_pdf();
        let pages = doc.get_all_pages().unwrap();
        assert_eq!(pages.len(), 0);
    }

    #[test]
    fn test_add_page() {
        let mut doc = Document::empty_pdf();
        let mut page_dict = Dict::new();
        page_dict.insert(Name::new("Type"), Object::Name(Name::new("Page")));
        page_dict.insert(
            Name::new("MediaBox"),
            Object::Array(vec![
                Object::Int(0),
                Object::Int(0),
                Object::Int(612),
                Object::Int(792),
            ]),
        );
        doc.add_page(Object::Dict(page_dict), false).unwrap();
        assert_eq!(doc.page_count().unwrap(), 1);
    }

    #[test]
    fn test_warnings() {
        let mut doc = Document::new();
        doc.warn("test warning".to_string());
        assert!(doc.any_warnings());
        assert_eq!(doc.get_warnings().len(), 1);
    }
}
