//! PDF Reader - Parse and read PDF files
//!
//! This module provides functionality to parse PDF files and extract their structure.

use super::error::{EnhancedError, Result};
use super::object_stream::ObjectStreamParser;
use crate::pdf::crypt::{Crypt, EncryptionAlgorithm, PasswordType};
use crate::pdf::object::{Dict, Name, Object};
use crate::pdf::xref::{XrefEntry, XrefTable};
use std::collections::HashMap;
use std::fs;
use std::path::Path;

/// PDF document structure
pub struct PdfDocument {
    /// PDF file data
    data: Vec<u8>,
    /// Cross-reference table
    xref: XrefTable,
    /// Root object (Catalog)
    root: Option<i32>,
    /// Pages object
    pages: Option<i32>,
    /// Page objects (ordered list)
    page_objects: Vec<i32>,
    /// Encryption context (None if document is not encrypted)
    crypt: Option<Crypt>,
    /// Whether the document has been authenticated
    authenticated: bool,
}

impl PdfDocument {
    /// Open and parse a PDF file
    pub fn open(path: &str) -> Result<Self> {
        if !Path::new(path).exists() {
            return Err(EnhancedError::Io(std::io::Error::new(
                std::io::ErrorKind::NotFound,
                format!("PDF file not found: {}", path),
            )));
        }

        let data = fs::read(path)?;
        if !data.starts_with(b"%PDF-") {
            return Err(EnhancedError::InvalidParameter(
                "Not a valid PDF file".into(),
            ));
        }

        let mut doc = Self {
            data,
            xref: XrefTable::new(),
            root: None,
            pages: None,
            page_objects: Vec::new(),
            crypt: None,
            authenticated: false,
        };

        doc.parse()?;
        Ok(doc)
    }

    /// Parse the PDF structure
    fn parse(&mut self) -> Result<()> {
        self.parse_xref()?;
        self.detect_encryption();
        self.find_root()?;
        self.find_pages()?;
        Ok(())
    }

    /// Open a password-protected PDF file
    pub fn open_with_password(path: &str, password: &str) -> Result<Self> {
        let mut doc = Self::open(path)?;
        if doc.is_encrypted() && !doc.authenticated {
            let pw_type = doc.authenticate(password.as_bytes());
            if pw_type == PasswordType::Invalid {
                return Err(EnhancedError::InvalidParameter(
                    "Invalid password for encrypted PDF".into(),
                ));
            }
        }
        Ok(doc)
    }

    /// Check if the document is encrypted
    pub fn is_encrypted(&self) -> bool {
        self.crypt.is_some()
    }

    /// Check if the document has been authenticated
    pub fn is_authenticated(&self) -> bool {
        self.authenticated
    }

    /// Try to authenticate with the given password.
    /// Returns the type of password that matched.
    pub fn authenticate(&mut self, password: &[u8]) -> PasswordType {
        let content = String::from_utf8_lossy(&self.data).into_owned();

        let u_value = Self::extract_hex_string(&content, "/U")
            .or_else(|| Self::extract_paren_string(&content, "/U"))
            .unwrap_or_default();

        if let Some(ref crypt) = self.crypt {
            let result = crypt.try_authenticate(password, &u_value);
            if result != PasswordType::Invalid {
                self.authenticated = true;
            }
            result
        } else {
            self.authenticated = true;
            PasswordType::User
        }
    }

    /// Get the encryption context
    pub fn crypt(&self) -> Option<&Crypt> {
        self.crypt.as_ref()
    }

    /// Detect encryption from the PDF trailer/encrypt dictionary
    fn detect_encryption(&mut self) {
        let content = String::from_utf8_lossy(&self.data).into_owned();

        // Look for /Encrypt reference in trailer or xref stream
        let encrypt_ref = Self::extract_int(&content, "/Encrypt");
        if encrypt_ref.is_none() {
            // Not encrypted — auto-authenticate
            self.authenticated = true;
            return;
        }

        // Find /Encrypt dictionary parameters
        let v = Self::extract_int(&content, "/V").unwrap_or(0);
        let r = Self::extract_int(&content, "/R").unwrap_or(0);
        let length = Self::extract_int(&content, "/Length").unwrap_or(40);
        let p = Self::extract_int(&content, "/P").unwrap_or(-1);

        let algorithm = match (v, length) {
            (1, _) => EncryptionAlgorithm::Rc4_40,
            (2, l) if l <= 40 => EncryptionAlgorithm::Rc4_40,
            (2, _) => EncryptionAlgorithm::Rc4_128,
            (4, _) => EncryptionAlgorithm::Aes128,
            (5, _) => EncryptionAlgorithm::Aes256,
            _ => EncryptionAlgorithm::Rc4_128,
        };

        let o_value = Self::extract_hex_string(&content, "/O")
            .or_else(|| Self::extract_paren_string(&content, "/O"))
            .unwrap_or_default();
        let u_value = Self::extract_hex_string(&content, "/U")
            .or_else(|| Self::extract_paren_string(&content, "/U"))
            .unwrap_or_default();

        // Extract document ID from the /ID array
        let doc_id = Self::extract_doc_id(&content).unwrap_or_default();

        if let Ok(crypt) = Crypt::new(algorithm, v, r, o_value, u_value, p as u32, doc_id) {
            // Try empty password (many PDFs only have owner password protection)
            let u_val = Self::extract_hex_string(&content, "/U")
                .or_else(|| Self::extract_paren_string(&content, "/U"))
                .unwrap_or_default();
            if crypt.authenticate_user_password(b"", &u_val) {
                self.authenticated = true;
            }
            self.crypt = Some(crypt);
        }
    }

    fn extract_hex_string(content: &str, key: &str) -> Option<Vec<u8>> {
        let pos = content.find(key)?;
        let after = &content[pos + key.len()..];
        let lt = after.find('<')?;
        let gt = after[lt..].find('>')?;
        let hex = &after[lt + 1..lt + gt];
        let clean: String = hex.chars().filter(|c| c.is_ascii_hexdigit()).collect();
        let bytes: Vec<u8> = (0..clean.len())
            .step_by(2)
            .filter_map(|i| {
                if i + 2 <= clean.len() {
                    u8::from_str_radix(&clean[i..i + 2], 16).ok()
                } else {
                    None
                }
            })
            .collect();
        if bytes.is_empty() { None } else { Some(bytes) }
    }

    fn extract_paren_string(content: &str, key: &str) -> Option<Vec<u8>> {
        let pos = content.find(key)?;
        let after = &content[pos + key.len()..];
        let open = after.find('(')?;
        let close = after[open..].find(')')?;
        let inner = &after[open + 1..open + close];
        Some(inner.as_bytes().to_vec())
    }

    fn extract_doc_id(content: &str) -> Option<Vec<u8>> {
        let pos = content.find("/ID")?;
        let after = &content[pos + 3..];
        let bracket = after.find('[')?;
        let inner = &after[bracket + 1..];
        // First hex string in the array
        let lt = inner.find('<')?;
        let gt = inner[lt..].find('>')?;
        let hex = &inner[lt + 1..lt + gt];
        let clean: String = hex.chars().filter(|c| c.is_ascii_hexdigit()).collect();
        let bytes: Vec<u8> = (0..clean.len())
            .step_by(2)
            .filter_map(|i| {
                if i + 2 <= clean.len() {
                    u8::from_str_radix(&clean[i..i + 2], 16).ok()
                } else {
                    None
                }
            })
            .collect();
        if bytes.is_empty() { None } else { Some(bytes) }
    }

    /// Parse xref table or xref stream
    fn parse_xref(&mut self) -> Result<()> {
        let startxref_offset = self.find_startxref()?;

        if self.is_xref_stream(startxref_offset) {
            self.parse_xref_stream(startxref_offset)?;
        } else {
            self.parse_traditional_xref()?;
        }

        Ok(())
    }

    /// Find the byte offset indicated by startxref
    fn find_startxref(&self) -> Result<usize> {
        let search_region = if self.data.len() > 1024 {
            &self.data[self.data.len() - 1024..]
        } else {
            &self.data
        };
        let region_str = String::from_utf8_lossy(search_region);

        if let Some(pos) = region_str.rfind("startxref") {
            let after = &region_str[pos + 9..];
            let trimmed = after.trim();
            let num_end = trimmed
                .find(|c: char| !c.is_ascii_digit())
                .unwrap_or(trimmed.len());
            if num_end > 0 {
                if let Ok(offset) = trimmed[..num_end].parse::<usize>() {
                    return Ok(offset);
                }
            }
        }
        Err(EnhancedError::InvalidParameter("No startxref found".into()))
    }

    /// Check if the xref at the given offset is a stream (not a traditional table)
    fn is_xref_stream(&self, offset: usize) -> bool {
        if offset >= self.data.len() {
            return false;
        }
        let region = &self.data[offset..self.data.len().min(offset + 20)];
        let s = String::from_utf8_lossy(region);
        let trimmed = s.trim_start();
        trimmed.starts_with(|c: char| c.is_ascii_digit())
    }

    /// Parse a cross-reference stream (PDF 1.5+)
    fn parse_xref_stream(&mut self, offset: usize) -> Result<()> {
        use crate::pdf::filter::decode_flate;
        use crate::pdf::filter::params::FlateDecodeParams;
        use crate::pdf::filter::predictor::apply_predictor_decode;

        // Copy the region to an owned String to avoid borrowing self.data
        let after = &self.data[offset..];
        let after_str = String::from_utf8_lossy(after).into_owned();

        let dict_start = after_str
            .find("<<")
            .ok_or_else(|| EnhancedError::InvalidParameter("No dict in xref stream".into()))?;
        let stream_kw = after_str.find("stream").ok_or_else(|| {
            EnhancedError::InvalidParameter("No stream keyword in xref stream".into())
        })?;
        let dict_str = &after_str[dict_start..stream_kw];

        let w = Self::parse_int_array(dict_str, "/W")
            .ok_or_else(|| EnhancedError::InvalidParameter("/W not found in xref stream".into()))?;
        if w.len() != 3 {
            return Err(EnhancedError::InvalidParameter(
                "/W must have 3 elements".into(),
            ));
        }
        let entry_width = w[0] as usize + w[1] as usize + w[2] as usize;
        let size = Self::extract_int(dict_str, "/Size").unwrap_or(0) as usize;
        let index = Self::parse_int_array(dict_str, "/Index");
        let prev = Self::extract_int(dict_str, "/Prev");
        let is_flate = dict_str.contains("FlateDecode") || dict_str.contains("/Fl");
        let predictor = Self::extract_int(dict_str, "/Predictor").unwrap_or(1);
        let columns = Self::extract_int(dict_str, "/Columns").unwrap_or(entry_width as i32);
        let root_ref = Self::extract_ref(dict_str, "/Root");

        // Get stream data using owned offsets (no more borrowing dict_str after this)
        let stream_abs = offset + stream_kw + 6;
        let data_start = if self.data.get(stream_abs) == Some(&b'\r')
            && self.data.get(stream_abs + 1) == Some(&b'\n')
        {
            stream_abs + 2
        } else if self.data.get(stream_abs) == Some(&b'\n')
            || self.data.get(stream_abs) == Some(&b'\r')
        {
            stream_abs + 1
        } else {
            stream_abs
        };

        let endstream_pos = Self::find_bytes(&self.data[data_start..], b"endstream")
            .ok_or_else(|| EnhancedError::InvalidParameter("No endstream".into()))?;
        let raw_stream = self.data[data_start..data_start + endstream_pos].to_vec();

        let mut decompressed = if is_flate && !raw_stream.is_empty() {
            decode_flate(&raw_stream, None)
                .map_err(|e| EnhancedError::Generic(format!("Xref stream decompress: {}", e)))?
        } else {
            raw_stream
        };

        if predictor > 1 {
            let params = FlateDecodeParams {
                predictor,
                colors: 1,
                bits_per_component: 8,
                columns,
            };
            decompressed = apply_predictor_decode(&decompressed, &params)
                .map_err(|e| EnhancedError::Generic(format!("Predictor decode: {}", e)))?;
        }

        let index_ranges: Vec<(usize, usize)> = if let Some(ref idx) = index {
            idx.chunks(2)
                .map(|c| (c[0] as usize, c[1] as usize))
                .collect()
        } else {
            vec![(0, size)]
        };

        let mut data_pos = 0;
        for (start, count) in &index_ranges {
            for i in 0..*count {
                if data_pos + entry_width > decompressed.len() {
                    break;
                }
                let entry_data = &decompressed[data_pos..data_pos + entry_width];
                let mut pos = 0;

                let etype = if w[0] > 0 {
                    Self::read_be_uint(&entry_data[pos..pos + w[0] as usize])
                } else {
                    1
                };
                pos += w[0] as usize;

                let field2 = Self::read_be_uint(&entry_data[pos..pos + w[1] as usize]);
                pos += w[1] as usize;

                let field3 = if w[2] > 0 {
                    Self::read_be_uint(&entry_data[pos..pos + w[2] as usize])
                } else {
                    0
                };

                let obj_num = (*start + i) as i32;
                let entry = match etype {
                    0 => XrefEntry::free(obj_num, field3 as u16),
                    1 => XrefEntry::in_use(obj_num, field3 as u16, field2 as i64),
                    2 => XrefEntry::compressed(obj_num, field2 as i64, field3 as u16),
                    _ => XrefEntry::free(obj_num, 0),
                };
                self.xref.add_entry(entry);
                data_pos += entry_width;
            }
        }

        // Follow /Prev chain
        if let Some(prev_offset) = prev {
            let prev_off = prev_offset as usize;
            if prev_off < self.data.len() {
                if self.is_xref_stream(prev_off) {
                    let _ = self.parse_xref_stream(prev_off);
                } else {
                    let _ = self.parse_traditional_xref_at(prev_off);
                }
            }
        }

        if self.root.is_none() {
            if let Some(root_num) = root_ref {
                self.root = Some(root_num);
            }
        }

        Ok(())
    }

    /// Parse traditional xref table starting from the last "xref" keyword
    fn parse_traditional_xref(&mut self) -> Result<()> {
        let content = String::from_utf8_lossy(&self.data);
        let xref_pos = content
            .rfind("xref")
            .ok_or_else(|| EnhancedError::InvalidParameter("No xref table found".into()))?;
        self.parse_traditional_xref_at(xref_pos)
    }

    /// Parse traditional xref table at a specific byte position
    fn parse_traditional_xref_at(&mut self, xref_pos: usize) -> Result<()> {
        let content = String::from_utf8_lossy(&self.data);
        let lines: Vec<&str> = content[xref_pos..].lines().collect();
        let mut i = 1; // Skip "xref" line

        while i < lines.len() {
            let line = lines[i].trim();
            if line.is_empty() || line == "trailer" {
                break;
            }

            if let Some(space) = line.find(' ') {
                let start_str = &line[..space];
                let count_str = line[space..].trim();

                if let (Ok(start), Ok(count)) =
                    (start_str.parse::<i32>(), count_str.parse::<usize>())
                {
                    i += 1;
                    for j in 0..count {
                        if i + j < lines.len() {
                            let entry_line = lines[i + j].trim();
                            let parts: Vec<&str> = entry_line.split_whitespace().collect();
                            if parts.len() >= 3 {
                                if let (Ok(offset), Ok(generation)) =
                                    (parts[0].parse::<i64>(), parts[1].parse::<u16>())
                                {
                                    let entry = if parts[2] == "f" {
                                        XrefEntry::free(start + j as i32, generation)
                                    } else {
                                        XrefEntry::in_use(start + j as i32, generation, offset)
                                    };
                                    self.xref.add_entry(entry);
                                }
                            }
                        }
                    }
                    i += count;
                } else {
                    i += 1;
                }
            } else {
                i += 1;
            }
        }

        Ok(())
    }

    // ---- Helper functions for xref stream parsing ----

    fn read_be_uint(data: &[u8]) -> u64 {
        let mut val: u64 = 0;
        for &b in data {
            val = (val << 8) | b as u64;
        }
        val
    }

    fn extract_int(s: &str, key: &str) -> Option<i32> {
        let pos = s.find(key)?;
        let after = s[pos + key.len()..].trim_start();
        let mut num = String::new();
        for c in after.chars() {
            if c.is_ascii_digit() || (num.is_empty() && c == '-') {
                num.push(c);
            } else if !num.is_empty() {
                break;
            }
        }
        num.parse().ok()
    }

    fn extract_ref(s: &str, key: &str) -> Option<i32> {
        let pos = s.find(key)?;
        let after = s[pos + key.len()..].trim_start();
        let mut num = String::new();
        for c in after.chars() {
            if c.is_ascii_digit() {
                num.push(c);
            } else if !num.is_empty() {
                break;
            }
        }
        num.parse().ok()
    }

    fn parse_int_array(s: &str, key: &str) -> Option<Vec<i32>> {
        let pos = s.find(key)?;
        let after = &s[pos + key.len()..];
        let bracket_start = after.find('[')?;
        let bracket_end = after[bracket_start..].find(']')?;
        let inner = &after[bracket_start + 1..bracket_start + bracket_end];
        let nums: Vec<i32> = inner
            .split_whitespace()
            .filter_map(|n| n.parse().ok())
            .collect();
        if nums.is_empty() { None } else { Some(nums) }
    }

    fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
        haystack.windows(needle.len()).position(|w| w == needle)
    }

    /// Find root (Catalog) object
    fn find_root(&mut self) -> Result<()> {
        let content = String::from_utf8_lossy(&self.data);

        // Method 1: Look for /Root in trailer (traditional PDF)
        if let Some(trailer_pos) = content.rfind("trailer") {
            let trailer_section = &content[trailer_pos..];
            if let Some(root_num) = Self::extract_root_from_section(&trailer_section) {
                self.root = Some(root_num);
                return Ok(());
            }
        }

        // Method 2: Look for /Root anywhere in the PDF (handles xref streams, linearized PDFs)
        // Some PDFs embed the trailer dictionary in an xref stream object
        if let Some(root_num) = Self::extract_root_from_section(&content) {
            self.root = Some(root_num);
            return Ok(());
        }

        // Method 3: Look for /Type /Catalog to find the Root object directly
        for (i, _) in content.match_indices("/Type") {
            let section = &content[i..];
            if section.len() > 20 {
                let check = &section[..section.len().min(50)];
                if check.contains("/Catalog") {
                    if let Some(obj_num) = Self::find_object_number_before(&content, i) {
                        self.root = Some(obj_num);
                        return Ok(());
                    }
                }
            }
        }

        // Method 4: Search inside object streams for /Type /Catalog
        if let Ok(all_objs) = super::object_stream::parse_all_object_streams(&self.data) {
            for (&obj_num, obj_data) in &all_objs {
                if (obj_data.contains("/Type/Catalog") || obj_data.contains("/Type /Catalog"))
                    && !obj_data.contains("/Type/CatalogX")
                {
                    self.root = Some(obj_num);
                    return Ok(());
                }
            }
        }

        Err(EnhancedError::InvalidParameter(
            "Could not find Root object".into(),
        ))
    }

    /// Extract root object number from a section containing /Root
    fn extract_root_from_section(section: &str) -> Option<i32> {
        if let Some(root_pos) = section.find("/Root") {
            let after_root = &section[root_pos + 5..];
            // Skip whitespace and look for object reference
            let trimmed = after_root.trim_start();

            // Handle both "/Root N 0 R" and "/Root N gen R" formats
            let mut chars = trimmed.chars().peekable();
            let mut num_str = String::new();

            // Skip potential leading whitespace or special chars
            while let Some(&c) = chars.peek() {
                if c.is_ascii_digit() || c == '-' {
                    break;
                }
                chars.next();
            }

            // Collect digits
            while let Some(&c) = chars.peek() {
                if c.is_ascii_digit() {
                    num_str.push(c);
                    chars.next();
                } else {
                    break;
                }
            }

            if let Ok(num) = num_str.parse::<i32>() {
                return Some(num);
            }
        }
        None
    }

    /// Find object number before a given position (for /Type /Catalog matching)
    fn find_object_number_before(content: &str, pos: usize) -> Option<i32> {
        // Look backwards for "N 0 obj" pattern
        let before = &content[..pos];

        // Find the last "obj" keyword before pos
        if let Some(obj_pos) = before.rfind(" obj") {
            let section_before_obj = &before[..obj_pos];
            // Extract the number before "obj"
            let mut num_end = section_before_obj.len();
            let mut num_start;

            // Find end of number (skip whitespace backwards)
            while num_end > 0 && section_before_obj.as_bytes()[num_end - 1].is_ascii_whitespace() {
                num_end -= 1;
            }

            // Skip generation number
            while num_end > 0 && section_before_obj.as_bytes()[num_end - 1].is_ascii_digit() {
                num_end -= 1;
            }

            // Skip whitespace
            while num_end > 0 && section_before_obj.as_bytes()[num_end - 1].is_ascii_whitespace() {
                num_end -= 1;
            }

            // Find start of object number
            num_start = num_end;
            while num_start > 0 && section_before_obj.as_bytes()[num_start - 1].is_ascii_digit() {
                num_start -= 1;
            }

            if num_start < num_end {
                if let Ok(num) = section_before_obj[num_start..num_end].parse::<i32>() {
                    return Some(num);
                }
            }
        }
        None
    }

    /// Find pages object and page list
    fn find_pages(&mut self) -> Result<()> {
        // Search for Pages object using byte-level searching to avoid UTF-8 boundary issues
        let pages_type_pattern = b"/Type/Pages";
        let pages_type_pattern_space = b"/Type /Pages";

        let mut pages_obj_num = None;

        // Search for /Type/Pages or /Type /Pages pattern in raw bytes
        for (i, window) in self.data.windows(pages_type_pattern.len()).enumerate() {
            if window == pages_type_pattern {
                pages_obj_num = Self::find_obj_number_before_byte_pos(&self.data, i);
                if pages_obj_num.is_some() {
                    break;
                }
            }
        }

        // If not found, try with space
        if pages_obj_num.is_none() {
            for (i, window) in self
                .data
                .windows(pages_type_pattern_space.len())
                .enumerate()
            {
                if window == pages_type_pattern_space {
                    pages_obj_num = Self::find_obj_number_before_byte_pos(&self.data, i);
                    if pages_obj_num.is_some() {
                        break;
                    }
                }
            }
        }

        // ObjStm fallback: resolve catalog to find /Pages reference
        if pages_obj_num.is_none() {
            if let Some(root_num) = self.root {
                if let Some(catalog_data) = self.resolve_object_data(root_num) {
                    if let Some(pages_pos) = catalog_data.find("/Pages") {
                        let after_pages = &catalog_data[pages_pos + 6..];
                        let trimmed = after_pages.trim_start();
                        let num: String =
                            trimmed.chars().take_while(|c| c.is_ascii_digit()).collect();
                        if let Ok(n) = num.parse::<i32>() {
                            pages_obj_num = Some(n);
                        }
                    }
                }
            }
        }

        if let Some(pages_num) = pages_obj_num {
            self.pages = Some(pages_num);
        }

        // Find page count
        let page_count = self.get_page_count()?;

        // Find individual page objects by looking for /Type /Page (singular, not /Pages)
        self.find_page_objects(page_count);

        Ok(())
    }

    /// Find object number before a byte position (for binary PDF data)
    fn find_obj_number_before_byte_pos(data: &[u8], pos: usize) -> Option<i32> {
        // Look backwards for " obj" pattern
        let obj_pattern = b" obj";
        let search_start = pos.saturating_sub(500); // Look back up to 500 bytes
        let search_data = &data[search_start..pos];

        // Find the last " obj" in the search region
        let mut last_obj_pos = None;
        for i in 0..search_data.len().saturating_sub(obj_pattern.len()) {
            if &search_data[i..i + obj_pattern.len()] == obj_pattern {
                last_obj_pos = Some(search_start + i);
            }
        }

        if let Some(obj_pos) = last_obj_pos {
            // Look backwards from obj_pos to find the object number
            // Format: "N M obj" where N is object number, M is generation
            let before_obj = &data[obj_pos.saturating_sub(30)..obj_pos];

            // Find the digits working backwards
            let mut digits_end = before_obj.len();

            // Skip any whitespace at the end (before "obj")
            while digits_end > 0 && before_obj[digits_end - 1].is_ascii_whitespace() {
                digits_end -= 1;
            }

            // Skip generation number
            while digits_end > 0 && before_obj[digits_end - 1].is_ascii_digit() {
                digits_end -= 1;
            }

            // Skip whitespace between object number and generation
            while digits_end > 0 && before_obj[digits_end - 1].is_ascii_whitespace() {
                digits_end -= 1;
            }

            // Now read the object number
            let mut digits_start = digits_end;
            while digits_start > 0 && before_obj[digits_start - 1].is_ascii_digit() {
                digits_start -= 1;
            }

            if digits_start < digits_end {
                let num_str = String::from_utf8_lossy(&before_obj[digits_start..digits_end]);
                if let Ok(num) = num_str.parse::<i32>() {
                    return Some(num);
                }
            }
        }

        None
    }

    /// Find individual page objects by traversing the page tree via /Kids arrays.
    fn find_page_objects(&mut self, expected_count: usize) {
        if let Some(root_pages_num) = self.pages {
            // Try raw byte tree traversal first
            let mut ordered = Vec::new();
            Self::collect_pages_from_tree(&self.data, root_pages_num, &mut ordered, 0);
            if !ordered.is_empty() {
                self.page_objects = ordered;
                return;
            }

            // Fallback: try resolved tree traversal (handles ObjStm)
            let mut resolved_ordered = Vec::new();
            self.collect_pages_from_tree_resolved(root_pages_num, &mut resolved_ordered, 0);
            if !resolved_ordered.is_empty() {
                self.page_objects = resolved_ordered;
                return;
            }
        }

        self.find_page_objects_linear_fallback(expected_count);
    }

    /// Recursively traverse the page tree using resolve_object_data (handles ObjStm).
    fn collect_pages_from_tree_resolved(&self, obj_num: i32, pages: &mut Vec<i32>, depth: usize) {
        if depth > 20 {
            return;
        }

        let body = match self.resolve_object_data(obj_num) {
            Some(b) => b,
            None => return,
        };

        let is_pages_node = body.contains("/Type/Pages") || body.contains("/Type /Pages");

        if is_pages_node {
            // Parse /Kids array from the string
            if let Some(kids_pos) = body.find("/Kids") {
                let after_kids = &body[kids_pos + 5..];
                if let Some(bracket_start) = after_kids.find('[') {
                    if let Some(bracket_end) = after_kids[bracket_start..].find(']') {
                        let array_str = &after_kids[bracket_start + 1..bracket_start + bracket_end];
                        let parts: Vec<&str> = array_str.split_whitespace().collect();
                        let mut i = 0;
                        while i + 2 < parts.len() {
                            let r_part = parts[i + 2];
                            if r_part == "R" || r_part.starts_with("R/") || r_part.starts_with("R>")
                            {
                                if let Ok(kid_obj) = parts[i].parse::<i32>() {
                                    self.collect_pages_from_tree_resolved(
                                        kid_obj,
                                        pages,
                                        depth + 1,
                                    );
                                }
                            }
                            i += 3;
                        }
                    }
                }
            }
        } else {
            let is_page = (body.contains("/Type/Page") && !body.contains("/Type/Pages"))
                || (body.contains("/Type /Page") && !body.contains("/Type /Pages"));
            if is_page {
                pages.push(obj_num);
            }
        }
    }

    /// Recursively traverse the page tree to collect leaf page objects in order.
    fn collect_pages_from_tree(data: &[u8], obj_num: i32, pages: &mut Vec<i32>, depth: usize) {
        if depth > 20 {
            return;
        }

        let body = match Self::find_object_body_bytes(data, obj_num) {
            Some(b) => b,
            None => return,
        };

        let is_pages_node = body
            .windows(b"/Type/Pages".len())
            .any(|w| w == b"/Type/Pages")
            || body
                .windows(b"/Type /Pages".len())
                .any(|w| w == b"/Type /Pages");

        if is_pages_node {
            for kid in Self::parse_kids_from_bytes(&body) {
                Self::collect_pages_from_tree(data, kid, pages, depth + 1);
            }
        } else {
            let is_page = body.windows(b"/Type/Page".len()).enumerate().any(|(i, w)| {
                w == b"/Type/Page"
                    && (i + b"/Type/Page".len() >= body.len()
                        || body[i + b"/Type/Page".len()] != b's')
            }) || body
                .windows(b"/Type /Page".len())
                .enumerate()
                .any(|(i, w)| {
                    w == b"/Type /Page"
                        && (i + b"/Type /Page".len() >= body.len()
                            || body[i + b"/Type /Page".len()] != b's')
                });

            if is_page {
                pages.push(obj_num);
            }
        }
    }

    /// Find object body bytes between `N 0 obj` and `endobj`.
    fn find_object_body_bytes(data: &[u8], obj_num: i32) -> Option<Vec<u8>> {
        let marker = format!("{} 0 obj", obj_num);
        let marker_bytes = marker.as_bytes();
        let mut search_pos = 0;

        while search_pos + marker_bytes.len() < data.len() {
            if let Some(pos) = data[search_pos..]
                .windows(marker_bytes.len())
                .position(|w| w == marker_bytes)
            {
                let abs_pos = search_pos + pos;
                if abs_pos > 0 && data[abs_pos - 1].is_ascii_digit() {
                    search_pos = abs_pos + marker_bytes.len();
                    continue;
                }

                let body_start = abs_pos + marker_bytes.len();
                let endobj_pattern = b"endobj";
                if let Some(end_pos) = data[body_start..]
                    .windows(endobj_pattern.len())
                    .position(|w| w == endobj_pattern)
                {
                    return Some(data[body_start..body_start + end_pos].to_vec());
                }
                search_pos = abs_pos + marker_bytes.len();
            } else {
                break;
            }
        }
        None
    }

    /// Parse /Kids array from object body bytes.
    fn parse_kids_from_bytes(body: &[u8]) -> Vec<i32> {
        let mut kids = Vec::new();
        let kids_pattern = b"/Kids";
        let kids_pos = match body
            .windows(kids_pattern.len())
            .position(|w| w == kids_pattern)
        {
            Some(p) => p,
            None => return kids,
        };

        let after_kids = &body[kids_pos + kids_pattern.len()..];
        let bracket_start = match after_kids.iter().position(|&b| b == b'[') {
            Some(p) => p,
            None => return kids,
        };
        let bracket_end = match after_kids[bracket_start..].iter().position(|&b| b == b']') {
            Some(p) => p,
            None => return kids,
        };

        let array_str =
            String::from_utf8_lossy(&after_kids[bracket_start + 1..bracket_start + bracket_end]);
        let parts: Vec<&str> = array_str.split_whitespace().collect();
        let mut i = 0;
        while i + 2 < parts.len() {
            let r_part = parts[i + 2];
            if r_part == "R" || r_part.starts_with("R/") || r_part.starts_with("R>") {
                if let Ok(obj_num) = parts[i].parse::<i32>() {
                    kids.push(obj_num);
                }
            }
            i += 3;
        }
        kids
    }

    /// Fallback: linear scan for /Type /Page objects (may produce wrong ordering).
    fn find_page_objects_linear_fallback(&mut self, expected_count: usize) {
        let page_type_pattern = b"/Type/Page";
        let page_type_pattern_space = b"/Type /Page";

        let mut found_pages = Vec::new();

        for (i, _) in self.data.windows(page_type_pattern.len()).enumerate() {
            let window = &self.data[i..i + page_type_pattern.len()];
            if window == page_type_pattern {
                if i + page_type_pattern.len() < self.data.len()
                    && self.data[i + page_type_pattern.len()] != b's'
                {
                    if let Some(obj_num) = Self::find_obj_number_before_byte_pos(&self.data, i) {
                        if !found_pages.contains(&obj_num) {
                            found_pages.push(obj_num);
                        }
                    }
                }
            }
        }

        for (i, _) in self.data.windows(page_type_pattern_space.len()).enumerate() {
            let window = &self.data[i..i + page_type_pattern_space.len()];
            if window == page_type_pattern_space {
                if i + page_type_pattern_space.len() < self.data.len()
                    && self.data[i + page_type_pattern_space.len()] != b's'
                {
                    if let Some(obj_num) = Self::find_obj_number_before_byte_pos(&self.data, i) {
                        if !found_pages.contains(&obj_num) {
                            found_pages.push(obj_num);
                        }
                    }
                }
            }
        }

        if !found_pages.is_empty() {
            self.page_objects = found_pages;
        } else if let Some(pages_num) = self.pages {
            for i in 1..=expected_count {
                self.page_objects.push(pages_num + i as i32);
            }
        }
    }

    /// Get page count
    pub fn page_count(&self) -> Result<usize> {
        self.get_page_count()
    }

    fn get_page_count(&self) -> Result<usize> {
        // Search for /Count in binary data to avoid UTF-8 issues
        let count_pattern = b"/Count";
        let pages_type_pattern = b"/Type/Pages";
        let pages_type_pattern_space = b"/Type /Pages";

        // Find /Type/Pages or /Type /Pages position
        let mut pages_pos = None;
        for (i, _) in self.data.windows(pages_type_pattern.len()).enumerate() {
            if &self.data[i..i + pages_type_pattern.len()] == pages_type_pattern {
                pages_pos = Some(i);
                break;
            }
        }

        if pages_pos.is_none() {
            for (i, _) in self
                .data
                .windows(pages_type_pattern_space.len())
                .enumerate()
            {
                if &self.data[i..i + pages_type_pattern_space.len()] == pages_type_pattern_space {
                    pages_pos = Some(i);
                    break;
                }
            }
        }

        // Look for /Count near the Pages object
        if let Some(pos) = pages_pos {
            // Search in a window around the Pages object (both before and after)
            let search_start = pos.saturating_sub(200);
            let search_end = (pos + 500).min(self.data.len());
            let search_region = &self.data[search_start..search_end];

            for (i, _) in search_region.windows(count_pattern.len()).enumerate() {
                if &search_region[i..i + count_pattern.len()] == count_pattern {
                    // Found /Count, extract the number
                    let after_count = &search_region[i + count_pattern.len()..];

                    // Skip whitespace and extract digits
                    let mut start = 0;
                    while start < after_count.len() && after_count[start].is_ascii_whitespace() {
                        start += 1;
                    }

                    let mut end = start;
                    while end < after_count.len() && after_count[end].is_ascii_digit() {
                        end += 1;
                    }

                    if start < end {
                        let num_str = String::from_utf8_lossy(&after_count[start..end]);
                        if let Ok(count) = num_str.parse::<usize>() {
                            return Ok(count);
                        }
                    }
                }
            }
        }

        // Fallback: count the number of /Type /Page (not /Pages) occurrences
        let page_type_pattern = b"/Type /Page";
        let page_type_pattern_no_space = b"/Type/Page";
        let mut count = 0;

        for (i, _) in self.data.windows(page_type_pattern.len()).enumerate() {
            if &self.data[i..i + page_type_pattern.len()] == page_type_pattern {
                // Make sure it's not /Type /Pages
                if i + page_type_pattern.len() < self.data.len()
                    && self.data[i + page_type_pattern.len()] != b's'
                {
                    count += 1;
                }
            }
        }

        for (i, _) in self
            .data
            .windows(page_type_pattern_no_space.len())
            .enumerate()
        {
            if &self.data[i..i + page_type_pattern_no_space.len()] == page_type_pattern_no_space {
                // Make sure it's not /Type/Pages
                if i + page_type_pattern_no_space.len() < self.data.len()
                    && self.data[i + page_type_pattern_no_space.len()] != b's'
                {
                    count += 1;
                }
            }
        }

        if count > 0 {
            return Ok(count);
        }

        // ObjStm fallback: resolve pages object to get /Count
        if let Some(pages_num) = self.pages {
            if let Some(pages_data) = self.resolve_object_data(pages_num) {
                if let Some(count_pos) = pages_data.find("/Count") {
                    let after_count = &pages_data[count_pos + 6..];
                    let trimmed = after_count.trim_start();
                    let num: String = trimmed.chars().take_while(|c| c.is_ascii_digit()).collect();
                    if let Ok(count) = num.parse::<usize>() {
                        if count > 0 {
                            return Ok(count);
                        }
                    }
                }
            }
        }

        // ObjStm fallback: count pages from object streams
        if let Some(objstm_count) =
            super::object_stream::get_page_count_from_object_streams(&self.data)
        {
            if objstm_count > 0 {
                return Ok(objstm_count as usize);
            }
        }

        Ok(1) // Default to 1 page
    }

    /// Get page object number
    pub fn get_page_object(&self, page_num: usize) -> Option<i32> {
        self.page_objects.get(page_num).copied()
    }

    /// Get raw PDF data
    pub fn data(&self) -> &[u8] {
        &self.data
    }

    /// Get xref table
    pub fn xref(&self) -> &XrefTable {
        &self.xref
    }

    /// Resolve an object by number, handling both direct and compressed (ObjStm) objects.
    /// Returns the object data as a string.
    pub fn resolve_object_data(&self, obj_num: i32) -> Option<String> {
        let entry = self.xref.get(obj_num)?;

        if entry.is_in_use() {
            Self::find_object_body_bytes(&self.data, obj_num)
                .map(|bytes| String::from_utf8_lossy(&bytes).into_owned())
        } else if entry.is_compressed() {
            let stm_num = entry.offset as i32;
            self.extract_from_object_stream(stm_num, obj_num)
        } else {
            None
        }
    }

    /// Extract an object from an object stream
    fn extract_from_object_stream(&self, stm_num: i32, obj_num: i32) -> Option<String> {
        let parser = ObjectStreamParser::new(&self.data, stm_num).ok()?;
        parser.get_object(obj_num).cloned()
    }
}
