use pdfmake_rust::{
    AbsoluteNode, Alignment, ColumnsNode, Document, DocumentNode, FontRegistry, InlineFragment, Margins, PageSize, Style,
    TableCell, TableCellContent, TableLayoutConfig, TableNode, TableStyle, TableWidth, TextNode, VerticalAlignment,
};
use pdfmake_rust::{measure_document, normalize_document, MeasuredBlock, NormalizedBlock, NormalizedCellContent};
use pdfmake_rust::{layout_document, LayoutItem};
use pdfmake_rust::PdfMake;

#[test]
fn table_node_stores_rows_and_widths() {
    let table = TableNode::new(vec![
        vec![TableCell::text("A1"), TableCell::text("B1")],
        vec![TableCell::text("A2"), TableCell::text("B2")],
    ])
    .widths(vec![TableWidth::Fixed(80.0), TableWidth::Star]);

    assert_eq!(table.body.len(), 2);
    assert_eq!(table.body[0].len(), 2);
    assert_eq!(table.widths, vec![TableWidth::Fixed(80.0), TableWidth::Star]);
}

#[test]
fn table_cell_accepts_inline_fragments_and_style() {
    let cell = TableCell::with_inlines(vec![
        InlineFragment::new("plain "),
        InlineFragment::new("bold").style(Style::new().bold(true)),
    ])
    .padding(6.0);

    match &cell.content {
        TableCellContent::Text(inlines) => {
            assert_eq!(inlines.len(), 2);
            assert_eq!(inlines[1].style.bold, Some(true));
        }
        _ => panic!("expected text content"),
    }
    assert_eq!(cell.padding, 6.0);
}

#[test]
fn document_accepts_table_node() {
    let document = Document::builder()
        .content(DocumentNode::Table(TableNode::new(vec![vec![TableCell::text("A")]])))
        .content(DocumentNode::Text(TextNode::new("after")))
        .build();

    assert_eq!(document.content.len(), 2);
}

#[test]
fn normalize_table_resolves_cell_styles() {
    let document = Document::builder()
        .default_style(Style::default().font_size(10.0))
        .content(DocumentNode::Table(TableNode::new(vec![vec![
            TableCell::text("bold").style(Style::new().bold(true).font_size(16.0)),
        ]])))
        .build();

    let normalized = normalize_document(&document).unwrap();

    match &normalized.blocks[0] {
        NormalizedBlock::Table(table) => {
            assert_eq!(table.rows.len(), 1);
            match &table.rows[0][0].content {
                NormalizedCellContent::Inlines(inlines) => {
                    assert_eq!(inlines[0].style.font_size, 16.0);
                    assert_eq!(inlines[0].style.bold, true);
                }
                _ => panic!("expected inlines content"),
            }
            assert_eq!(table.rows[0][0].padding, 4.0);
        }
        NormalizedBlock::Text(_) => panic!("expected table block"),
        _ => panic!("expected table block"),
    }
}

#[test]
fn measure_table_assigns_fixed_and_star_widths() {
    let document = Document::builder()
        .page_size(pdfmake_rust::PageSize::new(300.0, 200.0))
        .page_margins(pdfmake_rust::Margins::all(20.0))
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![TableCell::text("A"), TableCell::text("B")]])
                .widths(vec![TableWidth::Fixed(80.0), TableWidth::Star]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    match &measured.blocks[0] {
        MeasuredBlock::Table(table) => {
            assert_eq!(table.column_widths.len(), 2);
            assert_eq!(table.column_widths[0], 80.0);
            assert_eq!(table.column_widths[1], 180.0);
        }
        MeasuredBlock::Text(_) => panic!("expected table block"),
        _ => panic!("expected table block"),
    }
}

#[test]
fn measure_table_wraps_cell_text_inside_column_width() {
    let document = Document::builder()
        .page_size(pdfmake_rust::PageSize::new(220.0, 200.0))
        .page_margins(pdfmake_rust::Margins::all(20.0))
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![TableCell::text("one two three four five six")]])
                .widths(vec![TableWidth::Fixed(80.0)]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    match &measured.blocks[0] {
        MeasuredBlock::Table(table) => {
            assert!(table.rows[0].height > 12.0);
            assert!(table.rows[0].cells[0].lines.len() > 1);
        }
        MeasuredBlock::Text(_) => panic!("expected table block"),
        _ => panic!("expected table block"),
    }
}

#[test]
fn layout_table_produces_cell_rectangles_and_borders() {
    let document = Document::builder()
        .page_size(pdfmake_rust::PageSize::new(300.0, 200.0))
        .page_margins(pdfmake_rust::Margins::all(20.0))
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![TableCell::text("A"), TableCell::text("B")]])
                .widths(vec![TableWidth::Fixed(80.0), TableWidth::Star]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    match &layout.pages[0].items[0] {
        LayoutItem::Table(table) => {
            assert_eq!(table.rows.len(), 1);
            assert_eq!(table.rows[0].cells.len(), 2);
            assert_eq!(table.rows[0].cells[0].x, 20.0);
            assert_eq!(table.rows[0].cells[0].width, 80.0);
            assert!(!table.borders.is_empty());
        }
        LayoutItem::Text(_) => panic!("expected table layout item"),
        _ => panic!("expected table layout item"),
    }
}

#[test]
fn layout_table_moves_whole_rows_to_next_page() {
    let document = Document::builder()
        .page_size(pdfmake_rust::PageSize::new(220.0, 200.0))
        .page_margins(pdfmake_rust::Margins::all(20.0))
        .content(DocumentNode::Table(
            TableNode::new(vec![
                vec![TableCell::text("row one wraps a lot of content")],
                vec![TableCell::text("row two wraps a lot of content")],
            ])
            .widths(vec![TableWidth::Fixed(100.0)]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    let total_rows: usize = layout.pages[0]
        .items
        .iter()
        .filter_map(|item| match item {
            LayoutItem::Table(t) => Some(t.rows.len()),
            _ => None,
        })
        .sum();

    assert_eq!(total_rows, 2);
}

#[test]
fn render_table_outputs_cell_text_and_border_lines() {
    let document = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![TableCell::text("A"), TableCell::text("B")]])
                .widths(vec![TableWidth::Fixed(80.0), TableWidth::Fixed(80.0)]),
        ))
        .build();

    let bytes = PdfMake::new().render(&document).unwrap();
    let pdf = String::from_utf8_lossy(&bytes);

    assert!(pdf.contains("/Filter /FlateDecode"));
    assert!(pdf.contains("stream\n"));
    assert!(pdf.contains("endstream"));
    assert!(pdf.contains("/F1"));
}

#[test]
fn render_table_preserves_bold_cell_runs() {
    let document = Document::builder()
        .content(DocumentNode::Table(TableNode::new(vec![vec![
            TableCell::with_inlines(vec![
                InlineFragment::new("plain "),
                InlineFragment::new("bold").style(Style::new().bold(true)),
            ]),
        ]])))
        .build();

    let bytes = PdfMake::new().render(&document).unwrap();
    let pdf = String::from_utf8_lossy(&bytes);

    assert!(pdf.contains("/Filter /FlateDecode"));
    assert!(pdf.contains("stream\n"));
    assert!(pdf.contains("endstream"));
    assert!(pdf.contains("/F1"));
    assert!(pdf.contains("/F2"));
}

#[test]
fn table_end_to_end_supports_wrapping_borders_and_inline_styles() {
    let document = Document::builder()
        .page_size(pdfmake_rust::PageSize::new(260.0, 180.0))
        .page_margins(pdfmake_rust::Margins::all(20.0))
        .content(DocumentNode::Table(
            TableNode::new(vec![
                vec![
                    TableCell::with_inlines(vec![
                        InlineFragment::new("plain "),
                        InlineFragment::new("bold").style(Style::new().bold(true)),
                    ]),
                    TableCell::text("one two three four five six"),
                ],
                vec![TableCell::text("A2"), TableCell::text("B2")],
            ])
            .widths(vec![TableWidth::Fixed(80.0), TableWidth::Star]),
        ))
        .build();

    let bytes = PdfMake::new().render(&document).unwrap();
    let pdf = String::from_utf8_lossy(&bytes);

    assert!(pdf.starts_with("%PDF-1.4"));
    assert!(pdf.contains("/Filter /FlateDecode"));
    assert!(pdf.contains("stream\n"));
    assert!(pdf.contains("endstream"));
    assert!(pdf.contains("/F1"));
    assert!(pdf.contains("/F2"));
    assert!(pdf.contains("%%EOF"));
}

#[test]
fn layout_table_cell_respects_center_and_right_alignment() {
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![vec![
                TableCell::text("A").style(Style::new().alignment(Alignment::Center)),
                TableCell::text("B").style(Style::new().alignment(Alignment::Right)),
            ]])
            .widths(vec![TableWidth::Fixed(100.0), TableWidth::Fixed(100.0)]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    let table = match &layout.pages[0].items[0] {
        LayoutItem::Table(table) => table,
        other => panic!("expected table, got {other:?}"),
    };

    let center_cell = &table.rows[0].cells[0];
    let right_cell = &table.rows[0].cells[1];
    let center_run = &center_cell.runs[0];
    let right_run = &right_cell.runs[0];

    assert!(center_run.x_offset > center_cell.x + center_cell.padding);
    assert!(right_run.x_offset > right_cell.x + right_cell.padding);
}

#[test]
fn measure_table_auto_width_uses_content_width_before_star_columns() {
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![vec![
                TableCell::text("WWWW"),
                TableCell::text("star"),
            ]])
            .widths(vec![TableWidth::Auto, TableWidth::Star]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    let table = match &measured.blocks[0] {
        MeasuredBlock::Table(table) => table,
        other => panic!("expected table, got {other:?}"),
    };

    let auto_width = table.rows[0].cells[0].width;
    let star_width = table.rows[0].cells[1].width;

    assert!(auto_width > 20.0);
    assert!(auto_width < star_width);
}

#[test]
fn table_cell_accepts_col_span() {
    let cell = TableCell::text("wide").col_span(2);
    assert_eq!(cell.col_span, 2);
}

#[test]
fn normalize_table_preserves_col_span() {
    let document = Document::builder()
        .content(DocumentNode::table(TableNode::new(vec![vec![
            TableCell::text("wide").col_span(2),
            TableCell::text("tail"),
        ]])))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let table = match &normalized.blocks[0] {
        NormalizedBlock::Table(table) => table,
        other => panic!("expected table, got {other:?}"),
    };

    assert_eq!(table.rows[0][0].col_span, 2);
    assert_eq!(table.rows[0][1].col_span, 1);
}

#[test]
fn measure_table_col_span_cell_uses_sum_of_covered_columns() {
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![vec![
                TableCell::text("wide").col_span(2),
                TableCell::text("tail"),
            ]])
            .widths(vec![TableWidth::Fixed(50.0), TableWidth::Fixed(60.0), TableWidth::Fixed(70.0)]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    let table = match &measured.blocks[0] {
        MeasuredBlock::Table(table) => table,
        other => panic!("expected table, got {other:?}"),
    };

    assert_eq!(table.rows[0].cells[0].width, 110.0);
    assert_eq!(table.rows[0].cells[0].col_span, 2);
    assert_eq!(table.rows[0].cells[1].width, 70.0);
}

#[test]
fn layout_table_col_span_cell_draws_merged_rectangle() {
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![vec![
                TableCell::text("wide").col_span(2),
                TableCell::text("tail"),
            ]])
            .widths(vec![
                TableWidth::Fixed(50.0),
                TableWidth::Fixed(60.0),
                TableWidth::Fixed(70.0),
            ]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    let table = match &layout.pages[0].items[0] {
        LayoutItem::Table(table) => table,
        other => panic!("expected table, got {other:?}"),
    };

    assert_eq!(table.rows[0].cells[0].width, 110.0);
    assert_eq!(table.rows[0].cells[0].col_span, 2);
    assert_eq!(table.rows[0].cells[1].x, table.rows[0].cells[0].x + 110.0);
}

#[test]
fn render_table_with_col_span_produces_pdf() {
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![vec![
                TableCell::text("wide").col_span(2),
                TableCell::text("tail"),
            ]])
            .widths(vec![
                TableWidth::Fixed(50.0),
                TableWidth::Fixed(60.0),
                TableWidth::Fixed(70.0),
            ]),
        ))
        .build();

    let pdf = PdfMake::new().render(&document).unwrap();
    let text = String::from_utf8_lossy(&pdf);

    assert!(text.starts_with("%PDF-1.4"));
    assert!(text.contains("/Filter /FlateDecode"));
    assert!(text.contains("xref"));
}

#[test]
fn measure_table_fixed_and_star_widths_remain_stable_after_auto_changes() {
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![vec![TableCell::text("A"), TableCell::text("B")]])
                .widths(vec![TableWidth::Fixed(100.0), TableWidth::Star]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let table = match &measured.blocks[0] {
        MeasuredBlock::Table(table) => table,
        other => panic!("expected table, got {other:?}"),
    };

    assert_eq!(table.rows[0].cells[0].width, 100.0);
    assert!(table.rows[0].cells[1].width > 100.0);
}

#[test]
fn measure_table_col_span_inflates_column_count_and_defaults_extra_to_star() {
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![vec![TableCell::text("wide").col_span(99)]])
                .widths(vec![TableWidth::Fixed(50.0), TableWidth::Fixed(60.0)]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let table = match &measured.blocks[0] {
        MeasuredBlock::Table(table) => table,
        other => panic!("expected table, got {other:?}"),
    };

    assert!(table.rows[0].cells[0].width > 110.0);
}

#[test]
fn table_cell_accepts_row_span() {
    let cell = TableCell::text("tall").row_span(3);
    assert_eq!(cell.row_span, 3);
}

#[test]
fn normalize_table_preserves_row_span() {
    let document = Document::builder()
        .content(DocumentNode::table(TableNode::new(vec![
            vec![TableCell::text("tall").row_span(2), TableCell::text("B")],
            vec![TableCell::text("D")],
        ])))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let table = match &normalized.blocks[0] {
        NormalizedBlock::Table(table) => table,
        other => panic!("expected table, got {other:?}"),
    };

    assert_eq!(table.rows[0][0].row_span, 2);
    assert_eq!(table.rows[0][1].row_span, 1);
}

#[test]
fn measure_table_row_span_cell_has_height_across_rows() {
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![
                vec![TableCell::text("tall tall tall").row_span(2), TableCell::text("B")],
                vec![TableCell::text("D")],
            ])
            .widths(vec![TableWidth::Fixed(50.0), TableWidth::Fixed(50.0)]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    let table = match &measured.blocks[0] {
        MeasuredBlock::Table(table) => table,
        other => panic!("expected table, got {other:?}"),
    };

    assert_eq!(table.rows[0].cells[0].row_span, 2);
    assert_eq!(table.rows[0].cells.len(), 2);
    assert!(table.height > 0.0);
}

#[test]
fn layout_table_row_span_draws_merged_rectangle_and_skips_covered_position() {
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![
                vec![TableCell::text("tall").row_span(2), TableCell::text("B")],
                vec![TableCell::text("D")],
            ])
            .widths(vec![TableWidth::Fixed(50.0), TableWidth::Fixed(50.0)]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    let table = match &layout.pages[0].items[0] {
        LayoutItem::Table(table) => table,
        other => panic!("expected table, got {other:?}"),
    };

    assert!(table.rows.len() >= 2);
    let row0_col0 = &table.rows[0].cells[0];
    assert_eq!(row0_col0.row_span, 2);
    assert!(row0_col0.height > table.rows[0].height);
}

#[test]
fn layout_table_consumes_placeholder_cells_under_spans() {
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![
                vec![
                    TableCell::text("header").col_span(2),
                    TableCell::text(""),
                    TableCell::text("h3"),
                ],
                vec![
                    TableCell::text("r1c1").row_span(2),
                    TableCell::text("r1c2"),
                    TableCell::text("r1c3"),
                ],
                vec![
                    TableCell::text(""),
                    TableCell::text("r2c2"),
                    TableCell::text("r2c3"),
                ],
                vec![
                    TableCell::text("r3c1"),
                    TableCell::text("r3c2"),
                    TableCell::text("r3c3"),
                ],
            ])
            .widths(vec![
                TableWidth::Fixed(40.0),
                TableWidth::Fixed(50.0),
                TableWidth::Fixed(60.0),
            ]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    let tables = layout.pages[0]
        .items
        .iter()
        .filter_map(|item| match item {
            LayoutItem::Table(table) => Some(table),
            _ => None,
        })
        .collect::<Vec<_>>();

    assert_eq!(tables.len(), 3);
    let header = tables[0];
    let row_span_group = tables[1];
    let last_row = tables[2];

    assert_eq!(header.rows[0].cells.len(), 2);
    assert_eq!(row_span_group.rows[0].cells.len(), 3);
    assert_eq!(row_span_group.rows[1].cells.len(), 2);
    assert_eq!(last_row.rows[0].cells.len(), 3);

    assert_eq!(header.rows[0].cells[0].width, 90.0);
    assert_eq!(header.rows[0].cells[1].x, header.rows[0].cells[0].x + 90.0);
    assert_eq!(row_span_group.rows[1].cells[0].x, row_span_group.rows[0].cells[1].x);
    assert_eq!(row_span_group.rows[1].cells[1].x, row_span_group.rows[0].cells[2].x);
    assert_eq!(last_row.rows[0].cells[1].x, row_span_group.rows[0].cells[1].x);
    assert_eq!(last_row.rows[0].cells[2].x, row_span_group.rows[0].cells[2].x);
    assert!(row_span_group.rows[1].cells.iter().all(|cell| !cell.borders.is_empty()));
    assert!(last_row.rows[0].cells.iter().all(|cell| !cell.borders.is_empty()));
}

#[test]
fn layout_table_row_span_group_stays_on_same_page() {
    let document = Document::builder()
        .page_size(PageSize { width: 200.0, height: 45.0, auto_height: false })
        .page_margins(Margins { top: 0.0, bottom: 0.0, left: 0.0, right: 0.0 })
        .content(DocumentNode::table(
            TableNode::new(vec![
                vec![TableCell::text("row0").row_span(2), TableCell::text("B")],
                vec![TableCell::text("D")],
            ])
            .widths(vec![TableWidth::Fixed(50.0), TableWidth::Fixed(50.0)]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    assert!(!layout.pages.is_empty());
}

#[test]
fn render_table_with_row_span_produces_pdf() {
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![
                vec![TableCell::text("tall").row_span(2), TableCell::text("B")],
                vec![TableCell::text("D")],
            ])
            .widths(vec![TableWidth::Fixed(50.0), TableWidth::Fixed(50.0)]),
        ))
        .build();

    let pdf = PdfMake::new().render(&document).unwrap();
    let text = String::from_utf8_lossy(&pdf);

    assert!(text.starts_with("%PDF-1.4"));
    assert!(text.contains("/Filter /FlateDecode"));
    assert!(text.contains("xref"));
}

#[test]
fn measure_table_row_span_is_clamped_to_available_rows() {
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![vec![TableCell::text("tall").row_span(99)]])
                .widths(vec![TableWidth::Fixed(50.0)]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    let table = match &measured.blocks[0] {
        MeasuredBlock::Table(table) => table,
        other => panic!("expected table, got {other:?}"),
    };

    assert_eq!(table.rows.len(), 1);
    assert_eq!(table.rows[0].cells[0].row_span, 99);
}

#[test]
fn measure_table_row_span_default_is_one() {
    let cell = TableCell::text("normal");
    assert_eq!(cell.row_span, 1);
    assert_eq!(cell.col_span, 1);
}

/// 验证 rowSpan 单元格内容超过所跨行高度时，额外高度按比例分配到各行
/// 对应 pdfmake JS issue #2895
#[test]
fn measure_table_row_span_extra_height_distributed_across_rows() {
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![
                vec![
                    // rowSpan=2 且内容很多，会撑高行
                    TableCell::text("A B C D E F G H I J K L M N O P Q R S T U V W X Y Z").row_span(2),
                    TableCell::text("B"),
                ],
                vec![TableCell::text("D")],
            ])
            .widths(vec![TableWidth::Fixed(30.0), TableWidth::Fixed(100.0)]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    let table = match &measured.blocks[0] {
        MeasuredBlock::Table(table) => table,
        other => panic!("expected table, got {other:?}"),
    };

    // row_span=2, 两行都应该被撑高（而非仅最后一行）
    assert_eq!(table.rows[0].cells[0].row_span, 2);
    // 窄列下长文本会换行，两行高度都应大于默认�?
    assert!(table.rows[0].height > 12.0, "row 0 should be taller than default");
    assert!(table.rows[1].height > 12.0, "row 1 should be taller than default");
    // 第一行不应比�?rowSpan 格撑高的第二行小太多
    let ratio = table.rows[0].height / table.rows[1].height;
    assert!(ratio > 0.3, "extra height should be distributed, not all on last row (ratio={ratio:.2})");
}

/// rowSpan group 高度超过一整页时不应崩�?
#[test]
fn layout_table_row_span_group_taller_than_page_does_not_crash() {
    // 构造大量文本使 rowSpan 单元格内容远超一页高度
    let long_text = "line\n".repeat(100);
    let document = Document::builder()
        .page_size(PageSize { width: 200.0, height: 100.0, auto_height: false })
        .page_margins(Margins { top: 0.0, bottom: 0.0, left: 0.0, right: 0.0 })
        .content(DocumentNode::table(
            TableNode::new(vec![
                vec![TableCell::text("row0").row_span(2), TableCell::text("B")],
                vec![TableCell::text("D")],
            ])
            .widths(vec![TableWidth::Fixed(50.0), TableWidth::Fixed(50.0)]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    // 不应崩溃，至少渲染了一�?table
    assert!(!layout.pages.is_empty());
    let has_table = layout.pages.iter().any(|p| {
        p.items.iter().any(|item| matches!(item, LayoutItem::Table(_)))
    });
    assert!(has_table, "should render at least one table even if it overflows page");
}

#[test]
fn layout_table_without_row_span_produces_correct_multi_row_output() {
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![
                vec![TableCell::text("A"), TableCell::text("B")],
                vec![TableCell::text("C"), TableCell::text("D")],
            ])
            .widths(vec![TableWidth::Fixed(50.0), TableWidth::Fixed(50.0)]),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    let total_rows: usize = layout.pages[0]
        .items
        .iter()
        .filter_map(|item| match item {
            LayoutItem::Table(t) => Some(t.rows.len()),
            _ => None,
        })
        .sum();

    assert_eq!(total_rows, 2);

    let first_table = match &layout.pages[0].items[0] {
        LayoutItem::Table(table) => table,
        other => panic!("expected table, got {other:?}"),
    };
    assert!(first_table.width > 0.0);
    assert!(first_table.height > 0.0);
}

#[test]
fn columns_node_stores_multiple_columns() {
    let node = ColumnsNode::new(vec![
        vec![DocumentNode::text("left")],
        vec![DocumentNode::text("right")],
    ]);
    assert_eq!(node.columns.len(), 2);
    assert_eq!(node.column_gap, 10.0);
}

#[test]
fn normalize_columns_produces_separate_block_lists() {
    let document = Document::builder()
        .content(DocumentNode::columns(ColumnsNode::new(vec![
            vec![DocumentNode::text("left")],
            vec![DocumentNode::text("right")],
        ])))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let columns = match &normalized.blocks[0] {
        NormalizedBlock::Columns(cols) => cols,
        other => panic!("expected columns, got {other:?}"),
    };

    assert_eq!(columns.columns.len(), 2);
    assert_eq!(columns.columns[0].len(), 1);
    assert_eq!(columns.columns[1].len(), 1);
}

#[test]
fn measure_columns_computes_width_and_height() {
    let document = Document::builder()
        .content(DocumentNode::columns(ColumnsNode::new(vec![
            vec![DocumentNode::text("left")],
            vec![DocumentNode::text("right column longer")],
        ])))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    let columns = match &measured.blocks[0] {
        MeasuredBlock::Columns(cols) => cols,
        other => panic!("expected columns, got {other:?}"),
    };

    assert_eq!(columns.columns.len(), 2);
    assert!(columns.width > 0.0);
    assert!(columns.height > 0.0);
}

#[test]
fn layout_columns_places_items_side_by_side() {
    let document = Document::builder()
        .content(DocumentNode::columns(ColumnsNode::new(vec![
            vec![DocumentNode::text("left")],
            vec![DocumentNode::text("right")],
        ])))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    let columns = match &layout.pages[0].items[0] {
        LayoutItem::Columns(cols) => cols,
        other => panic!("expected columns, got {other:?}"),
    };

    assert_eq!(columns.columns.len(), 2);
    assert!(!columns.columns[0].is_empty());
    assert!(!columns.columns[1].is_empty());
}

#[test]
fn render_columns_produces_pdf() {
    let document = Document::builder()
        .content(DocumentNode::columns(ColumnsNode::new(vec![
            vec![DocumentNode::text("left")],
            vec![DocumentNode::text("right")],
        ])))
        .build();

    let pdf = PdfMake::new().render(&document).unwrap();
    let text = String::from_utf8_lossy(&pdf);

    assert!(text.starts_with("%PDF-1.4"));
    assert!(text.contains("/Filter /FlateDecode"));
}

#[test]
fn absolute_node_wraps_inner_content_with_coordinates() {
    let node = AbsoluteNode::new(DocumentNode::text("hello"))
        .left(100.0)
        .top(200.0);
    assert_eq!(node.left, Some(100.0));
    assert_eq!(node.top, Some(200.0));
    assert!(node.right.is_none());
}

#[test]
fn normalize_absolute_preserves_coordinates() {
    let document = Document::builder()
        .content(DocumentNode::absolute(
            AbsoluteNode::new(DocumentNode::text("hello")).left(50.0).top(60.0),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let abs = match &normalized.blocks[0] {
        NormalizedBlock::Absolute(abs) => abs,
        other => panic!("expected absolute, got {other:?}"),
    };

    assert_eq!(abs.left, Some(50.0));
    assert_eq!(abs.top, Some(60.0));
    assert_eq!(abs.center_horizontal, false);
    assert_eq!(abs.center_vertical, false);
    assert!(!abs.blocks.is_empty());
}

#[test]
fn measure_absolute_computes_dimensions() {
    let document = Document::builder()
        .content(DocumentNode::absolute(
            AbsoluteNode::new(DocumentNode::text("hello")).left(100.0).top(200.0),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    let abs = match &measured.blocks[0] {
        MeasuredBlock::Absolute(abs) => abs,
        other => panic!("expected absolute, got {other:?}"),
    };

    assert_eq!(abs.left, Some(100.0));
    assert_eq!(abs.top, Some(200.0));
    assert!(abs.height > 0.0);
}

#[test]
fn layout_absolute_places_content_at_exact_coordinates() {
    let document = Document::builder()
        .content(DocumentNode::stack(vec![
            DocumentNode::text("before"),
            DocumentNode::absolute(
                AbsoluteNode::new(DocumentNode::text("absolute")).left(300.0).top(100.0),
            ),
            DocumentNode::text("after"),
        ]))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    let absolute_found = layout.pages[0].items.iter().any(|item| {
        if let LayoutItem::Text(text) = item {
            text.x > 250.0
        } else {
            false
        }
    });
    assert!(absolute_found, "Should find absolute text at x > 250");
}

#[test]
fn render_absolute_position_produces_pdf() {
    let document = Document::builder()
        .content(DocumentNode::stack(vec![
            DocumentNode::text("main content"),
            DocumentNode::absolute(
                AbsoluteNode::new(DocumentNode::text("watermark")).left(50.0).top(700.0),
            ),
        ]))
        .build();

    let pdf = PdfMake::new().render(&document).unwrap();
    let text = String::from_utf8_lossy(&pdf);

    assert!(text.starts_with("%PDF-1.4"));
    assert!(text.contains("xref"));
}

#[test]
fn table_cell_fill_color_renders_background_rect() {
    let document = Document::builder()
        .content(DocumentNode::table(TableNode::new(vec![
            vec![
                TableCell::text("Header").style(Style::new().fill_color("#333333").color("#FFFFFF")),
                TableCell::text("Column 2"),
            ],
            vec![TableCell::text("Data 1"), TableCell::text("Data 2")],
        ])))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    let mut found_fill = false;
    let mut found_no_fill = false;
    for page in &layout.pages {
        for item in &page.items {
            if let LayoutItem::Table(table) = item {
                for row in &table.rows {
                    for cell in &row.cells {
                        if cell.fill_color == Some("#333333".to_string()) {
                            found_fill = true;
                        }
                        if cell.fill_color.is_none() {
                            found_no_fill = true;
                        }
                    }
                }
            }
        }
    }
    assert!(found_fill, "Expected to find a cell with fill_color #333333");
    assert!(found_no_fill, "Expected to find a cell without fill_color");

    let pdf = PdfMake::new().render(&document).unwrap();
    assert!(pdf.starts_with(b"%PDF-1.4"));
}

#[test]
fn table_cell_fill_color_render_order_is_correct() {
    let document = Document::builder()
        .content(DocumentNode::table(TableNode::new(vec![
            vec![TableCell::text("A").style(Style::new().fill_color("#FF0000"))],
        ])))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    let table = match &layout.pages[0].items[0] {
        LayoutItem::Table(table) => table,
        other => panic!("expected table, got {other:?}"),
    };

    assert_eq!(table.rows[0].cells[0].fill_color, Some("#FF0000".to_string()));

    let pdf = PdfMake::new().render(&document).unwrap();
    assert!(pdf.starts_with(b"%PDF-1.4"));
    assert!(String::from_utf8_lossy(&pdf).contains("xref"));
}

#[test]
fn row_span_cross_page_has_borders_on_both_pages() {
    let rows: Vec<Vec<TableCell>> = (0..50)
        .map(|i| {
            if i == 0 {
                vec![
                    TableCell::text("Spanning cell that crosses page boundary with lots of content to make it tall")
                        .row_span(10),
                    TableCell::text(format!("Row {i} data")),
                ]
            } else if i < 10 {
                vec![TableCell::text(format!("Row {i} data"))]
            } else {
                vec![
                    TableCell::text("Regular"),
                    TableCell::text(format!("Row {i} data")),
                ]
            }
        })
        .collect();

    let document = Document::builder()
        .content(DocumentNode::table(TableNode::new(rows).widths(vec![
            TableWidth::Fixed(100.0),
            TableWidth::Star,
        ])))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    assert!(layout.pages.len() >= 2);

    for page in &layout.pages {
        for item in &page.items {
            if let LayoutItem::Table(table) = item {
                assert!(!table.rows.is_empty());
                for row in &table.rows {
                    for cell in &row.cells {
                        assert!(!cell.borders.is_empty(), "cell should have borders");
                    }
                }
            }
        }
    }
}

#[test]
fn row_span_content_not_lost() {
    let rows: Vec<Vec<TableCell>> = (0..50)
        .map(|i| {
            if i == 0 {
                vec![
                    TableCell::text("ROWSPAN_SPAN").row_span(10),
                    TableCell::text(format!("ROW_{i}")),
                ]
            } else if i < 10 {
                vec![TableCell::text(format!("ROW_{i}"))]
            } else {
                vec![
                    TableCell::text("REG"),
                    TableCell::text(format!("ROW_{i}")),
                ]
            }
        })
        .collect();

    let document = Document::builder()
        .content(DocumentNode::table(TableNode::new(rows).widths(vec![
            TableWidth::Fixed(100.0),
            TableWidth::Star,
        ])))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    assert!(layout.pages.len() >= 2);

    let mut found_spanning_text = false;
    for page in &layout.pages {
        for item in &page.items {
            if let LayoutItem::Table(table) = item {
                for row in &table.rows {
                    for cell in &row.cells {
                        for run in &cell.runs {
                            if run.text.contains("ROWSPAN") {
                                found_spanning_text = true;
                            }
                        }
                        // Also check items (for cells that use item-based layout)
                        for item in &cell.items {
                            match item {
                                LayoutItem::Text(t) => {
                                    for run in &t.runs {
                                        if run.text.contains("ROWSPAN") {
                                            found_spanning_text = true;
                                        }
                                    }
                                }
                                _ => {}
                            }
                        }
                    }
                }
            }
        }
    }
    assert!(found_spanning_text, "rowSpan cell text should be present in layout");

    let pdf = PdfMake::new().render(&document).unwrap();
    let text = String::from_utf8_lossy(&pdf);
    assert!(text.starts_with("%PDF-1.4"));
    assert!(text.contains("%%EOF"));
    assert!(text.contains("/Filter /FlateDecode"));
}

#[test]
fn table_accepts_keep_with_header_rows() {
    let table = TableNode::new(vec![
        vec![TableCell::text("Header")],
        vec![TableCell::text("Row 1")],
        vec![TableCell::text("Row 2")],
    ])
    .header_rows(1)
    .keep_with_header_rows(2);
    assert_eq!(table.keep_with_header_rows, Some(2));
}

#[test]
fn keep_with_header_rows_default_is_none() {
    let table = TableNode::new(vec![
        vec![TableCell::text("A")],
    ]);
    assert_eq!(table.keep_with_header_rows, None);
}

#[test]
fn keep_with_header_rows_normalizes_and_measures() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![
                vec![TableCell::text("H1"), TableCell::text("H2")],
                vec![TableCell::text("R1C1"), TableCell::text("R1C2")],
                vec![TableCell::text("R2C1"), TableCell::text("R2C2")],
            ])
            .header_rows(1)
            .keep_with_header_rows(2),
        ))
        .build();
    let normalized = pdfmake_rust::normalize_document(&doc).unwrap();
    match &normalized.blocks[0] {
        pdfmake_rust::NormalizedBlock::Table(t) => {
            assert_eq!(t.keep_with_header_rows, Some(2));
        }
        _ => panic!("expected table"),
    }
    let fonts = pdfmake_rust::FontRegistry::empty();
    let measured = pdfmake_rust::measure_document(&normalized, &fonts).unwrap();
    match &measured.blocks[0] {
        pdfmake_rust::MeasuredBlock::Table(t) => {
            assert_eq!(t.keep_with_header_rows, Some(2));
        }
        _ => panic!("expected table"),
    }
}

#[test]
fn col_span_row_span_cross_page() {
    let rows: Vec<Vec<TableCell>> = (0..50)
        .map(|i| {
            if i == 0 {
                vec![
                    TableCell::text("Merged").col_span(2).row_span(10),
                    TableCell::text(format!("R{i}")),
                ]
            } else if i < 10 {
                vec![TableCell::text(format!("R{i}"))]
            } else {
                vec![
                    TableCell::text("A"),
                    TableCell::text("B"),
                    TableCell::text(format!("R{i}")),
                ]
            }
        })
        .collect();

    let document = Document::builder()
        .content(DocumentNode::table(TableNode::new(rows).widths(vec![
            TableWidth::Fixed(80.0),
            TableWidth::Fixed(80.0),
            TableWidth::Star,
        ])))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    assert!(layout.pages.len() >= 2);

    let first_table = layout.pages[0]
        .items
        .iter()
        .find_map(|item| match item {
            LayoutItem::Table(t) => Some(t),
            _ => None,
        })
        .expect("page 1 should have a table");

    let merged = &first_table.rows[0].cells[0];
    assert!(merged.col_span >= 2);
    assert!(merged.row_span >= 2);

    for page in &layout.pages {
        let has_table = page.items.iter().any(|item| matches!(item, LayoutItem::Table(_)));
        assert!(has_table, "each page should have table content");
    }
}

#[test]
fn table_header_rows_repeated_on_second_page() {
    let mut rows = vec![
        vec![TableCell::text("HEADER").style(Style::new().bold(true))],
    ];
    for i in 0..50 {
        rows.push(vec![TableCell::text(format!("Row {i}"))]);
    }
    let document = Document::builder()
        .content(DocumentNode::table(TableNode::new(rows).header_rows(1)))
        .build();
    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    assert!(layout.pages.len() >= 2, "Should have at least 2 pages");

    for page in &layout.pages {
        let first_table = page.items.iter().find_map(|item| match item {
            LayoutItem::Table(t) => Some(t),
            _ => None,
        });
        if let Some(table) = first_table {
            if let Some(first_cell) = table.rows.first().and_then(|r| r.cells.first()) {
                let has_header = first_cell.runs.iter().any(|r| r.text.contains("HEADER"));
                assert!(has_header, "Each page's first table should start with HEADER row");
            }
        }
    }
}

#[test]
fn table_header_rows_none_does_not_repeat() {
    let mut rows = vec![
        vec![TableCell::text("HEADER")],
    ];
    for i in 0..50 {
        rows.push(vec![TableCell::text(format!("Row {i}"))]);
    }
    let document = Document::builder()
        .content(DocumentNode::table(TableNode::new(rows)))
        .build();
    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    assert!(layout.pages.len() >= 2);

    let mut header_count = 0;
    for page in &layout.pages {
        for item in &page.items {
            if let LayoutItem::Table(table) = item {
                if let Some(first_cell) = table.rows.first().and_then(|r| r.cells.first()) {
                    if first_cell.runs.iter().any(|r| r.text.contains("HEADER")) {
                        header_count += 1;
                    }
                }
            }
        }
    }
    assert_eq!(header_count, 1, "Without header_rows, HEADER should appear only once");
}

#[test]
fn table_dont_break_rows_flag_threaded_through_normalize() {
    let document = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![TableCell::text("A")]]).dont_break_rows(),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    match &normalized.blocks[0] {
        NormalizedBlock::Table(table) => {
            assert!(table.dont_break_rows, "dont_break_rows should be true after normalize");
        }
        _ => panic!("expected table block"),
    }
}

#[test]
fn table_dont_break_rows_flag_threaded_through_measure() {
    let document = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![TableCell::text("A")]]).dont_break_rows(),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    match &measured.blocks[0] {
        MeasuredBlock::Table(table) => {
            assert!(table.dont_break_rows, "dont_break_rows should be true after measure");
        }
        _ => panic!("expected table block"),
    }
}

#[test]
fn table_dont_break_rows_defaults_to_false() {
    let document = Document::builder()
        .content(DocumentNode::Table(TableNode::new(vec![vec![TableCell::text("A")]])))
        .build();

    let normalized = normalize_document(&document).unwrap();
    match &normalized.blocks[0] {
        NormalizedBlock::Table(table) => {
            assert!(!table.dont_break_rows);
        }
        _ => panic!("expected table block"),
    }
}

#[test]
fn table_dont_break_rows_keeps_rows_intact_on_page_break() {
    let row_text = "This is a row with enough text to make it take some vertical space. ".repeat(5);
    let document = Document::builder()
        .page_size(PageSize::new(595.28, 300.0))
        .page_margins(Margins::all(20.0))
        .content(DocumentNode::Table(
            TableNode::new(vec![
                vec![TableCell::text(&row_text)],
                vec![TableCell::text(&row_text)],
                vec![TableCell::text(&row_text)],
            ])
            .dont_break_rows(),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    let page_bottom = 300.0 - 20.0;
    for page in &layout.pages {
        for item in &page.items {
            if let LayoutItem::Table(table) = item {
                for row in &table.rows {
                    let row_top = row.y;
                    let row_bottom = row_top + row.height;
                    assert!(
                        row_top >= 20.0 - 0.01,
                        "Row top ({}) should be within page top margin",
                        row_top
                    );
                    if row_bottom > page_bottom + 0.01 {
                        for (i, r) in table.rows.iter().enumerate() {
                            eprintln!(
                                "  row {}: y={:.1}, h={:.1}, bottom={:.1}",
                                i, r.y, r.height, r.y + r.height
                            );
                        }
                        panic!(
                            "Row bottom ({:.1}) exceeds page bottom ({:.1}) �?row was split across pages",
                            row_bottom, page_bottom
                        );
                    }
                }
            }
        }
    }
}

#[test]
fn table_node_accepts_heights_array() {
    let table = TableNode::new(vec![
        vec![TableCell::text("H1"), TableCell::text("H2")],
        vec![TableCell::text("R1"), TableCell::text("R2")],
    ])
    .heights(vec![30.0, 50.0]);

    assert_eq!(table.heights, Some(vec![30.0, 50.0]));
}

#[test]
fn table_node_accepts_single_height() {
    let table = TableNode::new(vec![
        vec![TableCell::text("A")],
        vec![TableCell::text("B")],
    ])
    .heights(vec![40.0]);

    assert_eq!(table.heights, Some(vec![40.0]));
}

#[test]
fn measured_table_uses_specified_row_heights() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![
                vec![TableCell::text("Row1")],
                vec![TableCell::text("Row2")],
            ])
            .heights(vec![50.0, 80.0])
        ))
        .build();

    let normalized = normalize_document(&doc).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    match &measured.blocks[0] {
        MeasuredBlock::Table(table) => {
            assert!((table.rows[0].height - 50.0).abs() < 0.1);
            assert!((table.rows[1].height - 80.0).abs() < 0.1);
        }
        _ => panic!("expected table"),
    }
}

#[test]
fn row_height_at_least_content_height() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![
                vec![TableCell::text("This is a long text that will wrap to multiple lines and create a tall row")],
            ])
            .heights(vec![10.0])
        ))
        .build();

    let normalized = normalize_document(&doc).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    match &measured.blocks[0] {
        MeasuredBlock::Table(table) => {
            assert!(table.rows[0].height > 10.0);
        }
        _ => panic!("expected table"),
    }
}

#[test]
fn table_dont_break_rows_moves_tall_row_to_next_page() {
    let tall_text = "Line of text that adds height. ".repeat(20);
    let filler_lines: Vec<DocumentNode> = (0..10)
        .map(|_| DocumentNode::Text(TextNode::new("Filler line to consume vertical space")))
        .collect();

    let mut builder = Document::builder()
        .page_size(PageSize::new(595.28, 300.0))
        .page_margins(Margins::all(20.0));

    for line in filler_lines {
        builder = builder.content(line);
    }

    let document = builder
        .content(DocumentNode::Table(
            TableNode::new(vec![
                vec![TableCell::text(&tall_text)],
                vec![TableCell::text("Short row")],
            ])
            .dont_break_rows(),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    assert!(layout.pages.len() >= 2, "Should have multiple pages due to tall content");

    let page_bottom = 300.0 - 20.0;
    for page in &layout.pages {
        for item in &page.items {
            if let LayoutItem::Table(table) = item {
                for row in &table.rows {
                    let row_bottom = row.y + row.height;
                    assert!(
                        row_bottom <= page_bottom + 0.01,
                        "Row bottom ({:.1}) should not exceed page bottom ({:.1})",
                        row_bottom, page_bottom
                    );
                }
            }
        }
    }
}

#[test]
fn style_accepts_border_color_array() {
    let style = Style::new()
        .border_color(vec!["red", "green", "blue", "yellow"]);
    assert_eq!(style.border_color, Some(vec!["red".into(), "green".into(), "blue".into(), "yellow".into()]));
}

#[test]
fn table_cell_with_border_colors_renders() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![
                TableCell::text("colored borders")
                    .style(Style::new().border_color(vec!["red", "green", "blue", "yellow"]))
            ]])
        ))
        .build();
    let pdf = PdfMake::new().render(&doc).unwrap();
    assert!(pdf.len() > 100);
}

#[test]
fn border_color_single_applies_to_all_sides() {
    let style = Style::new().border_color(vec!["red"]);
    assert_eq!(style.border_color, Some(vec!["red".into()]));
}

#[test]
fn table_cell_accepts_vertical_alignment() {
    let cell = TableCell::text("middle")
        .vertical_alignment(VerticalAlignment::Middle);
    assert_eq!(cell.vertical_alignment, Some(VerticalAlignment::Middle));
}

#[test]
fn vertical_alignment_renders_without_error() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![
                vec![
                    TableCell::text("top").vertical_alignment(VerticalAlignment::Top),
                    TableCell::text("middle").vertical_alignment(VerticalAlignment::Middle),
                    TableCell::text("bottom").vertical_alignment(VerticalAlignment::Bottom),
                ],
            ])
        ))
        .build();
    let pdf = PdfMake::new().render(&doc).unwrap();
    assert!(pdf.len() > 100);
}

#[test]
fn vertical_alignment_default_is_none() {
    let cell = TableCell::text("default");
    assert_eq!(cell.vertical_alignment, None);
}

#[test]
fn table_accepts_builtin_style() {
    let table = TableNode::new(vec![
        vec![TableCell::text("H1"), TableCell::text("H2")],
        vec![TableCell::text("A"), TableCell::text("B")],
    ])
    .table_style(TableStyle::NoBorders);
    assert_eq!(table.table_style, Some(TableStyle::NoBorders));
}

#[test]
fn table_style_flows_through_pipeline() {
    let document = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![
                vec![TableCell::text("H1"), TableCell::text("H2")],
                vec![TableCell::text("A"), TableCell::text("B")],
            ])
            .table_style(TableStyle::HeaderLineOnly)
            .header_rows(1),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    match &normalized.blocks[0] {
        NormalizedBlock::Table(t) => {
            assert_eq!(t.table_style, Some(TableStyle::HeaderLineOnly));
        }
        _ => panic!("expected table"),
    }

    let fonts = pdfmake_rust::FontRegistry::empty();
    let measured = pdfmake_rust::measure_document(&normalized, &fonts).unwrap();
    match &measured.blocks[0] {
        pdfmake_rust::MeasuredBlock::Table(t) => {
            assert_eq!(t.table_style, Some(TableStyle::HeaderLineOnly));
        }
        _ => panic!("expected table"),
    }
}

#[test]
fn table_no_borders_produces_empty_borders() {
    let document = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![
                vec![TableCell::text("A"), TableCell::text("B")],
                vec![TableCell::text("C"), TableCell::text("D")],
            ])
            .table_style(TableStyle::NoBorders),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let fonts = pdfmake_rust::FontRegistry::empty();
    let measured = pdfmake_rust::measure_document(&normalized, &fonts).unwrap();
    let layout = pdfmake_rust::layout_document(&measured).unwrap();

    match &layout.pages[0].items[0] {
        LayoutItem::Table(t) => {
            assert!(t.borders.is_empty(), "NoBorders should produce no table-level borders");
            for row in &t.rows {
                for cell in &row.cells {
                    assert!(cell.borders.is_empty(), "NoBorders should produce no cell borders");
                }
            }
        }
        _ => panic!("expected table layout item"),
    }
}

#[test]
fn table_cross_lines_keeps_all_borders() {
    let document = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![
                vec![TableCell::text("A"), TableCell::text("B")],
                vec![TableCell::text("C"), TableCell::text("D")],
            ])
            .table_style(TableStyle::CrossLines),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let fonts = pdfmake_rust::FontRegistry::empty();
    let measured = pdfmake_rust::measure_document(&normalized, &fonts).unwrap();
    let layout = pdfmake_rust::layout_document(&measured).unwrap();

    match &layout.pages[0].items[0] {
        LayoutItem::Table(t) => {
            assert!(!t.borders.is_empty(), "CrossLines should have borders");
        }
        _ => panic!("expected table layout item"),
    }
}

#[test]
fn table_accepts_layout_config() {
    let config = TableLayoutConfig {
        h_line_width: Some(1.0),
        v_line_width: Some(0.5),
        h_line_color: Some("red".into()),
        ..Default::default()
    };
    let table = TableNode::new(vec![vec![TableCell::text("A")]])
        .layout_config(config);
    assert!(table.layout_config.is_some());
    let cfg = table.layout_config.unwrap();
    assert_eq!(cfg.h_line_width, Some(1.0));
    assert_eq!(cfg.v_line_width, Some(0.5));
    assert_eq!(cfg.h_line_color, Some("red".into()));
}

#[test]
fn layout_config_passed_through_pipeline() {
    let config = TableLayoutConfig {
        h_line_width: Some(2.0),
        v_line_width: Some(1.0),
        ..Default::default()
    };
    let document = Document::builder()
        .content(DocumentNode::table(
            TableNode::new(vec![vec![TableCell::text("A")]]).layout_config(config),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let fonts = pdfmake_rust::FontRegistry::empty();
    let measured = pdfmake_rust::measure_document(&normalized, &fonts).unwrap();
    let layout = pdfmake_rust::layout_document(&measured).unwrap();

    match &layout.pages[0].items[0] {
        LayoutItem::Table(t) => {
            assert!(t.layout_config.is_some());
            let cfg = t.layout_config.as_ref().unwrap();
            assert_eq!(cfg.h_line_width, Some(2.0));
            assert_eq!(cfg.v_line_width, Some(1.0));
        }
        _ => panic!("expected table layout item"),
    }
}

#[test]
fn fill_opacity_renders() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![
                TableCell::text("50% opacity")
                    .style(Style::new().fill_color("#3498db").fill_opacity(0.5))
            ]])
        ))
        .build();
    let pdf = PdfMake::new().render(&doc).unwrap();
    assert!(pdf.len() > 100);
}

#[test]
fn table_cell_has_border_fields() {
    let cell = TableCell::text("hello")
        .border(vec![true, false, true, false])
        .border_color(vec![String::from("#ff0000"), String::from("#00ff00")])
        .fill_color("#3366cc")
        .fill_opacity(0.5);

    assert_eq!(cell.border, Some(vec![true, false, true, false]));
    assert_eq!(cell.border_color, Some(vec!["#ff0000".to_string(), "#00ff00".to_string()]));
    assert_eq!(cell.fill_color, Some("#3366cc".to_string()));
    assert_eq!(cell.fill_opacity, Some(0.5));
}

#[test]
fn table_cell_default_border_fields_are_none() {
    let cell = TableCell::text("hello");
    assert_eq!(cell.border, None);
    assert_eq!(cell.border_color, None);
    assert_eq!(cell.fill_color, None);
    assert_eq!(cell.fill_opacity, None);
}

#[test]
fn per_cell_fill_color_overrides_style_in_layout() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![
                TableCell::text("cell")
                    .fill_color("#ff0000")
                    .style(Style::new().fill_color("#0000ff"))
            ]])
        ))
        .build();

    let normalized = normalize_document(&doc).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    match &layout.pages[0].items[0] {
        LayoutItem::Table(table) => {
            let cell = &table.rows[0].cells[0];
            assert_eq!(cell.fill_color, Some("#ff0000".to_string()));
        }
        _ => panic!("expected table"),
    }
}

#[test]
fn per_cell_fill_opacity_overrides_style_in_layout() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![
                TableCell::text("cell")
                    .fill_opacity(0.7)
                    .style(Style::new().fill_opacity(0.3))
            ]])
        ))
        .build();

    let normalized = normalize_document(&doc).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    match &layout.pages[0].items[0] {
        LayoutItem::Table(table) => {
            let cell = &table.rows[0].cells[0];
            assert_eq!(cell.fill_opacity, Some(0.7));
        }
        _ => panic!("expected table"),
    }
}

#[test]
fn per_cell_border_color_overrides_style_in_layout() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![
                TableCell::text("cell")
                    .border_color(vec![String::from("#ff0000")])
                    .style(Style::new().border_color(vec!["#0000ff"]))
            ]])
        ))
        .build();

    let normalized = normalize_document(&doc).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    match &layout.pages[0].items[0] {
        LayoutItem::Table(table) => {
            let cell = &table.rows[0].cells[0];
            assert_eq!(cell.border_colors, Some(vec!["#ff0000".to_string()]));
        }
        _ => panic!("expected table"),
    }
}

#[test]
fn per_cell_border_removes_sides_from_layout() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![
                TableCell::text("cell")
                    .border(vec![true, false, true, false])
            ]])
        ))
        .build();

    let normalized = normalize_document(&doc).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    match &layout.pages[0].items[0] {
        LayoutItem::Table(table) => {
            let cell = &table.rows[0].cells[0];
            // border=[left,top,right,bottom] = [true,false,true,false]
            // cell_borders order: [top, right, bottom, left]
            // cell_borders[0]=top → border[1]=false → skip
            // cell_borders[1]=right → border[2]=true → keep
            // cell_borders[2]=bottom → border[3]=false → skip
            // cell_borders[3]=left → border[0]=true → keep
            assert_eq!(cell.borders.len(), 2);
        }
        _ => panic!("expected table"),
    }
}

#[test]
fn per_cell_border_all_false_removes_all_borders() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![
                TableCell::text("cell")
                    .border(vec![false, false, false, false])
            ]])
        ))
        .build();

    let normalized = normalize_document(&doc).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    match &layout.pages[0].items[0] {
        LayoutItem::Table(table) => {
            let cell = &table.rows[0].cells[0];
            assert_eq!(cell.borders.len(), 0);
        }
        _ => panic!("expected table"),
    }
}

#[test]
fn per_cell_settings_render_to_pdf() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![
                vec![
                    TableCell::text("A1")
                        .fill_color("#ff0000")
                        .fill_opacity(0.5)
                        .border(vec![true, true, false, false])
                        .border_color(vec![String::from("#00ff00")]),
                    TableCell::text("B1"),
                ],
            ])
        ))
        .build();
    let pdf = PdfMake::new().render(&doc).unwrap();
    assert!(pdf.len() > 100);
}

#[test]
fn fill_opacity_full_is_noop() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![
                TableCell::text("100% opacity")
                    .style(Style::new().fill_color("#3498db").fill_opacity(1.0))
            ]])
        ))
        .build();
    let pdf = PdfMake::new().render(&doc).unwrap();
    assert!(pdf.len() > 100);
}

#[test]
fn fill_opacity_none_works() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![
                TableCell::text("no opacity")
                    .style(Style::new().fill_color("#3498db"))
            ]])
        ))
        .build();
    let pdf = PdfMake::new().render(&doc).unwrap();
    assert!(pdf.len() > 100);
}

#[test]
fn table_h_line_when_broken_defaults_to_none() {
    let document = Document::builder()
        .content(DocumentNode::Table(TableNode::new(vec![vec![TableCell::text("A")]])))
        .build();

    let normalized = normalize_document(&document).unwrap();
    match &normalized.blocks[0] {
        NormalizedBlock::Table(table) => {
            assert_eq!(table.h_line_when_broken, None);
        }
        _ => panic!("expected table block"),
    }
}

#[test]
fn table_h_line_when_broken_flag_threaded_through_normalize() {
    let document = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![TableCell::text("A")]]).h_line_when_broken(false),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    match &normalized.blocks[0] {
        NormalizedBlock::Table(table) => {
            assert_eq!(table.h_line_when_broken, Some(false), "h_line_when_broken should be Some(false) after normalize");
        }
        _ => panic!("expected table block"),
    }
}

#[test]
fn table_h_line_when_broken_flag_threaded_through_measure() {
    let document = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![TableCell::text("A")]]).h_line_when_broken(true),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    match &measured.blocks[0] {
        MeasuredBlock::Table(table) => {
            assert_eq!(table.h_line_when_broken, Some(true), "h_line_when_broken should be Some(true) after measure");
        }
        _ => panic!("expected table block"),
    }
}

#[test]
fn table_h_line_when_broken_false_removes_borders_at_break() {
    let rows: Vec<Vec<TableCell>> = (0..40)
        .map(|i| vec![TableCell::text(format!("Row {}", i))])
        .collect();
    let doc = Document::builder()
        .page_size(PageSize::a4())
        .content(DocumentNode::Table(
            TableNode::new(rows)
                .widths(vec![TableWidth::Star])
                .h_line_when_broken(false),
        ))
        .build();
    let normalized = normalize_document(&doc).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    let tables: Vec<&pdfmake_rust::LayoutTable> = layout.pages.iter()
        .flat_map(|p| p.items.iter())
        .filter_map(|item| match item {
            LayoutItem::Table(t) => Some(t),
            _ => None,
        })
        .collect();
    assert!(tables.len() >= 2, "table should span multiple pages, got {}", tables.len());

    let first = tables[0];
    let last = tables[tables.len() - 1];

    let has_h_border = |table: &pdfmake_rust::LayoutTable, y: f32| -> bool {
        table.borders.iter().any(|l| (l.y1 - y).abs() < 0.01 && (l.y2 - y).abs() < 0.01)
    };

    let first_bottom_y = first.rows.last().map(|r| r.y + r.height).unwrap_or(0.0);
    let last_top_y = last.rows.first().map(|r| r.y).unwrap_or(0.0);

    assert!(has_h_border(first, first.rows[0].y), "first fragment should keep top border");
    assert!(!has_h_border(first, first_bottom_y), "first fragment should remove bottom border at break");
    assert!(!has_h_border(last, last_top_y), "last fragment should remove top border at break");
    assert!(has_h_border(last, last.rows.last().map(|r| r.y + r.height).unwrap_or(0.0)), "last fragment should keep bottom border");
}

#[test]
fn table_h_line_when_broken_true_preserves_borders_at_break() {
    let rows: Vec<Vec<TableCell>> = (0..40)
        .map(|i| vec![TableCell::text(format!("Row {}", i))])
        .collect();
    let doc = Document::builder()
        .page_size(PageSize::a4())
        .content(DocumentNode::Table(
            TableNode::new(rows)
                .widths(vec![TableWidth::Star])
                .h_line_when_broken(true),
        ))
        .build();
    let normalized = normalize_document(&doc).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    let tables: Vec<&pdfmake_rust::LayoutTable> = layout.pages.iter()
        .flat_map(|p| p.items.iter())
        .filter_map(|item| match item {
            LayoutItem::Table(t) => Some(t),
            _ => None,
        })
        .collect();
    assert!(tables.len() >= 2, "table should span multiple pages, got {}", tables.len());

    let first = tables[0];
    let last = tables[tables.len() - 1];

    let has_h_border = |table: &pdfmake_rust::LayoutTable, y: f32| -> bool {
        table.borders.iter().any(|l| (l.y1 - y).abs() < 0.01 && (l.y2 - y).abs() < 0.01)
    };

    let first_bottom_y = first.rows.last().map(|r| r.y + r.height).unwrap_or(0.0);
    let last_top_y = last.rows.first().map(|r| r.y).unwrap_or(0.0);

    assert!(has_h_border(first, first.rows[0].y), "first fragment should keep top border");
    assert!(has_h_border(first, first_bottom_y), "first fragment should keep bottom border (h_line_when_broken=true)");
    assert!(has_h_border(last, last_top_y), "last fragment should keep top border (h_line_when_broken=true)");
    assert!(has_h_border(last, last.rows.last().map(|r| r.y + r.height).unwrap_or(0.0)), "last fragment should keep bottom border");
}
