use crate::{
    error::{PdfMakeError, Result},
    model::{Document, DocumentNode, HeaderFooter, ImageSource, Margins, PageBreakMode, PageSize, TableCellContent, TableNode, TableStyle, TextNode, WatermarkConfig},
    style::ResolvedStyle,
    text::NormalizedInline,
};

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

#[derive(Debug, Clone, PartialEq)]
pub struct TocEntry {
    pub text: String,
    pub style: ResolvedStyle,
}

#[derive(Debug, Clone, PartialEq)]
pub enum NormalizedBlock {
    Text(NormalizedTextBlock),
    Table(NormalizedTable),
    Image(NormalizedImage),
    Svg(NormalizedSvg),
    Qr(NormalizedQr),
    Columns(NormalizedColumns),
    Absolute(NormalizedAbsolute),
    List(NormalizedList),
    Section(NormalizedSection),
    Canvas(NormalizedCanvas),
    Toc(NormalizedToc),
}

impl NormalizedBlock {
    pub fn origin_node_idx(&self) -> Option<usize> {
        match self {
            Self::Text(b) => b.origin_node_idx,
            Self::Table(b) => b.origin_node_idx,
            Self::Image(b) => b.origin_node_idx,
            Self::Svg(b) => b.origin_node_idx,
            Self::Qr(b) => b.origin_node_idx,
            Self::Columns(b) => b.origin_node_idx,
            Self::Absolute(b) => b.origin_node_idx,
            Self::List(b) => b.origin_node_idx,
            Self::Section(b) => b.origin_node_idx,
            Self::Canvas(_) => None,
            Self::Toc(_) => None,
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct NormalizedTextBlock {
    pub inlines: Vec<NormalizedInline>,
    pub style: ResolvedStyle,
    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 NormalizedImage {
    pub source: ImageSource,
    pub width: Option<f32>,
    pub height: Option<f32>,
    pub fit: Option<crate::model::Fit>,
    pub alignment: crate::model::Alignment,
    pub max_width: Option<f32>,
    pub max_height: Option<f32>,
    pub min_width: Option<f32>,
    pub min_height: Option<f32>,
    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 NormalizedSvg {
    pub markup: String,
    pub width: Option<f32>,
    pub height: Option<f32>,
    pub fit: Option<crate::model::Fit>,
    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 NormalizedQr {
    pub text: String,
    pub fit: Option<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 NormalizedTable {
    pub rows: Vec<Vec<NormalizedTableCell>>,
    pub widths: Vec<crate::model::TableWidth>,
    pub heights: Option<Vec<f32>>,
    pub style: ResolvedStyle,
    pub header_rows: Option<usize>,
    pub dont_break_rows: bool,
    pub page_break: Option<PageBreakMode>,
    pub keep_with_header_rows: Option<usize>,
    pub table_style: Option<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 enum NormalizedCellContent {
    Inlines(Vec<NormalizedInline>),
    Blocks(Vec<NormalizedBlock>),
}

#[derive(Debug, Clone, PartialEq)]
pub struct NormalizedTableCell {
    pub content: NormalizedCellContent,
    pub style: ResolvedStyle,
    pub padding: f32,
    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 NormalizedColumns {
    pub columns: Vec<Vec<NormalizedBlock>>,
    pub column_gap: f32,
    pub snaking_columns: bool,
    pub origin_node_idx: Option<usize>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct NormalizedAbsolute {
    pub blocks: Vec<NormalizedBlock>,
    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 origin_node_idx: Option<usize>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct NormalizedList {
    pub items: Vec<Vec<NormalizedBlock>>,
    pub ordered: bool,
    pub marker_type: String,
    pub start: usize,
    pub separator: String,
    pub reversed: bool,
    pub style: ResolvedStyle,
    pub color: Option<String>,
    pub origin_node_idx: Option<usize>,
}

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

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

#[derive(Debug, Clone, PartialEq)]
pub struct NormalizedToc {
    pub id: Option<String>,
    pub title: Option<Box<NormalizedBlock>>,
    pub text_style: ResolvedStyle,
    pub number_style: ResolvedStyle,
    pub origin_node_idx: Option<usize>,
}

fn resolve_marker_type(user_type: &Option<String>, ordered: bool) -> String {
    match (user_type, ordered) {
        (Some(t), _) => t.clone(),
        (None, true) => "decimal".to_string(),
        (None, false) => "disc".to_string(),
    }
}

pub fn normalize_document(document: &Document) -> Result<NormalizedDocument> {
    if document.page_size.width <= 0.0 || document.page_size.height <= 0.0 {
        return Err(PdfMakeError::InvalidPageSize(format!("{}x{}", document.page_size.width, document.page_size.height)));
    }

    let mut blocks = Vec::new();
    for (idx, node) in document.content.iter().enumerate() {
        normalize_node(node, document, &mut blocks, Some(idx))?;
    }

    let toc_items = collect_toc_items(&blocks);

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

fn collect_toc_items(blocks: &[NormalizedBlock]) -> std::collections::BTreeMap<String, Vec<TocEntry>> {
    let mut toc_items: std::collections::BTreeMap<String, Vec<TocEntry>> = std::collections::BTreeMap::new();

    fn scan_blocks(blocks: &[NormalizedBlock], toc_items: &mut std::collections::BTreeMap<String, Vec<TocEntry>>) {
        for block in blocks {
            match block {
                NormalizedBlock::Text(text) => {
                    if let Some(ref toc_id) = text.toc_item {
                        let entry_text: String = text.inlines.iter().map(|i| i.text.as_str()).collect();
                        let entry = TocEntry {
                            text: entry_text,
                            style: text.style.clone(),
                        };
                        toc_items.entry(toc_id.clone()).or_default().push(entry);
                    }
                }
                NormalizedBlock::Table(table) => {
                    for row in &table.rows {
                        for cell in row {
                            match &cell.content {
                                crate::normalize::NormalizedCellContent::Inlines(_) => {}
                                crate::normalize::NormalizedCellContent::Blocks(blocks) => {
                                    scan_blocks(blocks, toc_items);
                                }
                            }
                        }
                    }
                }
                NormalizedBlock::List(list) => {
                    for item in &list.items {
                        scan_blocks(item, toc_items);
                    }
                }
                NormalizedBlock::Columns(columns) => {
                    for col in &columns.columns {
                        scan_blocks(col, toc_items);
                    }
                }
                NormalizedBlock::Absolute(abs) => {
                    scan_blocks(&abs.blocks, toc_items);
                }
                NormalizedBlock::Section(section) => {
                    scan_blocks(&section.blocks, toc_items);
                }
                _ => {}
            }
        }
    }

    scan_blocks(blocks, &mut toc_items);
    toc_items
}

fn normalize_node(node: &DocumentNode, document: &Document, blocks: &mut Vec<NormalizedBlock>, origin_node_idx: Option<usize>) -> Result<()> {
    match node {
        DocumentNode::Text(text) => {
            let mut b = normalize_text(text, document)?;
            b.origin_node_idx = origin_node_idx;
            blocks.push(NormalizedBlock::Text(b));
        }
        DocumentNode::Table(table) => {
            let mut b = normalize_table(table, document)?;
            b.origin_node_idx = origin_node_idx;
            blocks.push(NormalizedBlock::Table(b));
        }
        #[cfg(feature = "image")]
        DocumentNode::Image(image) => blocks.push(NormalizedBlock::Image(NormalizedImage {
            source: image.source.clone(),
            width: image.width,
            height: image.height,
            fit: image.fit,
            alignment: image.alignment,
            max_width: image.max_width,
            max_height: image.max_height,
            min_width: image.min_width,
            min_height: image.min_height,
            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,
        })),
        #[cfg(feature = "svg")]
        DocumentNode::Svg(svg) => blocks.push(NormalizedBlock::Svg(NormalizedSvg {
            markup: svg.markup.clone(),
            width: svg.width,
            height: svg.height,
            fit: svg.fit,
            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,
        })),
        #[cfg(feature = "qr")]
        DocumentNode::Qr(qr) => blocks.push(NormalizedBlock::Qr(NormalizedQr {
            text: qr.text.clone(),
            fit: qr.fit,
            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,
        })),
        DocumentNode::Stack(children) => {
            for child in children {
                normalize_node(child, document, blocks, origin_node_idx)?;
            }
        }
        DocumentNode::Columns(columns) => {
            let mut normalized_columns = Vec::new();
            for col in &columns.columns {
                let mut col_blocks = Vec::new();
                for node in col {
                    normalize_node(node, document, &mut col_blocks, origin_node_idx)?;
                }
                normalized_columns.push(col_blocks);
            }
            blocks.push(NormalizedBlock::Columns(NormalizedColumns {
                columns: normalized_columns,
                column_gap: columns.column_gap,
                snaking_columns: columns.snaking_columns,
                origin_node_idx,
            }));
        }
        DocumentNode::Absolute(abs) => {
            let mut inner = Vec::new();
            normalize_node(&abs.content, document, &mut inner, origin_node_idx)?;
            blocks.push(NormalizedBlock::Absolute(NormalizedAbsolute {
                blocks: inner,
                left: abs.left,
                top: abs.top,
                right: abs.right,
                bottom: abs.bottom,
                center_horizontal: abs.center_horizontal,
                center_vertical: abs.center_vertical,
                origin_node_idx,
            }));
        }
        DocumentNode::List(list) => {
            let marker_type = resolve_marker_type(&list.marker_type, list.ordered);
            let start = list.start.unwrap_or(if list.ordered { 1 } else { 0 });
            let separator = list.separator.clone().unwrap_or_else(|| {
                if list.ordered { ".".to_string() } else { String::new() }
            });
            let reversed = list.reversed;
            let list_style = ResolvedStyle::from(&document.default_style);

            let mut normalized_items = Vec::new();
            for item in &list.items {
                let mut item_blocks = Vec::new();
                normalize_node(item, document, &mut item_blocks, origin_node_idx)?;
                normalized_items.push(item_blocks);
            }

            blocks.push(NormalizedBlock::List(NormalizedList {
                items: normalized_items,
                ordered: list.ordered,
                marker_type,
                start,
                separator,
                reversed,
                style: list_style,
                color: list.color.clone(),
                origin_node_idx,
            }));
        }
        DocumentNode::Section(section) => {
            let mut inner = Vec::new();
            for child in &section.content {
                normalize_node(child, document, &mut inner, origin_node_idx)?;
            }
            blocks.push(NormalizedBlock::Section(NormalizedSection {
                blocks: inner,
                page_size: section.page_size.clone(),
                page_margins: section.page_margins.clone(),
                header: section.header.clone(),
                footer: section.footer.clone(),
                watermark: section.watermark.clone(),
                origin_node_idx,
            }));
        }
        DocumentNode::Canvas(canvas) => {
            blocks.push(NormalizedBlock::Canvas(NormalizedCanvas {
                shapes: canvas.shapes.clone(),
            }));
        }
        DocumentNode::Toc(toc) => {
            let title = if let Some(ref title_node) = toc.title {
                let mut title_blocks = Vec::new();
                normalize_node(title_node, document, &mut title_blocks, origin_node_idx)?;
                if title_blocks.len() > 0 {
                    Some(Box::new(title_blocks.into_iter().next().unwrap()))
                } else {
                    None
                }
            } else {
                None
            };

            let text_style = toc.text_style.as_ref()
                .map(|s| ResolvedStyle::from(&document.default_style).merge_style(s))
                .unwrap_or_else(|| ResolvedStyle::from(&document.default_style));

            let number_style = toc.number_style.as_ref()
                .map(|s| ResolvedStyle::from(&document.default_style).merge_style(s))
                .unwrap_or_else(|| ResolvedStyle::from(&document.default_style));

            blocks.push(NormalizedBlock::Toc(NormalizedToc {
                id: toc.id.clone(),
                title,
                text_style,
                number_style,
                origin_node_idx,
            }));
        }
    }
    Ok(())
}

static CJK_WARNING_ONCE: std::sync::Once = std::sync::Once::new();
static CJK_CHARS_FOUND: std::sync::Mutex<Vec<char>> = std::sync::Mutex::new(Vec::new());

fn warn_cjk_missing_font(chars: &[char]) {
    if let Ok(mut found) = CJK_CHARS_FOUND.lock() {
        found.extend(chars.iter().take(5));
        found.sort();
        found.dedup();
    }
    CJK_WARNING_ONCE.call_once(|| {
        let chars = CJK_CHARS_FOUND.lock().unwrap();
        let preview: String = chars.iter().take(5).map(|c| format!("'{}'", c)).collect::<Vec<_>>().join(", ");
        eprintln!(
            "[pdfmake_rust] Warning: Document contains CJK characters ({}) but no font_family is set. \
            Characters may not render correctly. \
            Use Document::builder().font(\"family\", \"path.ttf\") to register a CJK font.",
            preview
        );
    });
}

fn normalize_text(text: &TextNode, document: &Document) -> Result<NormalizedTextBlock> {
    let mut base_style = ResolvedStyle::from(&document.default_style);
    for name in &text.style_names {
        let named_style = document.styles.get(name).ok_or_else(|| PdfMakeError::UnknownStyle(name.clone()))?;
        base_style = base_style.merge_style(named_style);
    }
    base_style = base_style.merge_style(&text.style);

    let inlines = text
        .inlines
        .iter()
        .map(|fragment| {
            // Only clone and merge if fragment has custom style
            let style = if fragment.style.has_any() {
                base_style.clone().merge_style(&fragment.style)
            } else {
                base_style.clone()
            };
            if style.font_family.is_none() {
                let special_chars = crate::font::detect_special_chars(&fragment.text);
                if !special_chars.is_empty() {
                    warn_cjk_missing_font(&special_chars);
                }
            }
            NormalizedInline {
                text: fragment.text.clone(),
                style,
            }
        })
        .collect();

    Ok(NormalizedTextBlock { inlines, style: base_style, page_break: text.page_break, id: text.id.clone(), page_reference: text.page_reference.clone(), toc_item: text.toc_item.clone(), origin_node_idx: None })
}

fn normalize_table(table: &TableNode, document: &Document) -> Result<NormalizedTable> {
    let mut table_style = ResolvedStyle::from(&document.default_style);
    for name in &table.style_names {
        let named_style = document.styles.get(name).ok_or_else(|| PdfMakeError::UnknownStyle(name.clone()))?;
        table_style = table_style.merge_style(named_style);
    }
    table_style = table_style.merge_style(&table.style);

    let rows = table
        .body
        .iter()
        .map(|row| {
            row.iter()
                .map(|cell| {
                    let mut cell_style = table_style.clone().merge_style(&cell.style);
                    // Merge cell-level style overrides (cell-level takes priority)
                    if let Some(ref fc) = cell.fill_color {
                        cell_style.fill_color = Some(fc.clone());
                    }
                    if let Some(fo) = cell.fill_opacity {
                        cell_style.fill_opacity = Some(fo);
                    }
                    if let Some(ref bc) = cell.border_color {
                        cell_style.border_color = Some(bc.clone());
                    }
                    let content = match &cell.content {
                        TableCellContent::Text(inlines) => {
                            NormalizedCellContent::Inlines(inlines
                                .iter()
                                .map(|fragment| {
                                    let style = cell_style.clone().merge_style(&fragment.style);
                                    if style.font_family.is_none() {
                                        let special_chars = crate::font::detect_special_chars(&fragment.text);
                                        if !special_chars.is_empty() {
                                            warn_cjk_missing_font(&special_chars);
                                        }
                                    }
                                    NormalizedInline {
                                        text: fragment.text.clone(),
                                        style,
                                    }
                                })
                                .collect())
                        }
                        TableCellContent::Node(node) => {
                            let mut blocks = Vec::new();
                            normalize_node(node, document, &mut blocks, None)?;
                            NormalizedCellContent::Blocks(blocks)
                        }
                    };
                    Ok(NormalizedTableCell { content, style: cell_style, padding: cell.padding, col_span: cell.col_span.max(1), row_span: cell.row_span.max(1), vertical_alignment: cell.vertical_alignment, border: cell.border.clone() })
                })
                .collect::<Result<Vec<_>>>()
        })
        .collect::<Result<Vec<Vec<_>>>>()?;

    Ok(NormalizedTable { rows, widths: table.widths.clone(), heights: table.heights.clone(), style: table_style, header_rows: table.header_rows, dont_break_rows: table.dont_break_rows, page_break: table.page_break, keep_with_header_rows: table.keep_with_header_rows, table_style: table.table_style, layout_config: table.layout_config.clone(), h_line_when_broken: table.h_line_when_broken, origin_node_idx: None })
}