#[cfg(feature = "bench")]
pub mod benchmark_fixtures;
#[cfg(feature = "bench")]
pub mod benchmark_report;
pub mod color;
pub mod error;
pub mod font;
pub mod font_subset;
pub mod image;
pub(crate) mod layout;
pub(crate) mod measure;
pub mod model;
pub(crate) mod normalize;
pub mod pdf_stream;
pub mod pdf_writer;
pub mod qr;
pub(crate) mod render;
pub mod style;
pub mod svg;
pub mod text;
pub mod vector;
pub mod prelude;

pub use error::{PdfMakeError, Result};
pub use font::{build_to_unicode_cmap, FontRegistry, GlyphMapping, LoadedFont};
pub use model::{AbsoluteNode, Alignment, Attachment, CanvasNode, CanvasPoint, CanvasShape, ColumnsNode, CompressionLevel, DecorationStyle, Document, DocumentInfo, DocumentNode, Fit, FontSource, HeaderFooter, ImageCover, ImageNode, ImageSource, InlineFragment, ListNode, Margins, NamedDest, OrderedMarkerType, OutlineEntry, PageBreakCallback, PageBreakInfo, PageBreakMode, PageSize, PdfEncryption, QrNode, RenderOptions, SectionNode, Style, SvgNode, TableCell, TableCellContent, TableLayoutConfig, TableNode, TableStyle, TableWidth, TextDecoration, TextNode, TocNode, UnorderedMarkerType, VerticalAlignment, WatermarkConfig, WatermarkFontSize};
pub use normalize::{normalize_document, NormalizedAbsolute, NormalizedBlock, NormalizedCanvas, NormalizedCellContent, NormalizedColumns, NormalizedDocument, NormalizedImage, NormalizedList, NormalizedQr, NormalizedSection, NormalizedSvg, NormalizedTable, NormalizedTableCell, NormalizedTextBlock, NormalizedToc, TocEntry};
pub use measure::{measure_document, ListItemMarker, MeasuredAbsolute, MeasuredBlock, MeasuredCanvas, MeasuredColumns, MeasuredDocument, MeasuredImage, MeasuredList, MeasuredQr, MeasuredSection, MeasuredSvg, MeasuredTable, MeasuredTableCell, MeasuredTableRow, MeasuredTextBlock, MeasuredToc, MeasuredTocEntry};
pub use layout::{inject_header_footer_with_custom_properties, layout_document, BulletShape, LayoutCanvas, LayoutColumns, LayoutDocument, LayoutHeaderFooter, LayoutImage, LayoutItem, LayoutLine, LayoutList, LayoutListMarker, LayoutPage, LayoutQr, LayoutRun, LayoutSvg, LayoutTable, LayoutTableCell, LayoutTableRow, LayoutTextItem, LayoutToc, LayoutTocEntry, LayoutWatermark, PageCustomProperties};
pub use style::ResolvedStyle;
pub use text::{InlineLine, InlineRun, NormalizedInline, TextMeasurer, measure_helvetica, wrap_inlines};
pub use image::{ImageFormat, LoadedImage};
pub use qr::{DarkModule, QrMatrix};
pub use svg::ParsedSvg;
pub use vector::{FillRule, GradientStop, PathCommand, VectorShape};

pub struct PdfMake;

impl PdfMake {
    pub fn new() -> Self {
        Self
    }

    pub fn render(&self, document: &Document) -> Result<Vec<u8>> {
        self.render_with_options(document, &RenderOptions::default())
    }

    pub fn render_with_options(&self, document: &Document, options: &RenderOptions) -> Result<Vec<u8>> {
        if document.content.is_empty() {
            return Err(PdfMakeError::EmptyDocument);
        }

        let fonts = FontRegistry::load(&document.fonts)?;
        let normalized = normalize::normalize_document(document)?;
        let measured = measure::measure_document(&normalized, &fonts)?;

        let default_font_family = document.default_style.font_family.as_deref();

        #[allow(unused_assignments)]
        let mut total_pages: usize = 0;

        // Pre-extract origin_node_idx for each block so closures don't need to capture measured
        let block_origin_indices: Vec<Option<usize>> = measured.blocks.iter().map(|b| match b {
            measure::MeasuredBlock::Text(t) => t.origin_node_idx,
            measure::MeasuredBlock::Table(t) => t.origin_node_idx,
            measure::MeasuredBlock::Image(i) => i.origin_node_idx,
            measure::MeasuredBlock::Qr(q) => q.origin_node_idx,
            measure::MeasuredBlock::Svg(s) => s.origin_node_idx,
            measure::MeasuredBlock::Columns(c) => c.origin_node_idx,
            measure::MeasuredBlock::Absolute(a) => a.origin_node_idx,
            measure::MeasuredBlock::List(l) => l.origin_node_idx,
            measure::MeasuredBlock::Section(s) => s.origin_node_idx,
            measure::MeasuredBlock::Canvas(_) => None,
            measure::MeasuredBlock::Toc(_) => None,
        }).collect();

        let mut layout = if let Some(ref pbb) = document.page_break_before {
            let cb = &pbb.0;
            let origins = &block_origin_indices;
            let doc_content = &document.content;
            let measured_blocks = &measured.blocks;
            layout::layout_document_with_callback(&measured, Some(&|block_idx: usize, current_page: usize| -> bool {
                let origin_idx = origins.get(block_idx).and_then(|o| *o);
                let (node_text, is_stack) = if let Some(idx) = origin_idx {
                    match doc_content.get(idx) {
                        Some(DocumentNode::Text(t)) => {
                            let text: String = t.inlines.iter().map(|f| f.text.as_str()).collect();
                            (Some(text), false)
                        }
                        Some(DocumentNode::Stack(_)) => {
                            let text = measured_blocks.get(block_idx).and_then(|b| {
                                match b {
                                    measure::MeasuredBlock::Text(t) => {
                                        t.lines.first().map(|l| l.runs.iter().map(|r| r.text.as_str()).collect::<String>())
                                    }
                                    _ => None,
                                }
                            });
                            (text, true)
                        }
                        _ => (None, false),
                    }
                } else {
                    (None, false)
                };
                let info = model::PageBreakInfo {
                    page_number: current_page,
                    total_pages: 1,
                    node_text,
                    is_stack,
                };
                let result = cb(&info);
                result
            }), &fonts, default_font_family)?
        } else {
            layout::layout_document_with_fonts(&measured, &fonts, default_font_family)?
        };

        // Enforce max_pages limit
        if let Some(max) = document.max_pages {
            layout.pages.truncate(max);
        }

        // Substitute TOC page numbers
        substitute_toc_page_numbers(&mut layout, &normalized.toc_items);

        // Substitute cross-reference page numbers
        substitute_page_references(&mut layout);

        // Re-layout with correct total_pages if callback was used
        if document.page_break_before.is_some() {
            total_pages = layout.pages.len();
            let pbb = document.page_break_before.as_ref().unwrap();
            let cb = &pbb.0;
            let origins = &block_origin_indices;
            let doc_content = &document.content;
            let measured_blocks = &measured.blocks;
            layout = layout::layout_document_with_callback(&measured, Some(&|block_idx: usize, current_page: usize| -> bool {
                let origin_idx = origins.get(block_idx).and_then(|o| *o);
                let (node_text, is_stack) = if let Some(idx) = origin_idx {
                    match doc_content.get(idx) {
                        Some(DocumentNode::Text(t)) => {
                            let text: String = t.inlines.iter().map(|f| f.text.as_str()).collect();
                            (Some(text), false)
                        }
                        Some(DocumentNode::Stack(_)) => {
                            // Extract text from the first line of the measured block
                            let text = measured_blocks.get(block_idx).and_then(|b| {
                                match b {
                                    measure::MeasuredBlock::Text(t) => {
                                        t.lines.first().map(|l| l.runs.iter().map(|r| r.text.as_str()).collect::<String>())
                                    }
                                    _ => None,
                                }
                            });
                            (text, true)
                        }
                        _ => (None, false),
                    }
                } else {
                    (None, false)
                };
                let info = model::PageBreakInfo {
                    page_number: current_page,
                    total_pages,
                    node_text,
                    is_stack,
                };
                cb(&info)
            }), &fonts, default_font_family)?;
            if let Some(max) = document.max_pages {
                layout.pages.truncate(max);
            }
            // Re-apply substitutions after re-layout
            substitute_toc_page_numbers(&mut layout, &normalized.toc_items);
            substitute_page_references(&mut layout);
        }

        if let Some(ref bg_node) = document.background {
            let bg_items = self.layout_background(bg_node, document, &fonts)?;
            if !bg_items.is_empty() {
                for page in layout.pages.iter_mut() {
                    page.background = Some(bg_items.clone());
                }
            }
        }

        let has_header_footer = document.header.is_some()
            || document.footer.is_some()
            || layout.pages.iter().any(|p| p.custom_properties.header.is_some() || p.custom_properties.footer.is_some());

        if has_header_footer {
            let total_pages = layout.pages.len() as u32;
            let available_width = document.page_size.width - document.page_margins.left - document.page_margins.right;

            // Pre-compute static header/footer items (layout once, clone for all pages)
            let static_header_items = match &document.header {
                Some(HeaderFooter::Static(node)) => {
                    let items = self.layout_single_header_footer(
                        node,
                        document,
                        &fonts,
                        document.page_margins.left,
                        0.0,
                        available_width,
                        document.page_margins.top,
                    )?;
                    if items.is_empty() { None } else { Some(items) }
                }
                _ => None,
            };

            let static_footer_items = match &document.footer {
                Some(HeaderFooter::Static(node)) => {
                    let footer_y = document.page_size.height - document.page_margins.bottom;
                    let items = self.layout_single_header_footer(
                        node,
                        document,
                        &fonts,
                        document.page_margins.left,
                        footer_y,
                        available_width,
                        document.page_margins.bottom,
                    )?;
                    if items.is_empty() { None } else { Some(items) }
                }
                _ => None,
            };

            for (i, page) in layout.pages.iter_mut().enumerate() {
                let page_num = (i + 1) as u32;

                let header_hf = page.custom_properties.header.as_ref()
                    .or(document.header.as_ref());

                if let Some(header) = header_hf {
                    let items = match header {
                        HeaderFooter::Static(_) => {
                            // Reuse pre-computed static items
                            static_header_items.clone().unwrap_or_default()
                        }
                        HeaderFooter::Dynamic(_) => {
                            let node = header.resolve(page_num, total_pages, &document.page_size);
                            self.layout_single_header_footer(
                                &node,
                                document,
                                &fonts,
                                document.page_margins.left,
                                0.0,
                                available_width,
                                document.page_margins.top,
                            )?
                        }
                    };
                    if !items.is_empty() {
                        page.header = Some(layout::LayoutHeaderFooter { items });
                    }
                }

                let footer_hf = page.custom_properties.footer.as_ref()
                    .or(document.footer.as_ref());

                if let Some(footer) = footer_hf {
                    let items = match footer {
                        HeaderFooter::Static(_) => {
                            // Reuse pre-computed static items
                            static_footer_items.clone().unwrap_or_default()
                        }
                        HeaderFooter::Dynamic(_) => {
                            let node = footer.resolve(page_num, total_pages, &document.page_size);
                            let footer_y = document.page_size.height - document.page_margins.bottom;
                            self.layout_single_header_footer(
                                &node,
                                document,
                                &fonts,
                                document.page_margins.left,
                                footer_y,
                                available_width,
                                document.page_margins.bottom,
                            )?
                        }
                    };
                    if !items.is_empty() {
                        page.footer = Some(layout::LayoutHeaderFooter { items });
                    }
                }
            }
        }

        let config = render::RenderConfig {
            pdf_version: document.pdf_version.as_deref(),
            compress: document.compress.unwrap_or(true),
            language: document.language.as_deref(),
            tagged: document.tagged.unwrap_or(false),
            named_dests: &document.named_dests,
            outlines: &document.outlines,
            attachments: &document.attachments,
        };
        render::render_pdf(&layout, &fonts, options, document.info.as_ref(), document.encryption.as_ref(), &config)
    }

    pub fn layout_single_header_footer(
        &self,
        node: &DocumentNode,
        document: &Document,
        fonts: &FontRegistry,
        x: f32,
        y: f32,
        available_width: f32,
        height_limit: f32,
    ) -> Result<Vec<LayoutItem>> {
        let mut tmp_doc = Document::default();
        tmp_doc.content = vec![node.clone()];
        tmp_doc.page_size = document.page_size;
        tmp_doc.page_margins = document.page_margins;
        tmp_doc.default_style = document.default_style.clone();
        tmp_doc.styles = document.styles.clone();
        tmp_doc.fonts = document.fonts.clone();

        let norm = normalize::normalize_document(&tmp_doc)?;
        let meas = measure::measure_document(&norm, fonts)?;

        Ok(layout::layout_header_footer(
            &meas.blocks,
            x,
            y,
            available_width,
            height_limit,
            &document.page_size,
            &document.page_margins,
        ))
    }

    fn layout_background(
        &self,
        node: &DocumentNode,
        document: &Document,
        fonts: &FontRegistry,
    ) -> Result<Vec<LayoutItem>> {
        let mut tmp_doc = Document::default();
        tmp_doc.content = vec![node.clone()];
        tmp_doc.page_size = document.page_size;
        tmp_doc.page_margins = document.page_margins;
        tmp_doc.default_style = document.default_style.clone();
        tmp_doc.styles = document.styles.clone();
        tmp_doc.fonts = document.fonts.clone();

        let norm = normalize::normalize_document(&tmp_doc)?;
        let meas = measure::measure_document(&norm, fonts)?;
        let bg_layout = layout::layout_document_with_fonts(&meas, fonts, None)?;

        Ok(bg_layout.pages.into_iter().next().map(|p| p.items).unwrap_or_default())
    }
}

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

fn substitute_toc_page_numbers(layout: &mut layout::LayoutDocument, toc_items: &std::collections::BTreeMap<String, Vec<normalize::TocEntry>>) {
    if toc_items.is_empty() {
        return;
    }

    // Build a map from toc entry text to page index
    let mut entry_page_map: std::collections::BTreeMap<String, std::collections::BTreeMap<String, usize>> = std::collections::BTreeMap::new();
    for (toc_id, entries) in toc_items {
        for entry in entries {
            entry_page_map.entry(toc_id.clone()).or_default().insert(entry.text.clone(), 0);
        }
    }

    // Scan all pages to find which page each toc_item text appears on
    for (page_idx, page) in layout.pages.iter().enumerate() {
        for item in &page.items {
            scan_item_for_toc_text(item, page_idx, &mut entry_page_map);
        }
    }

    // Update TOC entries with page numbers
    for page in &mut layout.pages {
        for item in &mut page.items {
            if let layout::LayoutItem::Toc(toc) = item {
                if let Some(toc_id) = &toc.id {
                    if let Some(entries_map) = entry_page_map.get(toc_id) {
                        for entry in &mut toc.entries {
                            if let Some(&page_num) = entries_map.get(&entry.text) {
                                entry.page_number = Some(page_num + 1); // 1-based page numbers
                            }
                        }
                    }
                }
            }
        }
    }
}

fn scan_item_for_toc_text(item: &layout::LayoutItem, page_idx: usize, entry_page_map: &mut std::collections::BTreeMap<String, std::collections::BTreeMap<String, usize>>) {
    match item {
        layout::LayoutItem::Text(text) => {
            // Check if this text matches any TOC entry
            for (_, entries_map) in entry_page_map.iter_mut() {
                for (entry_text, page_num) in entries_map.iter_mut() {
                    if &text.text == entry_text && *page_num == 0 {
                        *page_num = page_idx;
                    }
                }
            }
        }
        layout::LayoutItem::Table(table) => {
            for row in &table.rows {
                for cell in &row.cells {
                    for nested_item in &cell.items {
                        scan_item_for_toc_text(nested_item, page_idx, entry_page_map);
                    }
                }
            }
        }
        layout::LayoutItem::Columns(cols) => {
            for col in &cols.columns {
                for nested_item in col {
                    scan_item_for_toc_text(nested_item, page_idx, entry_page_map);
                }
            }
        }
        layout::LayoutItem::List(list) => {
            for list_item in &list.items {
                for nested_item in &list_item.content {
                    scan_item_for_toc_text(nested_item, page_idx, entry_page_map);
                }
            }
        }
        _ => {}
    }
}

fn substitute_page_references(layout: &mut layout::LayoutDocument) {
    // First pass: build a map of node id -> page index
    let mut id_page_map: std::collections::BTreeMap<String, usize> = std::collections::BTreeMap::new();
    for (page_idx, page) in layout.pages.iter().enumerate() {
        for item in &page.items {
            scan_item_for_ids(item, page_idx, &mut id_page_map);
        }
    }

    if id_page_map.is_empty() {
        return;
    }

    // Second pass: substitute page numbers in text items with page_reference
    for page in &mut layout.pages {
        for item in &mut page.items {
            substitute_refs_in_item(item, &id_page_map);
        }
    }
}

fn scan_item_for_ids(item: &layout::LayoutItem, page_idx: usize, id_page_map: &mut std::collections::BTreeMap<String, usize>) {
    match item {
        layout::LayoutItem::Text(text) => {
            if let Some(ref id) = text.id {
                id_page_map.entry(id.clone()).or_insert(page_idx);
            }
        }
        layout::LayoutItem::Table(table) => {
            for row in &table.rows {
                for cell in &row.cells {
                    for nested_item in &cell.items {
                        scan_item_for_ids(nested_item, page_idx, id_page_map);
                    }
                }
            }
        }
        layout::LayoutItem::Columns(cols) => {
            for col in &cols.columns {
                for nested_item in col {
                    scan_item_for_ids(nested_item, page_idx, id_page_map);
                }
            }
        }
        layout::LayoutItem::List(list) => {
            for list_item in &list.items {
                for nested_item in &list_item.content {
                    scan_item_for_ids(nested_item, page_idx, id_page_map);
                }
            }
        }
        _ => {}
    }
}

fn substitute_refs_in_item(item: &mut layout::LayoutItem, id_page_map: &std::collections::BTreeMap<String, usize>) {
    match item {
        layout::LayoutItem::Text(text) => {
            if let Some(ref target_id) = text.page_reference {
                if let Some(&page_idx) = id_page_map.get(target_id) {
                    let page_num = page_idx + 1; // 1-based
                    let new_text = page_num.to_string();
                    // Update the text and recompute width
                    text.text = new_text.clone();
                    // Update the first run's text
                    if let Some(first_run) = text.runs.first_mut() {
                        first_run.text = new_text;
                    }
                    // Recompute width based on the new text
                    let font_size = text.runs.first().map(|r| r.font_size).unwrap_or(11.0);
                    text.width = text.text.len() as f32 * font_size * 0.5;
                }
            }
        }
        layout::LayoutItem::Table(table) => {
            for row in &mut table.rows {
                for cell in &mut row.cells {
                    for nested_item in &mut cell.items {
                        substitute_refs_in_item(nested_item, id_page_map);
                    }
                }
            }
        }
        layout::LayoutItem::Columns(cols) => {
            for col in &mut cols.columns {
                for nested_item in col {
                    substitute_refs_in_item(nested_item, id_page_map);
                }
            }
        }
        layout::LayoutItem::List(list) => {
            for list_item in &mut list.items {
                for nested_item in &mut list_item.content {
                    substitute_refs_in_item(nested_item, id_page_map);
                }
            }
        }
        _ => {}
    }
}