mod table;
mod list;

use crate::{
    error::Result,
    font::FontRegistry,
    model::{FontSource, HeaderFooter, Margins, PageBreakMode, PageSize, WatermarkConfig},
    normalize::NormalizedDocument,
    style::ResolvedStyle,
    text::{InlineLine, TextMeasurer, wrap_inlines_with_fonts},
};
use std::collections::BTreeMap;
use std::sync::Arc;

use table::measure_table_block;
use list::measure_list_block;

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredDocument {
    pub page_size: PageSize,
    pub page_margins: Margins,
    pub fonts: BTreeMap<String, FontSource>,
    pub blocks: Vec<MeasuredBlock>,
    pub watermark: Option<crate::model::WatermarkConfig>,
}

#[derive(Debug, Clone, PartialEq)]
pub enum MeasuredBlock {
    Text(MeasuredTextBlock),
    Table(MeasuredTable),
    Image(MeasuredImage),
    Qr(MeasuredQr),
    Svg(MeasuredSvg),
    Columns(MeasuredColumns),
    Absolute(MeasuredAbsolute),
    List(MeasuredList),
    Section(MeasuredSection),
    Canvas(MeasuredCanvas),
    Toc(MeasuredToc),
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredToc {
    pub id: Option<String>,
    pub title: Option<Box<MeasuredBlock>>,
    pub text_style: ResolvedStyle,
    pub number_style: ResolvedStyle,
    pub title_height: f32,
    pub entries: Vec<MeasuredTocEntry>,
    pub height: f32,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredTocEntry {
    pub text: String,
    pub style: ResolvedStyle,
    pub page_number: Option<usize>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredTextBlock {
    pub lines: Vec<InlineLine>,
    pub style: ResolvedStyle,
    pub line_height: f32,
    pub height: f32,
    pub page_break: Option<PageBreakMode>,
    pub id: Option<String>,
    pub page_reference: Option<String>,
    pub toc_item: Option<String>,
    pub origin_node_idx: Option<usize>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredTable {
    pub column_widths: Vec<f32>,
    pub rows: Vec<MeasuredTableRow>,
    pub style: ResolvedStyle,
    pub height: f32,
    pub header_rows: Option<usize>,
    pub dont_break_rows: bool,
    pub page_break: Option<PageBreakMode>,
    pub heights: Option<Vec<f32>>,
    pub keep_with_header_rows: Option<usize>,
    pub table_style: Option<crate::model::TableStyle>,
    pub layout_config: Option<crate::model::TableLayoutConfig>,
    pub h_line_when_broken: Option<bool>,
    pub origin_node_idx: Option<usize>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredTableRow {
    pub cells: Vec<MeasuredTableCell>,
    pub height: f32,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredTableCell {
    pub lines: Vec<InlineLine>,
    pub items: Vec<MeasuredBlock>,
    pub width: f32,
    pub height: f32,
    pub padding: f32,
    pub style: ResolvedStyle,
    pub col_span: usize,
    pub row_span: usize,
    pub vertical_alignment: Option<crate::model::VerticalAlignment>,
    pub border: Option<Vec<bool>>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredImage {
    pub source: crate::model::ImageSource,
    pub format: crate::image::ImageFormat,
    pub data: Arc<Vec<u8>>,
    pub intrinsic_width: u32,
    pub intrinsic_height: u32,
    pub width: f32,
    pub height: f32,
    pub alignment: crate::model::Alignment,
    pub cover: Option<crate::model::ImageCover>,
    pub opacity: Option<f32>,
    pub link: Option<String>,
    pub link_to_page: Option<usize>,
    pub link_to_destination: Option<String>,
    pub origin_node_idx: Option<usize>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredQr {
    pub text: String,
    pub modules: usize,
    pub dark_modules: Vec<crate::qr::DarkModule>,
    pub width: f32,
    pub height: f32,
    pub alignment: crate::model::Alignment,
    pub foreground: Option<String>,
    pub background: Option<String>,
    pub ecc_level: Option<String>,
    pub padding: Option<f32>,
    pub mode: Option<String>,
    pub version: Option<u32>,
    pub mask: Option<u8>,
    pub origin_node_idx: Option<usize>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredSvg {
    pub markup: String,
    pub view_width: f32,
    pub view_height: f32,
    pub width: f32,
    pub height: f32,
    pub shapes: Vec<crate::vector::VectorShape>,
    pub alignment: crate::model::Alignment,
    pub link: Option<String>,
    pub link_to_page: Option<usize>,
    pub link_to_destination: Option<String>,
    pub origin_node_idx: Option<usize>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredAbsolute {
    pub blocks: Vec<MeasuredBlock>,
    pub left: Option<f32>,
    pub top: Option<f32>,
    pub right: Option<f32>,
    pub bottom: Option<f32>,
    pub center_horizontal: bool,
    pub center_vertical: bool,
    pub width: f32,
    pub height: f32,
    pub origin_node_idx: Option<usize>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredColumns {
    pub columns: Vec<Vec<MeasuredBlock>>,
    pub column_gap: f32,
    pub snaking_columns: bool,
    pub width: f32,
    pub height: f32,
    pub origin_node_idx: Option<usize>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredList {
    pub items: Vec<Vec<MeasuredBlock>>,
    pub ordered: bool,
    pub marker_type: String,
    pub gap_size: f32,
    pub max_text_width: f32,
    pub width: f32,
    pub height: f32,
    pub item_markers: Vec<ListItemMarker>,
    pub font_size: f32,
    pub font_family: Option<String>,
    pub marker_color: Option<String>,
    pub origin_node_idx: Option<usize>,
}

#[derive(Debug, Clone, PartialEq)]
pub enum ListItemMarker {
    Bullet { width: f32, height: f32 },
    Number { text: String, text_width: f32, width: f32, height: f32 },
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredSection {
    pub blocks: Vec<MeasuredBlock>,
    pub page_size: Option<PageSize>,
    pub page_margins: Option<Margins>,
    pub header: Option<HeaderFooter>,
    pub footer: Option<HeaderFooter>,
    pub watermark: Option<WatermarkConfig>,
    pub height: f32,
    pub origin_node_idx: Option<usize>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasuredCanvas {
    pub shapes: Vec<crate::model::CanvasShape>,
    pub width: f32,
    pub height: f32,
}

pub fn measure_document(document: &NormalizedDocument, fonts: &FontRegistry) -> Result<MeasuredDocument> {
    let available_width = document.page_size.width - document.page_margins.left - document.page_margins.right;
    let mut measurer = TextMeasurer::new();

    let mut blocks = Vec::new();
    for block in &document.blocks {
        blocks.push(measure_one_block(block, &mut measurer, fonts, available_width)?);
    }

    // Populate TOC entries from toc_items collected during normalization
    populate_toc_entries(&mut blocks, &document.toc_items);

    Ok(MeasuredDocument { page_size: document.page_size, page_margins: document.page_margins, fonts: document.fonts.clone(), blocks, watermark: document.watermark.clone() })
}

fn populate_toc_entries(blocks: &mut [MeasuredBlock], toc_items: &std::collections::BTreeMap<String, Vec<crate::normalize::TocEntry>>) {
    for block in blocks.iter_mut() {
        if let MeasuredBlock::Toc(toc) = block {
            if let Some(toc_id) = &toc.id {
                if let Some(entries) = toc_items.get(toc_id) {
                    toc.entries = entries.iter().map(|e| MeasuredTocEntry {
                        text: e.text.clone(),
                        style: e.style.clone(),
                        page_number: None,
                    }).collect();
                    // Update height to include entries
                    let entry_height = toc.text_style.line_height * toc.entries.len() as f32;
                    toc.height = toc.title_height + entry_height;
                }
            }
        }
    }
}

fn measure_one_block(
    block: &crate::normalize::NormalizedBlock,
    measurer: &mut TextMeasurer,
    fonts: &FontRegistry,
    available_width: f32,
) -> Result<MeasuredBlock> {
    match block {
        crate::normalize::NormalizedBlock::Text(text) => {
            measure_text_block(text, available_width, measurer, fonts).map(MeasuredBlock::Text)
        }
        crate::normalize::NormalizedBlock::Table(table) => {
            measure_table_block(table, available_width, measurer, fonts).map(MeasuredBlock::Table)
        }
        crate::normalize::NormalizedBlock::Image(image) => measure_image_block(image).map(MeasuredBlock::Image),
        crate::normalize::NormalizedBlock::Qr(qr) => measure_qr_block(qr).map(MeasuredBlock::Qr),
        crate::normalize::NormalizedBlock::Svg(svg) => measure_svg_block(svg).map(MeasuredBlock::Svg),
        crate::normalize::NormalizedBlock::Columns(cols) => {
            let column_count = cols.columns.len() as f32;
            let column_width = if column_count > 1.0 {
                (available_width - cols.column_gap * (column_count - 1.0)) / column_count
            } else {
                available_width
            };

            let mut measured_columns = Vec::new();
            let mut max_height = 0.0f32;

            for col_blocks in &cols.columns {
                let mut col_measured = Vec::new();
                let mut col_height = 0.0f32;

                for block in col_blocks {
                    let measured = measure_one_block(block, measurer, fonts, column_width)?;

                    let block_height = match &measured {
                        MeasuredBlock::Text(text) => text.height,
                        MeasuredBlock::Table(table) => table.height,
                        MeasuredBlock::Image(image) => image.height,
                        MeasuredBlock::Qr(qr) => qr.height,
                        MeasuredBlock::Svg(svg) => svg.height,
                        MeasuredBlock::Columns(_) => 0.0,
                        MeasuredBlock::Absolute(_) => 0.0,
                        MeasuredBlock::List(l) => l.height,
                        MeasuredBlock::Section(s) => s.height,
                        MeasuredBlock::Canvas(c) => c.height,
                        MeasuredBlock::Toc(t) => t.height,
                };

                col_height += block_height;
                    col_measured.push(measured);
                }

                max_height = max_height.max(col_height);
                measured_columns.push(col_measured);
            }

            Ok(MeasuredBlock::Columns(MeasuredColumns {
                columns: measured_columns,
                column_gap: cols.column_gap,
                snaking_columns: cols.snaking_columns,
                width: available_width,
                height: max_height,
                origin_node_idx: cols.origin_node_idx,
            }))
        }
        crate::normalize::NormalizedBlock::Absolute(abs) => {
            let mut measured_blocks = Vec::new();
            let mut total_height = 0.0f32;
            let mut max_width = 0.0f32;

            for block in &abs.blocks {
                let measured = measure_one_block(block, measurer, fonts, available_width)?;
                let (block_width, block_height) = match &measured {
                    MeasuredBlock::Text(text) => {
                        let width = text.lines.iter().map(|l| l.width).fold(0.0f32, f32::max);
                        (width, text.height)
                    }
                    MeasuredBlock::Table(table) => {
                        let width: f32 = table.column_widths.iter().sum();
                        (width, table.height)
                    }
                    MeasuredBlock::Image(image) => (image.width, image.height),
                    MeasuredBlock::Qr(qr) => (qr.width, qr.height),
                    MeasuredBlock::Svg(svg) => (svg.width, svg.height),
                    MeasuredBlock::Columns(_) => (0.0, 0.0),
                    MeasuredBlock::Absolute(_) => (0.0, 0.0),
                    MeasuredBlock::List(l) => (l.width, l.height),
                    MeasuredBlock::Section(s) => (0.0, s.height),
                    MeasuredBlock::Canvas(c) => (c.width, c.height),
                    MeasuredBlock::Toc(t) => (0.0, t.height),
                };
                max_width = max_width.max(block_width);
                total_height += block_height;
                measured_blocks.push(measured);
            }

            Ok(MeasuredBlock::Absolute(MeasuredAbsolute {
                blocks: measured_blocks,
                left: abs.left,
                top: abs.top,
                right: abs.right,
                bottom: abs.bottom,
                center_horizontal: abs.center_horizontal,
                center_vertical: abs.center_vertical,
                width: max_width,
                height: total_height,
                origin_node_idx: abs.origin_node_idx,
            }))
        }
        crate::normalize::NormalizedBlock::List(list) => {
            measure_list_block(list, available_width, measurer, fonts).map(MeasuredBlock::List)
        }
        crate::normalize::NormalizedBlock::Section(section) => {
            let mut measured_blocks = Vec::new();
            let mut total_height = 0.0f32;
            for block in &section.blocks {
                let measured = measure_one_block(block, measurer, fonts, available_width)?;
                total_height += measured_block_height(&measured);
                measured_blocks.push(measured);
            }
            Ok(MeasuredBlock::Section(MeasuredSection {
                blocks: measured_blocks,
                page_size: section.page_size.clone(),
                page_margins: section.page_margins.clone(),
                header: section.header.clone(),
                footer: section.footer.clone(),
                watermark: section.watermark.clone(),
                height: total_height,
                origin_node_idx: section.origin_node_idx,
            }))
        }
        crate::normalize::NormalizedBlock::Canvas(canvas) => {
            let mut max_x = 0.0f32;
            let mut max_y = 0.0f32;
            for shape in &canvas.shapes {
                match shape {
                    crate::model::CanvasShape::Rect { x, y, w, h, .. } => {
                        max_x = max_x.max(*x + *w);
                        max_y = max_y.max(*y + *h);
                    }
                    crate::model::CanvasShape::Ellipse { x, y, r1, r2, .. } => {
                        max_x = max_x.max(*x + *r1);
                        max_y = max_y.max(*y + *r2);
                    }
                    crate::model::CanvasShape::Line { x1, y1, x2, y2, .. } => {
                        max_x = max_x.max(*x1).max(*x2);
                        max_y = max_y.max(*y1).max(*y2);
                    }
                    crate::model::CanvasShape::Polyline { points, .. } => {
                        for p in points {
                            max_x = max_x.max(p.x);
                            max_y = max_y.max(p.y);
                        }
                    }
                }
            }
            Ok(MeasuredBlock::Canvas(MeasuredCanvas {
                shapes: canvas.shapes.clone(),
                width: max_x,
                height: max_y,
            }))
        }
        crate::normalize::NormalizedBlock::Toc(toc) => {
            let title_height = if let Some(ref title) = toc.title {
                let measured_title = measured_block_from_normalized(title, measurer, fonts, available_width)?;
                measured_block_height(&measured_title)
            } else {
                0.0
            };

            Ok(MeasuredBlock::Toc(MeasuredToc {
                id: toc.id.clone(),
                title: if let Some(ref title) = toc.title {
                    Some(Box::new(measured_block_from_normalized(title, measurer, fonts, available_width)?))
                } else {
                    None
                },
                text_style: toc.text_style.clone(),
                number_style: toc.number_style.clone(),
                title_height,
                entries: Vec::new(),
                height: title_height,
            }))
        }
    }
}

fn measure_text_block(block: &crate::normalize::NormalizedTextBlock, available_width: f32, measurer: &mut TextMeasurer, fonts: &FontRegistry) -> Result<MeasuredTextBlock> {
    let margin = block.style.margin.unwrap_or(Margins::all(0.0));
    let content_width = (available_width - margin.left - margin.right).max(0.0);
    let lines = wrap_inlines_with_fonts(&block.inlines, content_width, measurer, fonts)?;
    let line_height = lines.iter().map(|line| line.line_height).fold(0.0f32, f32::max);
    let height = lines.iter().map(|line| line.line_height).sum::<f32>() + margin.top + margin.bottom;
    Ok(MeasuredTextBlock { lines, style: block.style.clone(), line_height, height, page_break: block.page_break, id: block.id.clone(), page_reference: block.page_reference.clone(), toc_item: block.toc_item.clone(), origin_node_idx: block.origin_node_idx })
}

fn measured_block_height(block: &MeasuredBlock) -> f32 {
    match block {
        MeasuredBlock::Text(t) => t.height,
        MeasuredBlock::Table(t) => t.height,
        MeasuredBlock::Image(i) => i.height,
        MeasuredBlock::Qr(q) => q.height,
        MeasuredBlock::Svg(s) => s.height,
        MeasuredBlock::Columns(c) => c.height,
        MeasuredBlock::Absolute(a) => a.height,
        MeasuredBlock::List(l) => l.height,
        MeasuredBlock::Section(s) => s.height,
        MeasuredBlock::Canvas(c) => c.height,
        MeasuredBlock::Toc(t) => t.height,
    }
}

fn measured_block_from_normalized(
    block: &crate::normalize::NormalizedBlock,
    measurer: &mut TextMeasurer,
    fonts: &FontRegistry,
    available_width: f32,
) -> Result<MeasuredBlock> {
    measure_one_block(block, measurer, fonts, available_width)
}

fn measure_image_block(image: &crate::normalize::NormalizedImage) -> Result<MeasuredImage> {
    let loaded = crate::image::load_image(&image.source)?;
    let (mut width, mut height) = crate::image::resolve_display_size(loaded.width, loaded.height, image.width, image.height, image.fit)?;

    if let Some(cover) = image.cover {
        width = cover.width;
        height = cover.height;
    }

    if let Some(max_w) = image.max_width {
        if width > max_w {
            let ratio = max_w / width;
            width = max_w;
            height *= ratio;
        }
    }
    if let Some(max_h) = image.max_height {
        if height > max_h {
            let ratio = max_h / height;
            height = max_h;
            width *= ratio;
        }
    }

    if let Some(min_w) = image.min_width {
        if width < min_w {
            let ratio = min_w / width;
            width = min_w;
            height *= ratio;
        }
    }
    if let Some(min_h) = image.min_height {
        if height < min_h {
            let ratio = min_h / height;
            height = min_h;
            width *= ratio;
        }
    }

    Ok(MeasuredImage {
        source: image.source.clone(),
        format: loaded.format,
        data: loaded.data,
        intrinsic_width: loaded.width,
        intrinsic_height: loaded.height,
        width,
        height,
        alignment: image.alignment,
        cover: image.cover,
        opacity: image.opacity,
        link: image.link.clone(),
        link_to_page: image.link_to_page,
        link_to_destination: image.link_to_destination.clone(),
        origin_node_idx: image.origin_node_idx,
    })
}

fn measure_qr_block(qr: &crate::normalize::NormalizedQr) -> Result<MeasuredQr> {
    let matrix = crate::qr::encode_qr(&qr.text, qr.ecc_level.as_deref(), qr.mode.as_deref(), qr.version, qr.mask)?;
    let size = crate::qr::resolve_qr_size(qr.fit)?;
    Ok(MeasuredQr {
        text: qr.text.clone(),
        modules: matrix.modules,
        dark_modules: matrix.dark_modules,
        width: size,
        height: size,
        alignment: qr.alignment,
        foreground: qr.foreground.clone(),
        background: qr.background.clone(),
        ecc_level: qr.ecc_level.clone(),
        padding: qr.padding,
        mode: qr.mode.clone(),
        version: qr.version,
        mask: qr.mask,
        origin_node_idx: qr.origin_node_idx,
    })
}

fn measure_svg_block(svg: &crate::normalize::NormalizedSvg) -> Result<MeasuredSvg> {
    let parsed = crate::svg::parse_svg(&svg.markup)?;
    let (width, height) = crate::svg::resolve_svg_size(parsed.view_width, parsed.view_height, svg.width, svg.height, svg.fit)?;
    Ok(MeasuredSvg {
        markup: svg.markup.clone(),
        view_width: parsed.view_width,
        view_height: parsed.view_height,
        width,
        height,
        shapes: parsed.shapes,
        alignment: svg.alignment,
        link: svg.link.clone(),
        link_to_page: svg.link_to_page,
        link_to_destination: svg.link_to_destination.clone(),
        origin_node_idx: svg.origin_node_idx,
    })
}
