use pdfmake_rust::{text::wrap_text, style::ResolvedStyle, Alignment};
use pdfmake_rust::{Document, DocumentNode, FontRegistry, Margins, PageBreakMode, PageSize, Style, TextNode, TableNode, TableCell, layout_document, measure_document, normalize_document, MeasuredBlock, LayoutItem};

#[test]
fn wrap_text_splits_words_by_available_width() {
    let style = ResolvedStyle { font_size: 10.0, bold: false, italics: false, font_family: None, line_height: 1.0, alignment: Alignment::Left, decoration: None, color: None, background: None, margin: None, fill_color: None, fill_opacity: None, opacity: None, decoration_style: None, decoration_color: None, decoration_thickness: None, link: None, border_color: None, character_spacing: None, no_wrap: false, unbreakable: false, sup: false, sub: false, word_break: None, leading_indent: 0.0, preserve_leading_spaces: false, preserve_trailing_spaces: false, font_features: None, overlay_pattern: None, overlay_opacity: None };
    let lines = wrap_text("alpha beta gamma", 60.0, style);
    assert_eq!(lines, vec!["alpha beta".to_string(), "gamma".to_string()]);
}

#[test]
fn wrap_text_keeps_short_text_on_one_line() {
    let style = ResolvedStyle { font_size: 12.0, bold: false, italics: false, font_family: None, line_height: 1.0, alignment: Alignment::Left, decoration: None, color: None, background: None, margin: None, fill_color: None, fill_opacity: None, opacity: None, decoration_style: None, decoration_color: None, decoration_thickness: None, link: None, border_color: None, character_spacing: None, no_wrap: false, unbreakable: false, sup: false, sub: false, word_break: None, leading_indent: 0.0, preserve_leading_spaces: false, preserve_trailing_spaces: false, font_features: None, overlay_pattern: None, overlay_opacity: None };
    let lines = wrap_text("hello", 100.0, style);
    assert_eq!(lines, vec!["hello".to_string()]);
}

#[test]
fn wrap_text_handles_unbreakable_long_string() {
    // #2846: 无空格长字符串不应溢出可用宽度
    let style = ResolvedStyle {
        font_size: 10.0, bold: false, italics: false, font_family: None,
        line_height: 1.0, alignment: Alignment::Left, decoration: None,
        color: None, background: None, margin: None, fill_color: None, fill_opacity: None, opacity: None,
        decoration_style: None, decoration_color: None, decoration_thickness: None,
        link: None, border_color: None, character_spacing: None, no_wrap: false, unbreakable: false,
        sup: false, sub: false, word_break: None, leading_indent: 0.0,
        preserve_leading_spaces: false, preserve_trailing_spaces: false, font_features: None,
        overlay_pattern: None, overlay_opacity: None,
    };
    let font_size = style.font_size;
    let long = "u".repeat(100);
    let available_width = 100.0; // 每个字符约5pt宽，一行只能容纳~20个
    let lines = wrap_text(&long, available_width, style);
    // 关键断言：每行宽度不应超过 available_width
    for line in &lines {
        let line_width = line.chars().count() as f32 * font_size * 0.5;
        assert!(line_width <= available_width + 0.001,
            "line '{}' width {} exceeds available {} -> overflow bug #2846",
            &line[..line.len().min(20)], line_width, available_width);
    }
    // 长字符串应该被拆成多行
    assert!(lines.len() > 1, "100-char string in 100pt width should wrap to multiple lines, got {}", lines.len());
}

#[test]
fn measure_document_creates_lines_for_text_blocks() {
    let document = Document::builder()
        .content(DocumentNode::text("alpha beta gamma"))
        .build();
    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    assert_eq!(measured.blocks.len(), 1);
    let MeasuredBlock::Text(text_block) = &measured.blocks[0] else { panic!("expected text block") };
    assert!(!text_block.lines.is_empty());
}

#[test]
fn layout_document_positions_lines_from_top_margin() {
    let document = Document::builder()
        .page_size(PageSize::new(200.0, 120.0))
        .page_margins(Margins::all(20.0))
        .content(DocumentNode::text("hello world"))
        .build();
    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    let snapshot = serde_json::to_value(&layout).unwrap();
    assert_eq!(snapshot["pages"][0]["items"][0]["Text"]["x"], 20.0);
    assert_eq!(snapshot["pages"][0]["items"][0]["Text"]["y"], 20.0);
}

#[test]
fn text_with_margin_has_offset_position() {
    let doc_no_margin = Document::builder()
        .content(DocumentNode::text("No margin"))
        .build();
    let doc_with_margin = Document::builder()
        .content(DocumentNode::Text(TextNode::new("With margin").inline_style(
            Style::new().margin(Margins::new(20.0, 30.0, 20.0, 30.0))
        )))
        .build();

    let layout_no = layout_document(&measure_document(&normalize_document(&doc_no_margin).unwrap(), &FontRegistry::empty()).unwrap()).unwrap();
    let layout_with = layout_document(&measure_document(&normalize_document(&doc_with_margin).unwrap(), &FontRegistry::empty()).unwrap()).unwrap();

    let LayoutItem::Text(ref item_no) = layout_no.pages[0].items[0] else { panic!("expected text") };
    let LayoutItem::Text(ref item_with) = layout_with.pages[0].items[0] else { panic!("expected text") };

    assert!((item_with.x - item_no.x - 20.0).abs() < 0.01, "x should be offset by margin.left");
    assert!((item_with.y - item_no.y - 30.0).abs() < 0.01, "y should be offset by margin.top");
}

#[test]
fn text_margin_increases_measured_height() {
    let doc_no_margin = Document::builder()
        .content(DocumentNode::text("test"))
        .build();
    let doc_with_margin = Document::builder()
        .content(DocumentNode::Text(TextNode::new("test").inline_style(
            Style::new().margin(Margins::new(0.0, 15.0, 0.0, 25.0))
        )))
        .build();

    let measured_no = measure_document(&normalize_document(&doc_no_margin).unwrap(), &FontRegistry::empty()).unwrap();
    let measured_with = measure_document(&normalize_document(&doc_with_margin).unwrap(), &FontRegistry::empty()).unwrap();

    let MeasuredBlock::Text(ref tb_no) = measured_no.blocks[0] else { panic!("expected text") };
    let MeasuredBlock::Text(ref tb_with) = measured_with.blocks[0] else { panic!("expected text") };

    let height_diff = tb_with.height - tb_no.height;
    assert!((height_diff - 40.0).abs() < 0.01, "height difference should be margin.top + margin.bottom = 40, got {}", height_diff);
}

#[test]
fn page_break_before_forces_new_page() {
    let doc = Document::builder()
        .content(DocumentNode::text("Page 1 content"))
        .content(DocumentNode::Text(TextNode::new("Page 2 content").page_break(PageBreakMode::Before)))
        .build();
    let layout = layout_document(&measure_document(&normalize_document(&doc).unwrap(), &FontRegistry::empty()).unwrap()).unwrap();
    assert!(layout.pages.len() >= 2, "page_break::Before should create at least 2 pages, got {}", layout.pages.len());
}

#[test]
fn page_break_after_forces_new_page() {
    let doc = Document::builder()
        .content(DocumentNode::Text(TextNode::new("Page 1 content").page_break(PageBreakMode::After)))
        .content(DocumentNode::text("Page 2 content"))
        .build();
    let layout = layout_document(&measure_document(&normalize_document(&doc).unwrap(), &FontRegistry::empty()).unwrap()).unwrap();
    assert!(layout.pages.len() >= 2, "page_break::After should create at least 2 pages, got {}", layout.pages.len());
}

#[test]
fn page_break_before_on_empty_page_noop() {
    let doc = Document::builder()
        .content(DocumentNode::Text(TextNode::new("First block").page_break(PageBreakMode::Before)))
        .build();
    let layout = layout_document(&measure_document(&normalize_document(&doc).unwrap(), &FontRegistry::empty()).unwrap()).unwrap();
    assert_eq!(layout.pages.len(), 1, "Before on first block of empty page should not create extra page");
}

#[test]
fn page_break_before_odd_goes_to_odd_page() {
    let doc = Document::builder()
        .content(DocumentNode::text("Page 1"))
        .content(DocumentNode::Text(TextNode::new("Should be on odd page").page_break(PageBreakMode::BeforeOdd)))
        .build();
    let layout = layout_document(&measure_document(&normalize_document(&doc).unwrap(), &FontRegistry::empty()).unwrap()).unwrap();
    assert_eq!(layout.pages.len(), 1, "BeforeOdd on page 1 (odd) should stay on page 1, got {} pages", layout.pages.len());
}

#[test]
fn page_break_before_even_skips_to_even_page() {
    let doc = Document::builder()
        .content(DocumentNode::text("Page 1"))
        .content(DocumentNode::Text(TextNode::new("Should be on even page").page_break(PageBreakMode::BeforeEven)))
        .build();
    let layout = layout_document(&measure_document(&normalize_document(&doc).unwrap(), &FontRegistry::empty()).unwrap()).unwrap();
    assert_eq!(layout.pages.len(), 2, "BeforeEven from page 1 (odd) should skip to page 2 (even), got {} pages", layout.pages.len());
}

#[test]
fn page_break_after_odd_skips_to_next_odd() {
    let doc = Document::builder()
        .content(DocumentNode::Text(TextNode::new("Page 1 content").page_break(PageBreakMode::AfterOdd)))
        .content(DocumentNode::text("Should be on odd page"))
        .build();
    let layout = layout_document(&measure_document(&normalize_document(&doc).unwrap(), &FontRegistry::empty()).unwrap()).unwrap();
    assert_eq!(layout.pages.len(), 3, "AfterOdd from page 1 (odd) should place next content on page 3 (odd), got {} pages", layout.pages.len());
}

#[test]
fn page_break_after_even_skips_to_next_even() {
    let doc = Document::builder()
        .content(DocumentNode::Text(TextNode::new("Page 1 content").page_break(PageBreakMode::AfterEven)))
        .content(DocumentNode::text("Should be on even page"))
        .build();
    let layout = layout_document(&measure_document(&normalize_document(&doc).unwrap(), &FontRegistry::empty()).unwrap()).unwrap();
    assert_eq!(layout.pages.len(), 2, "AfterEven from page 1 (odd) should place next content on page 2 (even), got {} pages", layout.pages.len());
}

#[test]
fn table_page_break_before_forces_new_page() {
    let doc = Document::builder()
        .content(DocumentNode::text("Some content"))
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![TableCell::text("table cell")]])
                .page_break(PageBreakMode::Before)
        ))
        .build();
    let layout = layout_document(&measure_document(&normalize_document(&doc).unwrap(), &FontRegistry::empty()).unwrap()).unwrap();
    assert!(layout.pages.len() >= 2, "Table with page_break::Before should create at least 2 pages, got {}", layout.pages.len());
}

#[test]
fn table_page_break_after_forces_new_page() {
    let doc = Document::builder()
        .content(DocumentNode::Table(
            TableNode::new(vec![vec![TableCell::text("table cell")]])
                .page_break(PageBreakMode::After)
        ))
        .content(DocumentNode::text("After table"))
        .build();
    let layout = layout_document(&measure_document(&normalize_document(&doc).unwrap(), &FontRegistry::empty()).unwrap()).unwrap();
    assert!(layout.pages.len() >= 2, "Table with page_break::After should create at least 2 pages, got {}", layout.pages.len());
}

#[test]
fn snaking_columns_flows_content_across_columns() {
    use pdfmake_rust::ColumnsNode;

    // Create content that will overflow the first column
    let long_text = "word ".repeat(200);
    let doc = Document::builder()
        .page_size(PageSize::new(400.0, 300.0))
        .page_margins(Margins::all(20.0))
        .content(DocumentNode::columns(
            ColumnsNode::new(vec![
                vec![DocumentNode::text(&long_text)],
                vec![DocumentNode::text("Column 2 content")],
            ])
            .snaking_columns(true)
        ))
        .build();

    let layout = layout_document(&measure_document(&normalize_document(&doc).unwrap(), &FontRegistry::empty()).unwrap()).unwrap();
    assert!(!layout.pages.is_empty(), "Should produce at least one page");

    // Find the columns layout item on the first page
    let first_page = &layout.pages[0];
    let columns_item = first_page.items.iter().find_map(|item| {
        if let LayoutItem::Columns(c) = item { Some(c) } else { None }
    });
    assert!(columns_item.is_some(), "First page should have a Columns layout item");
    let cols = columns_item.unwrap();

    // Snaking columns should have content distributed across columns
    assert!(cols.columns.len() == 2, "Should have 2 columns");

    // Column 0 should have content (the long text overflow)
    // Column 1 should also have content (overflow from column 0 + its own content)
    let col0_has_content = !cols.columns[0].is_empty();
    let col1_has_content = !cols.columns[1].is_empty();
    assert!(col0_has_content, "Column 0 should have content");
    assert!(col1_has_content, "Column 1 should have content in snaking mode");
}

#[test]
fn snaking_columns_default_is_false() {
    use pdfmake_rust::ColumnsNode;

    let cols = ColumnsNode::new(vec![vec![DocumentNode::text("test")]]);
    assert!(!cols.snaking_columns, "snaking_columns should default to false");
}

#[test]
fn snaking_columns_builder_sets_value() {
    use pdfmake_rust::ColumnsNode;

    let cols = ColumnsNode::new(vec![vec![DocumentNode::text("test")]])
        .snaking_columns(true);
    assert!(cols.snaking_columns, "snaking_columns builder should set value to true");
}

#[test]
fn snaking_columns_multiple_pages() {
    use pdfmake_rust::ColumnsNode;

    // Create enough content to span multiple pages
    let long_text = "line content ".repeat(500);
    let doc = Document::builder()
        .page_size(PageSize::new(400.0, 300.0))
        .page_margins(Margins::all(20.0))
        .content(DocumentNode::columns(
            ColumnsNode::new(vec![
                vec![DocumentNode::text(&long_text)],
                vec![DocumentNode::text("more text")],
            ])
            .snaking_columns(true)
        ))
        .build();

    let layout = layout_document(&measure_document(&normalize_document(&doc).unwrap(), &FontRegistry::empty()).unwrap()).unwrap();
    assert!(layout.pages.len() >= 2, "Should produce multiple pages, got {}", layout.pages.len());

    for (i, page) in layout.pages.iter().enumerate() {
        assert!(!page.items.is_empty(), "Page {} should have content", i);
    }
}