use pdfmake_rust::{
    Document, DocumentNode, Margins, PageBreakInfo, PageSize, PdfMake, TextNode,
};
use std::sync::{Arc, Mutex};

#[allow(dead_code)]
fn make_long_text(n: usize) -> Vec<DocumentNode> {
    (0..n)
        .map(|i| DocumentNode::Text(TextNode::new(format!("Line {}", i + 1))))
        .collect()
}

#[test]
fn page_break_before_callback_type_is_exported() {
    let _ = std::any::TypeId::of::<PageBreakInfo>();
}

#[test]
fn no_callback_produces_valid_pdf() {
    let doc = Document::builder()
        .content(DocumentNode::Text(TextNode::new("Hello world")))
        .build();
    let pdf = PdfMake::new().render(&doc).unwrap();
    assert!(pdf.len() > 100);
    assert!(pdf.starts_with(b"%PDF"));
}

#[test]
fn callback_builder_method_sets_field() {
    let doc = Document::builder()
        .content(DocumentNode::Text(TextNode::new("Hello")))
        .page_break_before(|_info| false)
        .build();
    assert!(doc.page_break_before.is_some());
}

#[test]
fn callback_receives_page_number() {
    let pages_seen = Arc::new(Mutex::new(Vec::<usize>::new()));
    let pages_seen_clone = pages_seen.clone();

    let doc = Document::builder()
        .page_size(PageSize::new(595.28, 200.0))
        .page_margins(Margins::all(10.0))
        .content(DocumentNode::Text(TextNode::new("A")))
        .content(DocumentNode::Text(TextNode::new("B")))
        .content(DocumentNode::Text(TextNode::new("C")))
        .page_break_before(move |info: &PageBreakInfo| {
            pages_seen_clone.lock().unwrap().push(info.page_number);
            false
        })
        .build();

    let _ = PdfMake::new().render(&doc).unwrap();
    let seen = pages_seen.lock().unwrap();
    assert!(!seen.is_empty(), "callback should be invoked at least once");
}

#[test]
fn callback_receives_node_text_for_text_nodes() {
    let texts = Arc::new(Mutex::new(Vec::<Option<String>>::new()));
    let texts_clone = texts.clone();

    let doc = Document::builder()
        .content(DocumentNode::Text(TextNode::new("Hello World")))
        .content(DocumentNode::Text(TextNode::new("Second paragraph")))
        .page_break_before(move |info: &PageBreakInfo| {
            texts_clone.lock().unwrap().push(info.node_text.clone());
            false
        })
        .build();

    let _ = PdfMake::new().render(&doc).unwrap();
    let seen = texts.lock().unwrap();
    assert!(seen.iter().any(|t| t.as_deref() == Some("Hello World")));
    assert!(seen.iter().any(|t| t.as_deref() == Some("Second paragraph")));
}

#[test]
fn callback_receives_is_stack_for_stack_nodes() {
    let is_stacks = Arc::new(Mutex::new(Vec::<bool>::new()));
    let is_stacks_clone = is_stacks.clone();

    let doc = Document::builder()
        .content(DocumentNode::Stack(vec![
            DocumentNode::Text(TextNode::new("Child 1")),
            DocumentNode::Text(TextNode::new("Child 2")),
        ]))
        .page_break_before(move |info: &PageBreakInfo| {
            is_stacks_clone.lock().unwrap().push(info.is_stack);
            false
        })
        .build();

    let _ = PdfMake::new().render(&doc).unwrap();
    let seen = is_stacks.lock().unwrap();
    assert!(seen.iter().any(|&s| s), "callback should see is_stack=true for Stack nodes");
}

#[test]
fn callback_returning_true_inserts_page_break() {
    let doc = Document::builder()
        .page_size(PageSize::new(595.28, 841.89))
        .page_margins(Margins::all(40.0))
        .content(DocumentNode::Text(TextNode::new("Page 1 content")))
        .content(DocumentNode::Text(TextNode::new("Page 2 content")))
        .page_break_before(|info: &PageBreakInfo| {
            // Force a page break before the second text node
            info.node_text.as_deref() == Some("Page 2 content")
        })
        .build();

    let pdf = PdfMake::new().render(&doc).unwrap();
    assert!(pdf.starts_with(b"%PDF"), "should produce valid PDF");
}

#[test]
fn callback_can_force_every_node_to_new_page() {
    let doc = Document::builder()
        .page_size(PageSize::new(595.28, 841.89))
        .page_margins(Margins::all(40.0))
        .content(DocumentNode::Text(TextNode::new("First")))
        .content(DocumentNode::Text(TextNode::new("Second")))
        .content(DocumentNode::Text(TextNode::new("Third")))
        .page_break_before(|_info: &PageBreakInfo| true)
        .build();

    let pdf = PdfMake::new().render(&doc).unwrap();
    assert!(pdf.starts_with(b"%PDF"), "should produce valid PDF with all breaks");
}

#[test]
fn callback_with_no_origin_does_not_crash() {
    let callback_count = Arc::new(Mutex::new(0u32));
    let callback_count_clone = callback_count.clone();

    let doc = Document::builder()
        .content(DocumentNode::Text(TextNode::new("Text")))
        .page_break_before(move |_info: &PageBreakInfo| {
            *callback_count_clone.lock().unwrap() += 1;
            false
        })
        .build();

    let _ = PdfMake::new().render(&doc).unwrap();
    let count = callback_count.lock().unwrap();
    assert!(*count > 0, "callback should be invoked");
}

#[test]
fn page_break_info_fields_are_accessible() {
    let info = PageBreakInfo {
        page_number: 2,
        total_pages: 5,
        node_text: Some("test".to_string()),
        is_stack: false,
    };
    assert_eq!(info.page_number, 2);
    assert_eq!(info.total_pages, 5);
    assert_eq!(info.node_text.as_deref(), Some("test"));
    assert!(!info.is_stack);
}

#[test]
fn callback_with_table_content() {
    use pdfmake_rust::{TableNode, TableCell};

    let tables_seen = Arc::new(Mutex::new(Vec::<Option<String>>::new()));
    let tables_seen_clone = tables_seen.clone();

    let doc = Document::builder()
        .content(DocumentNode::Text(TextNode::new("Before table")))
        .content(DocumentNode::Table(TableNode::new(vec![
            vec![TableCell::text("A"), TableCell::text("B")],
            vec![TableCell::text("C"), TableCell::text("D")],
        ])))
        .content(DocumentNode::Text(TextNode::new("After table")))
        .page_break_before(move |info: &PageBreakInfo| {
            tables_seen_clone.lock().unwrap().push(info.node_text.clone());
            false
        })
        .build();

    let _ = PdfMake::new().render(&doc).unwrap();
    let seen = tables_seen.lock().unwrap();
    assert!(seen.len() >= 3, "callback should be invoked for all top-level nodes");
}

#[test]
fn callback_with_list_content() {
    use pdfmake_rust::ListNode;

    let seen = Arc::new(Mutex::new(Vec::<Option<String>>::new()));
    let seen_clone = seen.clone();

    let doc = Document::builder()
        .content(DocumentNode::List(ListNode::unordered(vec![
            DocumentNode::Text(TextNode::new("Item 1")),
            DocumentNode::Text(TextNode::new("Item 2")),
        ])))
        .page_break_before(move |info: &PageBreakInfo| {
            seen_clone.lock().unwrap().push(info.node_text.clone());
            false
        })
        .build();

    let _ = PdfMake::new().render(&doc).unwrap();
    let s = seen.lock().unwrap();
    assert!(!s.is_empty(), "callback should be invoked for list nodes");
}

#[test]
fn document_without_callback_still_works() {
    let doc = Document::builder()
        .content(DocumentNode::Text(TextNode::new("Hello")))
        .build();
    assert!(doc.page_break_before.is_none());
    let pdf = PdfMake::new().render(&doc).unwrap();
    assert!(pdf.starts_with(b"%PDF"));
}

#[test]
fn callback_invoked_on_second_layout_pass_with_total_pages() {
    let total_pages_values = Arc::new(Mutex::new(Vec::<usize>::new()));
    let total_pages_clone = total_pages_values.clone();

    let doc = Document::builder()
        .page_size(PageSize::new(595.28, 200.0))
        .page_margins(Margins::all(10.0))
        .content(DocumentNode::Text(TextNode::new("A")))
        .content(DocumentNode::Text(TextNode::new("B")))
        .page_break_before(move |info: &PageBreakInfo| {
            total_pages_clone.lock().unwrap().push(info.total_pages);
            false
        })
        .build();

    let _ = PdfMake::new().render(&doc).unwrap();
    let seen = total_pages_values.lock().unwrap();
    // The second pass should have total_pages > 0
    assert!(seen.iter().any(|&tp| tp > 0), "total_pages should be set during re-layout");
}

#[test]
fn callback_total_pages_consistent_across_invocations() {
    let total_pages_values = Arc::new(Mutex::new(Vec::<usize>::new()));
    let total_pages_clone = total_pages_values.clone();

    let doc = Document::builder()
        .page_size(PageSize::new(595.28, 80.0))
        .page_margins(Margins::all(5.0))
        .content(DocumentNode::Text(TextNode::new("First")))
        .content(DocumentNode::Text(TextNode::new("Second")))
        .content(DocumentNode::Text(TextNode::new("Third")))
        .content(DocumentNode::Text(TextNode::new("Fourth")))
        .content(DocumentNode::Text(TextNode::new("Fifth")))
        .page_break_before(move |info: &PageBreakInfo| {
            total_pages_clone.lock().unwrap().push(info.total_pages);
            false
        })
        .build();

    let _ = PdfMake::new().render(&doc).unwrap();
    let seen = total_pages_values.lock().unwrap();
    assert!(seen.len() >= 2, "callback should be invoked multiple times, got {}", seen.len());
    let max_tp = *seen.iter().max().unwrap();
    assert!(max_tp >= 2, "multi-page doc should have total_pages >= 2, got max_tp={}", max_tp);
    let second_pass: Vec<_> = seen.iter().filter(|&&tp| tp == max_tp).collect();
    assert!(second_pass.len() >= 2, "second pass should have consistent total_pages across nodes");
}

#[test]
fn callback_single_page_has_total_pages_one() {
    let total_pages_values = Arc::new(Mutex::new(Vec::<usize>::new()));
    let total_pages_clone = total_pages_values.clone();

    let doc = Document::builder()
        .content(DocumentNode::Text(TextNode::new("Single page content")))
        .page_break_before(move |info: &PageBreakInfo| {
            total_pages_clone.lock().unwrap().push(info.total_pages);
            false
        })
        .build();

    let _ = PdfMake::new().render(&doc).unwrap();
    let seen = total_pages_values.lock().unwrap();
    assert!(seen.iter().any(|&tp| tp == 1), "single-page doc should report total_pages=1");
}
