//! Print Production Tools - Page boxes, N-Up layouts, booklets, validation
//!
//! Professional print production features for pre-press workflows:
//! - Page box management (MediaBox, CropBox, BleedBox, TrimBox, ArtBox)
//! - N-Up and grid layouts
//! - Booklet creation and imposition
//! - Poster/tiling for large format
//! - PDF validation and repair

use super::error::{EnhancedError, Result};
use super::overlay::{
    find_catalog_obj_num_from_bytes, find_max_object_number,
    rebuild_xref_and_trailer as overlay_rebuild_xref,
};
use super::page_copy::copy_page_complete;
use super::page_merge::{combine_page_pdfs_bytes, count_pages};
use super::page_ops::flatten_object_streams_to_vec;
use super::writer::PdfWriter;
use std::fs;
use std::path::Path;

fn box_type_to_name(box_type: PageBox) -> &'static str {
    match box_type {
        PageBox::Media => "/MediaBox",
        PageBox::Crop => "/CropBox",
        PageBox::Bleed => "/BleedBox",
        PageBox::Trim => "/TrimBox",
        PageBox::Art => "/ArtBox",
    }
}

fn replace_or_add_page_box(
    data: &mut Vec<u8>,
    page_obj_num: i32,
    box_name: &str,
    new_box_str: &str,
) -> Result<()> {
    let obj_pattern = format!("{} 0 obj", page_obj_num);
    let obj_bytes = obj_pattern.as_bytes();
    let Some(obj_pos) = data.windows(obj_bytes.len()).position(|w| w == obj_bytes) else {
        return Err(EnhancedError::Generic(format!(
            "Page object {} not found",
            page_obj_num
        )));
    };
    let page_start = obj_pos;
    let endobj = b"endobj";
    let Some(end_pos) = data[page_start..]
        .windows(endobj.len())
        .position(|w| w == endobj)
    else {
        return Err(EnhancedError::Generic("Page object end not found".into()));
    };
    let page_end = page_start + end_pos;
    let page_region = &mut data[page_start..page_end];

    let box_bytes = box_name.as_bytes();
    let array_part = new_box_str
        .strip_prefix(box_name)
        .map(|s| s.trim_start())
        .unwrap_or(new_box_str);
    let new_entry = format!("/{} {}", box_name, array_part);
    let new_entry_bytes = new_entry.as_bytes();

    if let Some(box_pos) = page_region
        .windows(box_bytes.len())
        .position(|w| w == box_bytes)
    {
        let value_start = box_pos + box_bytes.len();
        let mut i = value_start;
        while i < page_region.len() && page_region[i].is_ascii_whitespace() {
            i += 1;
        }
        if i < page_region.len() && page_region[i] == b'[' {
            let mut depth = 1;
            i += 1;
            while i < page_region.len() && depth > 0 {
                if page_region[i] == b'[' {
                    depth += 1;
                } else if page_region[i] == b']' {
                    depth -= 1;
                }
                i += 1;
            }
            let old_len = i - value_start;
            let new_value = format!(" {}", array_part);
            let new_value_bytes = new_value.as_bytes();
            if new_value_bytes.len() <= old_len {
                for (j, &b) in new_value_bytes.iter().enumerate() {
                    page_region[value_start + j] = b;
                }
                for j in new_value_bytes.len()..old_len {
                    page_region[value_start + j] = b' ';
                }
            } else {
                let mut new_page =
                    Vec::with_capacity(page_region.len() + new_value_bytes.len() - old_len);
                new_page.extend_from_slice(&page_region[..value_start]);
                new_page.extend_from_slice(new_value_bytes);
                new_page.extend_from_slice(&page_region[i..]);
                data.splice(page_start..page_end, new_page);
            }
        }
    } else {
        let insert_pos = page_region
            .iter()
            .rposition(|&b| b == b'>')
            .unwrap_or(page_region.len().saturating_sub(1));
        let insert_str = format!(" {} ", new_entry);
        let insert_bytes = insert_str.as_bytes();
        let mut new_page = Vec::with_capacity(page_region.len() + insert_bytes.len());
        new_page.extend_from_slice(&page_region[..=insert_pos]);
        new_page.extend_from_slice(insert_bytes);
        new_page.extend_from_slice(&page_region[insert_pos + 1..]);
        data.splice(page_start..page_end, new_page);
    }
    Ok(())
}

/// PDF page box types
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PageBox {
    /// MediaBox - physical page size
    Media,
    /// CropBox - visible page area
    Crop,
    /// BleedBox - area including bleed
    Bleed,
    /// TrimBox - final trimmed size
    Trim,
    /// ArtBox - meaningful content area
    Art,
}

/// Box dimensions
#[derive(Debug, Clone, Copy)]
pub struct BoxDimensions {
    pub x: f32,
    pub y: f32,
    pub width: f32,
    pub height: f32,
}

/// Page ordering for N-Up layouts
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PageOrder {
    /// Left to right, top to bottom
    LeftToRightTopToBottom,
    /// Right to left, top to bottom
    RightToLeftTopToBottom,
    /// Top to bottom, left to right
    TopToBottomLeftToRight,
    /// Down and over
    DownAndOver,
}

/// N-Up layout options
#[derive(Debug, Clone)]
pub struct NUpOptions {
    /// Grid dimensions (columns, rows)
    pub grid: (u32, u32),
    /// Output page width
    pub page_width: f32,
    /// Output page height
    pub page_height: f32,
    /// Outer margins
    pub margin: f32,
    /// Space between pages
    pub spacing: f32,
    /// Border around each page
    pub border_width: f32,
    /// Page ordering
    pub order: PageOrder,
}

impl Default for NUpOptions {
    fn default() -> Self {
        Self {
            grid: (2, 2),
            page_width: 612.0,  // Letter width
            page_height: 792.0, // Letter height
            margin: 36.0,
            spacing: 10.0,
            border_width: 0.0,
            order: PageOrder::LeftToRightTopToBottom,
        }
    }
}

/// Booklet binding type
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BookletType {
    /// Saddle-stitch (fold and staple)
    SaddleStitch,
    /// Perfect bound (glued spine)
    PerfectBound,
    /// Spiral bound (holes for spiral)
    SpiralBound,
    /// Signature-based (traditional book printing)
    Signature(u32), // pages per signature
}

/// Booklet creation options
#[derive(Debug, Clone)]
pub struct BookletOptions {
    /// Binding type
    pub binding_type: BookletType,
    /// Output page size
    pub page_width: f32,
    pub page_height: f32,
    /// Binding margin (inner margin)
    pub margin: f32,
    /// Extra gutter space
    pub gutter: f32,
    /// Add blank pages to make multiple of 4
    pub add_blank_pages: bool,
    /// Double-sided printing
    pub double_sided: bool,
}

impl Default for BookletOptions {
    fn default() -> Self {
        Self {
            binding_type: BookletType::SaddleStitch,
            page_width: 612.0,
            page_height: 792.0,
            margin: 36.0,
            gutter: 18.0,
            add_blank_pages: true,
            double_sided: true,
        }
    }
}

/// Poster/tiling options
#[derive(Debug, Clone)]
pub struct PosterOptions {
    /// Number of tiles (columns, rows)
    pub tiles: (u32, u32),
    /// Tile page size
    pub tile_width: f32,
    pub tile_height: f32,
    /// Overlap for assembly (points)
    pub overlap: f32,
    /// Add cut marks
    pub cut_marks: bool,
    /// Add tile numbers
    pub tile_numbers: bool,
    /// Generate assembly guide
    pub assembly_guide: bool,
}

impl Default for PosterOptions {
    fn default() -> Self {
        Self {
            tiles: (3, 3),
            tile_width: 612.0,
            tile_height: 792.0,
            overlap: 20.0,
            cut_marks: true,
            tile_numbers: true,
            assembly_guide: true,
        }
    }
}

/// Validation mode
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValidationMode {
    /// Relaxed (allow common violations)
    Relaxed,
    /// Standard (ISO 32000-1 compliance)
    Standard,
    /// Strict interpretation
    Strict,
    /// PDF/A validation
    PdfA,
}

/// Validation result
#[derive(Debug, Clone)]
pub struct ValidationResult {
    pub valid: bool,
    pub errors: Vec<ValidationError>,
    pub warnings: Vec<ValidationWarning>,
}

/// Validation error
#[derive(Debug, Clone)]
pub struct ValidationError {
    pub code: String,
    pub message: String,
    pub location: String,
    pub severity: Severity,
}

/// Validation warning
#[derive(Debug, Clone)]
pub struct ValidationWarning {
    pub code: String,
    pub message: String,
    pub location: String,
}

/// Error severity
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity {
    Critical,
    Error,
    Warning,
    Info,
}

/// Get page box dimensions
pub fn get_page_box(pdf_path: &str, page: u32, box_type: PageBox) -> Result<BoxDimensions> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

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

    let page_obj_num = find_page_object(&data, page as usize)?;
    let content = String::from_utf8_lossy(&data);
    let obj_header = format!("{} 0 obj", page_obj_num);

    if let Some(obj_pos) = content.find(&obj_header) {
        let after_obj = &content[obj_pos..];
        let end_obj = after_obj.find("endobj").unwrap_or(after_obj.len());
        let obj_content = &after_obj[..end_obj];

        let box_name = box_type_to_name(box_type);
        let box_name_trimmed = box_name.trim_start_matches('/');

        if let Some(dims) = parse_box_from_object(obj_content, box_name_trimmed) {
            return Ok(dims);
        }

        if box_type != PageBox::Media {
            if let Some(dims) = parse_box_from_object(obj_content, "MediaBox") {
                return Ok(dims);
            }
        }
    }

    Ok(BoxDimensions {
        x: 0.0,
        y: 0.0,
        width: 612.0,
        height: 792.0,
    })
}

fn parse_box_from_object(obj_content: &str, box_name: &str) -> Option<BoxDimensions> {
    let pattern = format!("/{}", box_name);
    let pos = obj_content.find(&pattern)?;
    let after_name = &obj_content[pos + pattern.len()..];
    let bracket_start = after_name.find('[')?;
    let bracket_end = after_name.find(']')?;
    let nums_str = &after_name[bracket_start + 1..bracket_end];
    let nums: Vec<f32> = nums_str
        .split_whitespace()
        .filter_map(|s| s.parse::<f32>().ok())
        .collect();
    if nums.len() >= 4 {
        Some(BoxDimensions {
            x: nums[0],
            y: nums[1],
            width: nums[2] - nums[0],
            height: nums[3] - nums[1],
        })
    } else {
        None
    }
}

/// Set page box dimensions
pub fn set_page_box(
    pdf_path: &str,
    page: u32,
    box_type: PageBox,
    dimensions: BoxDimensions,
) -> Result<()> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

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

    if super::page_ops::has_object_streams(&data) {
        data = flatten_object_streams_to_vec(&data)?;
    }

    let page_obj_num = find_page_object(&data, page as usize)?;
    let box_name = box_type_to_name(box_type);
    let new_box_str = format!(
        "{} [{:.2} {:.2} {:.2} {:.2}]",
        box_name,
        dimensions.x,
        dimensions.y,
        dimensions.x + dimensions.width,
        dimensions.y + dimensions.height
    );

    replace_or_add_page_box(&mut data, page_obj_num, box_name, &new_box_str)?;
    let catalog_obj_num = find_catalog_obj_num_from_bytes(&data);
    let obj_count = find_max_object_number(&data) + 1;
    overlay_rebuild_xref(&mut data, obj_count, catalog_obj_num)?;
    fs::write(pdf_path, &data)?;

    Ok(())
}

/// Add bleed to page
pub fn add_bleed(pdf_path: &str, page: u32, bleed: f32) -> Result<()> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

    let trim = get_page_box(pdf_path, page, PageBox::Trim)?;
    let bleed_dims = BoxDimensions {
        x: trim.x - bleed,
        y: trim.y - bleed,
        width: trim.width + 2.0 * bleed,
        height: trim.height + 2.0 * bleed,
    };
    set_page_box(pdf_path, page, PageBox::Bleed, bleed_dims)
}

/// Create N-Up layout (2x2: 4 input pages per 1 output page)
pub fn create_nup(pdf_path: &str, output_path: &str, options: &NUpOptions) -> Result<()> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

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

    let effective_path = if super::page_ops::has_object_streams(&data) {
        let flat_data = flatten_object_streams_to_vec(&data)?;
        let flat_path = format!(
            "{}/micropdf_nup_flat_{}_{}.pdf",
            std::env::temp_dir().to_string_lossy(),
            std::process::id(),
            std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_nanos()
        );
        fs::write(&flat_path, &flat_data)?;
        flat_path
    } else {
        pdf_path.to_string()
    };

    let effective_data = fs::read(&effective_path)?;
    let page_count = count_pages(&effective_data)?;

    let (cols, rows) = options.grid;
    let cells_per_sheet = (cols * rows).max(1) as usize;

    let page_w = options.page_width;
    let page_h = options.page_height;
    let margin = options.margin;
    let cell_w = (page_w - 2.0 * margin - (cols as f32 - 1.0) * options.spacing) / cols as f32;
    let cell_h = (page_h - 2.0 * margin - (rows as f32 - 1.0) * options.spacing) / rows as f32;

    let mut writer = PdfWriter::new();
    let temp_dir = std::env::temp_dir().join(format!("micropdf_nup_{}", std::process::id()));
    let temp_dir = temp_dir.to_string_lossy().to_string();
    fs::create_dir_all(&temp_dir)?;

    let result = (|| -> Result<()> {
        for sheet_start in (0..page_count).step_by(cells_per_sheet) {
            let mut sheet_content = String::new();
            let mut cell_idx = 0usize;

            for i in 0..cells_per_sheet {
                let page_idx = sheet_start + i as usize;
                if page_idx >= page_count {
                    break;
                }
                let temp_path = format!("{}/page_{}.pdf", temp_dir, page_idx);
                copy_page_complete(&effective_path, page_idx, &temp_path)?;
                let page_data = fs::read(&temp_path)?;
                let _ = fs::remove_file(&temp_path);

                let content = extract_page_content_for_nup(&page_data)?;
                let row = cell_idx / cols as usize;
                let col = cell_idx % cols as usize;
                let x = margin + col as f32 * (cell_w + options.spacing);
                let y =
                    page_h - margin - (row as f32 + 1.0) * cell_h - row as f32 * options.spacing;

                let sx = cell_w / 612.0f32.min(1.0);
                let sy = cell_h / 792.0f32.min(1.0);
                sheet_content
                    .push_str(&format!("q {:.4} 0 0 {:.4} {:.2} {:.2} cm\n", sx, sy, x, y));
                sheet_content.push_str(&content);
                sheet_content.push_str("Q\n");
                cell_idx += 1;
            }

            if !sheet_content.is_empty() {
                writer.add_page_with_content(page_w, page_h, &sheet_content)?;
            }
        }

        if writer.page_count() == 0 {
            return Err(EnhancedError::InvalidParameter(
                "No pages to process".into(),
            ));
        }

        writer.save(output_path)?;
        Ok(())
    })();

    let _ = fs::remove_dir_all(&temp_dir);
    if effective_path != pdf_path {
        let _ = fs::remove_file(&effective_path);
    }

    result
}

/// Create booklet using saddle-stitch imposition
pub fn create_booklet(pdf_path: &str, output_path: &str, options: &BookletOptions) -> Result<()> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

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

    let effective_path = if super::page_ops::has_object_streams(&data) {
        let flat_data = flatten_object_streams_to_vec(&data)?;
        let flat_path = format!(
            "{}/micropdf_booklet_flat_{}.pdf",
            std::env::temp_dir().to_string_lossy(),
            std::process::id()
        );
        fs::write(&flat_path, &flat_data)?;
        flat_path
    } else {
        pdf_path.to_string()
    };

    let effective_data = fs::read(&effective_path)?;
    let mut page_count = count_pages(&effective_data)?;

    if options.add_blank_pages && page_count % 4 != 0 {
        page_count = ((page_count + 3) / 4) * 4;
    }

    let sheet_count = page_count / 2;
    let mut imposition_order: Vec<usize> = Vec::new();
    for sheet in 0..sheet_count {
        let front_left = page_count - 1 - sheet;
        let front_right = sheet;
        imposition_order.push(front_left);
        imposition_order.push(front_right);
    }

    let reordered_path = format!(
        "{}/micropdf_booklet_reorder_{}.pdf",
        std::env::temp_dir().to_string_lossy(),
        std::process::id()
    );
    super::page_ops::reorder_pages(&effective_path, &imposition_order, &reordered_path)?;

    let nup_options = NUpOptions {
        grid: (2, 1),
        page_width: options.page_width,
        page_height: options.page_height,
        margin: options.margin,
        spacing: options.gutter,
        border_width: 0.0,
        order: PageOrder::LeftToRightTopToBottom,
    };

    let nup_result = create_nup(&reordered_path, output_path, &nup_options);
    let _ = fs::remove_file(&reordered_path);
    nup_result?;

    if effective_path != pdf_path {
        let _ = fs::remove_file(&effective_path);
    }

    Ok(())
}

/// Create poster/tiling by splitting a page into tiles
pub fn create_poster(pdf_path: &str, output_path: &str, options: &PosterOptions) -> Result<()> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

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

    let effective_path = if super::page_ops::has_object_streams(&data) {
        let flat_data = flatten_object_streams_to_vec(&data)?;
        let flat_path = format!(
            "{}/micropdf_poster_flat_{}.pdf",
            std::env::temp_dir().to_string_lossy(),
            std::process::id()
        );
        fs::write(&flat_path, &flat_data)?;
        flat_path
    } else {
        pdf_path.to_string()
    };

    let effective_data = fs::read(&effective_path)?;
    let page_content = extract_page_content_for_nup(&effective_data)?;

    let media = get_page_box(pdf_path, 0, PageBox::Media)?;
    let (cols, rows) = options.tiles;
    let tile_w = options.tile_width;
    let tile_h = options.tile_height;

    let src_tile_w = media.width / cols as f32;
    let src_tile_h = media.height / rows as f32;
    let overlap = options.overlap;

    let mut writer = PdfWriter::new();

    for row in 0..rows {
        for col in 0..cols {
            let src_x = col as f32 * src_tile_w - overlap;
            let src_y = (rows - 1 - row) as f32 * src_tile_h - overlap;
            let src_clip_w = src_tile_w + 2.0 * overlap;
            let src_clip_h = src_tile_h + 2.0 * overlap;

            let sx = tile_w / src_clip_w;
            let sy = tile_h / src_clip_h;
            let tx = -src_x * sx;
            let ty = -src_y * sy;

            let mut tile_content = String::new();
            tile_content.push_str("q\n");
            tile_content.push_str(&format!("0 0 {:.2} {:.2} re W n\n", tile_w, tile_h));
            tile_content.push_str(&format!("{:.4} 0 0 {:.4} {:.2} {:.2} cm\n", sx, sy, tx, ty));
            tile_content.push_str(&page_content);
            tile_content.push_str("Q\n");

            if options.cut_marks {
                let mark_len = 18.0;
                tile_content.push_str("q 0.5 w 0 0 0 RG\n");
                for &(mx, my) in &[
                    (0.0f32, 0.0f32),
                    (tile_w, 0.0),
                    (0.0, tile_h),
                    (tile_w, tile_h),
                ] {
                    tile_content.push_str(&format!(
                        "{:.2} {:.2} m {:.2} {:.2} l S\n",
                        mx - mark_len,
                        my,
                        mx + mark_len,
                        my
                    ));
                    tile_content.push_str(&format!(
                        "{:.2} {:.2} m {:.2} {:.2} l S\n",
                        mx,
                        my - mark_len,
                        mx,
                        my + mark_len
                    ));
                }
                tile_content.push_str("Q\n");
            }

            if options.tile_numbers {
                tile_content.push_str(&format!(
                    "BT /F1 10 Tf 10 10 Td ({},{}) Tj ET\n",
                    col + 1,
                    row + 1
                ));
            }

            writer.add_page_with_content(tile_w, tile_h, &tile_content)?;
        }
    }

    writer.save(output_path)?;

    if effective_path != pdf_path {
        let _ = fs::remove_file(&effective_path);
    }

    Ok(())
}

/// Validate PDF structure
pub fn validate_pdf(pdf_path: &str, mode: ValidationMode) -> Result<ValidationResult> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

    let data = fs::read(pdf_path)?;
    let mut errors = Vec::new();
    let mut warnings = Vec::new();

    if !data.starts_with(b"%PDF-") {
        errors.push(ValidationError {
            code: "HEADER".to_string(),
            message: "Missing or invalid PDF header (%PDF-x.y)".to_string(),
            location: "start of file".to_string(),
            severity: Severity::Critical,
        });
    }

    if !data.ends_with(b"%%EOF\n") && !data.ends_with(b"%%EOF\r\n") && !data.ends_with(b"%%EOF") {
        warnings.push(ValidationWarning {
            code: "EOF".to_string(),
            message: "Missing %%EOF marker".to_string(),
            location: "end of file".to_string(),
        });
    }

    let content = String::from_utf8_lossy(&data);
    if !content.contains("xref") && !content.contains("/Type") {
        errors.push(ValidationError {
            code: "XREF".to_string(),
            message: "No xref table found".to_string(),
            location: "file".to_string(),
            severity: Severity::Critical,
        });
    }

    if !content.contains("trailer") {
        errors.push(ValidationError {
            code: "TRAILER".to_string(),
            message: "No trailer found".to_string(),
            location: "file".to_string(),
            severity: Severity::Error,
        });
    }

    if content.contains("trailer") && !content.contains("/Root") {
        warnings.push(ValidationWarning {
            code: "ROOT".to_string(),
            message: "Trailer may lack /Root reference".to_string(),
            location: "trailer".to_string(),
        });
    }

    if !content.contains("/Type /Catalog") && !content.contains("/Type/Catalog") {
        errors.push(ValidationError {
            code: "CATALOG".to_string(),
            message: "Catalog object not found".to_string(),
            location: "document".to_string(),
            severity: Severity::Critical,
        });
    }

    if !content.contains("/Type /Pages") && !content.contains("/Type/Pages") {
        errors.push(ValidationError {
            code: "PAGES".to_string(),
            message: "Pages tree not found".to_string(),
            location: "document".to_string(),
            severity: Severity::Critical,
        });
    }

    let valid = errors.is_empty() || !errors.iter().any(|e| e.severity == Severity::Critical);

    Ok(ValidationResult {
        valid,
        errors,
        warnings,
    })
}

/// Repair PDF structure
pub fn repair_pdf(pdf_path: &str, output_path: &str) -> Result<()> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

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

    remove_xref_trailer(&mut data);
    repair_xref_and_trailer(&mut data)?;
    fs::write(output_path, &data)?;

    Ok(())
}

fn find_page_object(data: &[u8], page_num: usize) -> Result<i32> {
    let type_page = b"/Type /Page";
    let type_pages = b"/Type /Pages";

    let mut page_objects: Vec<i32> = Vec::new();
    let mut search_pos = 0;

    while search_pos < data.len() {
        if let Some(pos) = data[search_pos..]
            .windows(type_page.len())
            .position(|w| w == type_page)
        {
            let abs_pos = search_pos + pos;
            if abs_pos + type_pages.len() <= data.len() && data[abs_pos..].starts_with(type_pages) {
                search_pos = abs_pos + type_pages.len();
                continue;
            }
            if let Some(obj_num) = find_obj_num_backwards(&data[..abs_pos]) {
                page_objects.push(obj_num);
            }
            search_pos = abs_pos + type_page.len();
        } else {
            break;
        }
    }

    page_objects
        .get(page_num)
        .copied()
        .ok_or_else(|| EnhancedError::InvalidParameter(format!("Page {} not found", page_num)))
}

fn find_obj_num_backwards(data: &[u8]) -> Option<i32> {
    let marker = b" 0 obj";
    let start = data.len().saturating_sub(300);
    let region = &data[start..];
    let pos = region.windows(marker.len()).rposition(|w| w == marker)?;
    let before = &region[..pos];
    let num_str: String = before
        .iter()
        .rev()
        .take_while(|b| b.is_ascii_digit())
        .collect::<Vec<_>>()
        .into_iter()
        .rev()
        .map(|&b| b as char)
        .collect();
    num_str.parse().ok()
}

fn get_page_object_body(data: &[u8], obj_num: i32) -> Option<Vec<u8>> {
    let marker = format!("{} 0 obj", obj_num);
    let marker_bytes = marker.as_bytes();
    let pos = data
        .windows(marker_bytes.len())
        .position(|w| w == marker_bytes)?;
    let body_start = pos + marker_bytes.len();
    let after = &data[body_start..];
    let end_pos = after.windows(6).position(|w| w == b"endobj")?;
    Some(after[..end_pos].to_vec())
}

fn extract_box_from_body(body: &[u8], box_name: &str) -> Option<BoxDimensions> {
    let s = String::from_utf8_lossy(body);
    let pos = s.find(box_name)?;
    let after = &s[pos + box_name.len()..];
    let bracket_start = after.find('[')?;
    let bracket_end = after[bracket_start..].find(']')?;
    let coords: Vec<f32> = after[bracket_start + 1..bracket_start + bracket_end]
        .split_whitespace()
        .filter_map(|x| x.parse().ok())
        .collect();
    if coords.len() >= 4 {
        Some(BoxDimensions {
            x: coords[0],
            y: coords[1],
            width: coords[2] - coords[0],
            height: coords[3] - coords[1],
        })
    } else {
        None
    }
}

fn remove_xref_trailer(data: &mut Vec<u8>) {
    let xref_pos = data
        .windows(4)
        .rposition(|w| w == b"\nxref" || w == b"xref")
        .or_else(|| data.windows(5).rposition(|w| w == b"\r\nxref"));
    if let Some(pos) = xref_pos {
        let truncate_at = if data[pos] == b'\n' || data[pos] == b'\r' {
            pos
        } else {
            pos
        };
        data.truncate(truncate_at);
    }
}

fn repair_xref_and_trailer(data: &mut Vec<u8>) -> Result<()> {
    use std::collections::HashMap;

    let mut offsets: HashMap<i32, usize> = HashMap::new();
    let mut search_pos = 0;

    while search_pos < data.len() {
        if (search_pos == 0 || data[search_pos.saturating_sub(1)] == b'\n')
            && data[search_pos].is_ascii_digit()
        {
            let mut end = search_pos;
            while end < data.len() && data[end].is_ascii_digit() {
                end += 1;
            }
            if end + 6 <= data.len() && &data[end..end + 6] == b" 0 obj" {
                if let Ok(num) = std::str::from_utf8(&data[search_pos..end])
                    .unwrap_or("0")
                    .parse::<i32>()
                {
                    if num > 0 && num < 1_000_000 {
                        offsets.insert(num, search_pos);
                    }
                }
                search_pos = end + 6;
                continue;
            }
        }
        search_pos += 1;
    }

    let max_obj = offsets.keys().copied().max().unwrap_or(1);
    let size = max_obj + 1;

    let xref_offset = data.len();
    data.extend_from_slice(format!("xref\n0 {}\n", size).as_bytes());
    data.extend_from_slice(b"0000000000 65535 f \n");
    for i in 1..size {
        if let Some(&off) = offsets.get(&i) {
            data.extend_from_slice(format!("{:010} 00000 n \n", off).as_bytes());
        } else {
            data.extend_from_slice(b"0000000000 00000 f \n");
        }
    }

    let root = offsets
        .keys()
        .copied()
        .find(|&n| {
            let marker = format!("{} 0 obj", n);
            let start = offsets.get(&n).copied().unwrap_or(0);
            let end = (start + 500).min(data.len());
            let region = String::from_utf8_lossy(&data[start..end]);
            region.contains("/Type /Catalog") || region.contains("/Type/Catalog")
        })
        .unwrap_or(1);

    data.extend_from_slice(
        format!(
            "trailer\n<</Size {}/Root {} 0 R>>\nstartxref\n{}\n%%EOF\n",
            size, root, xref_offset
        )
        .as_bytes(),
    );

    Ok(())
}

fn extract_page_content_for_nup(page_data: &[u8]) -> Result<String> {
    let page_content = super::content_stream::extract_page_content(page_data, 3)?;
    let combined = page_content.combined_content();
    String::from_utf8(combined)
        .map_err(|_| EnhancedError::Generic("Invalid UTF-8 in content".into()))
}

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

    #[test]
    fn test_nup_options_default() {
        let options = NUpOptions::default();
        assert_eq!(options.grid, (2, 2));
        assert_eq!(options.order, PageOrder::LeftToRightTopToBottom);
    }

    #[test]
    fn test_booklet_options_default() {
        let options = BookletOptions::default();
        assert_eq!(options.binding_type, BookletType::SaddleStitch);
        assert!(options.add_blank_pages);
        assert!(options.double_sided);
    }

    #[test]
    fn test_poster_options_default() {
        let options = PosterOptions::default();
        assert_eq!(options.tiles, (3, 3));
        assert!(options.cut_marks);
        assert!(options.tile_numbers);
    }
}
