use core_protocol::editor::{
    BlockProps, ContentNode, ContentNodePayload, EditorBlock, EditorBlockDocument,
    EditorBlockDocumentTiptapBridge, EditorBlockType, TextMark, TiptapCodeBlockAttrs,
    TiptapHeadingAttrs, TiptapNode, TiptapParagraphAttrs, TiptapTaskItemAttrs,
};
use std::collections::BTreeMap;

#[test]
fn p05_blocks_round_trip_through_tiptap_doc() {
    let document = EditorBlockDocument {
        document_id: "doc_1".into(),
        root_block_ids: vec![
            "p1".into(),
            "h1".into(),
            "b1".into(),
            "n1".into(),
            "t1".into(),
            "q1".into(),
            "c1".into(),
        ],
        blocks: vec![
            EditorBlock {
                block_id: "p1".into(),
                block_type: EditorBlockType::Paragraph,
                props: BlockProps::default(),
                content_nodes: vec![ContentNode {
                    payload: ContentNodePayload::Text {
                        text: "段落".into(),
                        marks: vec![TextMark::Bold],
                    },
                    attrs: BTreeMap::new(),
                }],
                child_block_ids: vec![],
            },
            EditorBlock {
                block_id: "h1".into(),
                block_type: EditorBlockType::Heading,
                props: BlockProps {
                    heading_level: Some(2),
                    ..BlockProps::default()
                },
                content_nodes: vec![ContentNode {
                    payload: ContentNodePayload::Text {
                        text: "标题".into(),
                        marks: vec![],
                    },
                    attrs: BTreeMap::new(),
                }],
                child_block_ids: vec![],
            },
            EditorBlock {
                block_id: "b1".into(),
                block_type: EditorBlockType::BulletListItem,
                props: BlockProps::default(),
                content_nodes: vec![ContentNode {
                    payload: ContentNodePayload::Text {
                        text: "项目".into(),
                        marks: vec![],
                    },
                    attrs: BTreeMap::new(),
                }],
                child_block_ids: vec![],
            },
            EditorBlock {
                block_id: "n1".into(),
                block_type: EditorBlockType::NumberedListItem,
                props: BlockProps::default(),
                content_nodes: vec![ContentNode {
                    payload: ContentNodePayload::Text {
                        text: "序号".into(),
                        marks: vec![],
                    },
                    attrs: BTreeMap::new(),
                }],
                child_block_ids: vec![],
            },
            EditorBlock {
                block_id: "t1".into(),
                block_type: EditorBlockType::Todo,
                props: BlockProps {
                    checked: Some(true),
                    ..BlockProps::default()
                },
                content_nodes: vec![ContentNode {
                    payload: ContentNodePayload::Text {
                        text: "待办".into(),
                        marks: vec![],
                    },
                    attrs: BTreeMap::new(),
                }],
                child_block_ids: vec![],
            },
            EditorBlock {
                block_id: "q1".into(),
                block_type: EditorBlockType::Quote,
                props: BlockProps::default(),
                content_nodes: vec![ContentNode {
                    payload: ContentNodePayload::Text {
                        text: "引用".into(),
                        marks: vec![],
                    },
                    attrs: BTreeMap::new(),
                }],
                child_block_ids: vec![],
            },
            EditorBlock {
                block_id: "c1".into(),
                block_type: EditorBlockType::CodeBlock,
                props: BlockProps {
                    language: Some("rust".into()),
                    ..BlockProps::default()
                },
                content_nodes: vec![ContentNode {
                    payload: ContentNodePayload::Text {
                        text: "fn main() {}".into(),
                        marks: vec![TextMark::Code],
                    },
                    attrs: BTreeMap::new(),
                }],
                child_block_ids: vec![],
            },
        ],
    };

    let doc =
        EditorBlockDocumentTiptapBridge::to_tiptap_doc(&document).expect("export should work");
    let round_trip = EditorBlockDocumentTiptapBridge::from_tiptap_doc("doc_2", &doc)
        .expect("import should work");

    assert_eq!(
        round_trip.root_block_ids,
        vec!["p1", "h1", "b1", "n1", "t1", "q1", "c1"]
    );
    assert_eq!(round_trip.blocks[0].block_id, "p1");
    assert_eq!(round_trip.blocks[0].content_nodes.len(), 1);
    match &round_trip.blocks[0].content_nodes[0].payload {
        ContentNodePayload::Text { marks, .. } => assert_eq!(marks, &vec![TextMark::Bold]),
        other => panic!("unexpected payload: {other:?}"),
    }
    assert_eq!(round_trip.blocks[1].props.heading_level, Some(2));
    assert_eq!(round_trip.blocks[4].props.checked, Some(true));
    assert_eq!(round_trip.blocks[6].props.language.as_deref(), Some("rust"));
}

#[test]
fn tiptap_import_preserves_explicit_block_ids_and_marks() {
    let doc = TiptapNode::Doc {
        content: vec![
            TiptapNode::Paragraph {
                attrs: TiptapParagraphAttrs {
                    block_id: Some("p-1".into()),
                },
                content: vec![TiptapNode::Text {
                    text: "Hello".into(),
                    marks: vec![core_protocol::editor::TiptapMark {
                        r#type: "italic".into(),
                    }],
                }],
            },
            TiptapNode::Heading {
                attrs: TiptapHeadingAttrs {
                    level: 3,
                    block_id: Some("h-1".into()),
                },
                content: vec![TiptapNode::Text {
                    text: "H".into(),
                    marks: vec![],
                }],
            },
            TiptapNode::TaskList {
                attrs: core_protocol::editor::TiptapListAttrs {
                    block_id: Some("t-1".into()),
                },
                content: vec![TiptapNode::TaskItem {
                    attrs: TiptapTaskItemAttrs {
                        checked: true,
                        block_id: Some("t-1".into()),
                    },
                    content: vec![TiptapNode::Paragraph {
                        attrs: TiptapParagraphAttrs {
                            block_id: Some("t-1".into()),
                        },
                        content: vec![TiptapNode::Text {
                            text: "todo".into(),
                            marks: vec![],
                        }],
                    }],
                }],
            },
            TiptapNode::CodeBlock {
                attrs: TiptapCodeBlockAttrs {
                    language: Some("ts".into()),
                    block_id: Some("c-1".into()),
                },
                content: vec![TiptapNode::Text {
                    text: "let x = 1;".into(),
                    marks: vec![core_protocol::editor::TiptapMark {
                        r#type: "code".into(),
                    }],
                }],
            },
        ],
    };

    let imported = EditorBlockDocumentTiptapBridge::from_tiptap_doc("doc_3", &doc).unwrap();
    assert_eq!(imported.root_block_ids, vec!["p-1", "h-1", "t-1", "c-1"]);
    assert_eq!(imported.blocks[0].block_id, "p-1");
    assert_eq!(imported.blocks[1].props.heading_level, Some(3));
    assert_eq!(imported.blocks[2].props.checked, Some(true));
    assert_eq!(imported.blocks[3].props.language.as_deref(), Some("ts"));

    match &imported.blocks[0].content_nodes[0].payload {
        ContentNodePayload::Text { marks, .. } => assert_eq!(marks, &vec![TextMark::Italic]),
        other => panic!("unexpected payload: {other:?}"),
    }
}
