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()),
                    text_align: None,
                    ..TiptapParagraphAttrs::default()
                },
                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()),
                    text_align: None,
                    collapsed: None,
                },
                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()),
                            text_align: None,
                            ..TiptapParagraphAttrs::default()
                        },
                        content: vec![TiptapNode::Text {
                            text: "todo".into(),
                            marks: vec![],
                        }],
                    }],
                }],
            },
            TiptapNode::CodeBlock {
                attrs: TiptapCodeBlockAttrs {
                    language: Some("ts".into()),
                    block_id: Some("c-1".into()),
                    text_align: None,
                },
                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:?}"),
    }
}

#[test]
fn tiptap_heading_round_trip_preserves_collapsed_attr() {
    let doc: TiptapNode = serde_json::from_value(serde_json::json!({
        "type": "doc",
        "content": [{
            "type": "heading",
            "attrs": {
                "level": 1,
                "blockId": "folded-h1",
                "collapsed": true
            },
            "content": [{ "type": "text", "text": "折叠主标题" }]
        }]
    }))
    .expect("heading JSON should parse");

    let imported = EditorBlockDocumentTiptapBridge::from_tiptap_doc("doc_folded", &doc)
        .expect("heading should import");
    assert_eq!(imported.blocks[0].props.heading_level, Some(1));
    assert_eq!(imported.blocks[0].props.collapsed, Some(true));

    let exported =
        EditorBlockDocumentTiptapBridge::to_tiptap_doc(&imported).expect("heading should export");
    let exported_value = serde_json::to_value(exported).expect("exported doc should serialize");
    assert_eq!(
        exported_value.pointer("/content/0/attrs/collapsed"),
        Some(&serde_json::json!(true))
    );
}

#[test]
fn tiptap_image_round_trip_preserves_image_node() {
    let doc: TiptapNode = serde_json::from_value(serde_json::json!({
        "type": "doc",
        "content": [{
            "type": "image",
            "attrs": {
                "src": "/api/editor/image-placeholder.svg",
                "alt": "E24 图片占位",
                "title": "E24 图片"
            }
        }]
    }))
    .expect("image JSON should parse");

    let imported = EditorBlockDocumentTiptapBridge::from_tiptap_doc("doc_image", &doc)
        .expect("image should import");
    assert_eq!(imported.blocks[0].block_type, EditorBlockType::Image);

    let exported =
        EditorBlockDocumentTiptapBridge::to_tiptap_doc(&imported).expect("image should export");
    let exported_value = serde_json::to_value(exported).expect("exported doc should serialize");
    assert_eq!(
        exported_value.pointer("/content/0/type"),
        Some(&serde_json::json!("image"))
    );
    assert_eq!(
        exported_value.pointer("/content/0/attrs/src"),
        Some(&serde_json::json!("/api/editor/image-placeholder.svg"))
    );
}

#[test]
fn tiptap_table_round_trip_preserves_table_node() {
    let doc: TiptapNode = serde_json::from_value(serde_json::json!({
        "type": "doc",
        "content": [{
            "type": "table",
            "attrs": { "blockId": "table-1" },
            "content": [{
                "type": "tableRow",
                "content": [{
                    "type": "tableCell",
                    "attrs": { "colspan": 1, "rowspan": 1, "colwidth": null },
                    "content": [{
                        "type": "paragraph",
                        "content": [{ "type": "text", "text": "A1" }]
                    }]
                }, {
                    "type": "tableCell",
                    "attrs": { "colspan": 1, "rowspan": 1, "colwidth": null },
                    "content": [{ "type": "paragraph" }]
                }]
            }, {
                "type": "tableRow",
                "content": [{
                    "type": "tableCell",
                    "attrs": { "colspan": 1, "rowspan": 1, "colwidth": null },
                    "content": [{ "type": "paragraph" }]
                }, {
                    "type": "tableCell",
                    "attrs": { "colspan": 1, "rowspan": 1, "colwidth": null },
                    "content": [{ "type": "paragraph" }]
                }]
            }]
        }]
    }))
    .expect("table JSON should parse");

    let imported = EditorBlockDocumentTiptapBridge::from_tiptap_doc("doc_table", &doc)
        .expect("table should import");
    assert_eq!(imported.blocks[0].block_type, EditorBlockType::Table);

    let exported =
        EditorBlockDocumentTiptapBridge::to_tiptap_doc(&imported).expect("table should export");
    let exported_value = serde_json::to_value(exported).expect("exported doc should serialize");
    assert_eq!(
        exported_value.pointer("/content/0/type"),
        Some(&serde_json::json!("table"))
    );
    assert_eq!(
        exported_value.pointer("/content/0/content/0/content/0/content/0/content/0/text"),
        Some(&serde_json::json!("A1"))
    );
}

#[test]
fn tiptap_mindmap_placeholder_round_trip_preserves_mindmap_attrs() {
    let doc: TiptapNode = serde_json::from_value(serde_json::json!({
        "type": "doc",
        "content": [{
            "type": "paragraph",
            "attrs": {
                "blockId": "mind_1",
                "mnoteBlockType": "mindmap",
                "mindmapId": "mind_1",
                "rootNodeId": "root",
                "projectionVersion": 1
            }
        }]
    }))
    .expect("mindmap placeholder JSON should parse");

    let imported = EditorBlockDocumentTiptapBridge::from_tiptap_doc("doc_mindmap", &doc)
        .expect("mindmap placeholder should import");
    assert_eq!(imported.blocks[0].block_type, EditorBlockType::Mindmap);
    assert_eq!(
        imported.blocks[0].props.extra.get("mindmapId"),
        Some(&serde_json::json!("mind_1"))
    );

    let exported =
        EditorBlockDocumentTiptapBridge::to_tiptap_doc(&imported).expect("mindmap should export");
    let exported_value = serde_json::to_value(exported).expect("exported doc should serialize");
    assert_eq!(
        exported_value.pointer("/content/0/attrs/mnoteBlockType"),
        Some(&serde_json::json!("mindmap"))
    );
    assert_eq!(
        exported_value.pointer("/content/0/attrs/mindmapId"),
        Some(&serde_json::json!("mind_1"))
    );
}
