use crate::editor::model::{
    BlockProps, ContentNode, ContentNodePayload, EditorBlock, EditorBlockDocument, EditorBlockType,
    TextMark,
};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TiptapBlockType {
    Paragraph,
    Heading,
    BulletList,
    OrderedList,
    TaskList,
    ListItem,
    TaskItem,
    Blockquote,
    CodeBlock,
    Text,
    HardBreak,
    Doc,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TiptapHeadingAttrs {
    pub level: u8,
    #[serde(default)]
    pub block_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct TiptapTaskItemAttrs {
    #[serde(default)]
    pub checked: bool,
    #[serde(default)]
    pub block_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct TiptapParagraphAttrs {
    #[serde(default)]
    pub block_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct TiptapListAttrs {
    #[serde(default)]
    pub block_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct TiptapListItemAttrs {
    #[serde(default)]
    pub block_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct TiptapBlockquoteAttrs {
    #[serde(default)]
    pub block_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TiptapCodeBlockAttrs {
    pub language: Option<String>,
    pub block_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TiptapMark {
    pub r#type: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum TiptapNode {
    Doc {
        content: Vec<TiptapNode>,
    },
    Paragraph {
        #[serde(default)]
        attrs: TiptapParagraphAttrs,
        #[serde(default)]
        content: Vec<TiptapNode>,
    },
    Heading {
        attrs: TiptapHeadingAttrs,
        #[serde(default)]
        content: Vec<TiptapNode>,
    },
    BulletList {
        #[serde(default)]
        attrs: TiptapListAttrs,
        #[serde(default)]
        content: Vec<TiptapNode>,
    },
    OrderedList {
        #[serde(default)]
        attrs: TiptapListAttrs,
        #[serde(default)]
        content: Vec<TiptapNode>,
    },
    TaskList {
        #[serde(default)]
        attrs: TiptapListAttrs,
        #[serde(default)]
        content: Vec<TiptapNode>,
    },
    ListItem {
        #[serde(default)]
        attrs: TiptapListItemAttrs,
        #[serde(default)]
        content: Vec<TiptapNode>,
    },
    TaskItem {
        attrs: TiptapTaskItemAttrs,
        #[serde(default)]
        content: Vec<TiptapNode>,
    },
    Blockquote {
        #[serde(default)]
        attrs: TiptapBlockquoteAttrs,
        #[serde(default)]
        content: Vec<TiptapNode>,
    },
    CodeBlock {
        attrs: TiptapCodeBlockAttrs,
        #[serde(default)]
        content: Vec<TiptapNode>,
    },
    Text {
        text: String,
        #[serde(default)]
        marks: Vec<TiptapMark>,
    },
    HardBreak,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EditorBlockDocumentTiptapError {
    UnsupportedNode(&'static str),
    InvalidDocument(&'static str),
}

pub struct EditorBlockDocumentTiptapBridge;

impl EditorBlockDocumentTiptapBridge {
    pub fn to_tiptap_doc(
        document: &EditorBlockDocument,
    ) -> Result<TiptapNode, EditorBlockDocumentTiptapError> {
        let mut content = Vec::new();
        for block_id in &document.root_block_ids {
            let block = document
                .blocks
                .iter()
                .find(|block| &block.block_id == block_id)
                .ok_or(EditorBlockDocumentTiptapError::InvalidDocument(
                    "根块不存在",
                ))?;
            content.push(block_to_tiptap_node(block)?);
        }
        Ok(TiptapNode::Doc { content })
    }

    pub fn from_tiptap_doc(
        document_id: impl Into<String>,
        doc: &TiptapNode,
    ) -> Result<EditorBlockDocument, EditorBlockDocumentTiptapError> {
        match doc {
            TiptapNode::Doc { content } => {
                let mut blocks = Vec::new();
                let mut root_block_ids = Vec::new();
                for (index, node) in content.iter().enumerate() {
                    let block = node_to_block(node, index)?;
                    root_block_ids.push(block.block_id.clone());
                    blocks.push(block);
                }
                Ok(EditorBlockDocument {
                    document_id: document_id.into(),
                    root_block_ids,
                    blocks,
                })
            }
            _ => Err(EditorBlockDocumentTiptapError::InvalidDocument(
                "顶层必须是 doc",
            )),
        }
    }
}

fn block_to_tiptap_node(block: &EditorBlock) -> Result<TiptapNode, EditorBlockDocumentTiptapError> {
    let content = text_nodes_to_tiptap(&block.content_nodes)?;
    match block.block_type {
        EditorBlockType::Paragraph => Ok(TiptapNode::Paragraph {
            attrs: TiptapParagraphAttrs {
                block_id: Some(block.block_id.clone()),
            },
            content,
        }),
        EditorBlockType::Heading => Ok(TiptapNode::Heading {
            attrs: TiptapHeadingAttrs {
                level: block.props.heading_level.unwrap_or(1),
                block_id: Some(block.block_id.clone()),
            },
            content,
        }),
        EditorBlockType::BulletListItem => Ok(TiptapNode::BulletList {
            attrs: TiptapListAttrs {
                block_id: Some(block.block_id.clone()),
            },
            content: vec![TiptapNode::ListItem {
                attrs: TiptapListItemAttrs {
                    block_id: Some(block.block_id.clone()),
                },
                content: vec![TiptapNode::Paragraph {
                    attrs: TiptapParagraphAttrs {
                        block_id: Some(block.block_id.clone()),
                    },
                    content,
                }],
            }],
        }),
        EditorBlockType::NumberedListItem => Ok(TiptapNode::OrderedList {
            attrs: TiptapListAttrs {
                block_id: Some(block.block_id.clone()),
            },
            content: vec![TiptapNode::ListItem {
                attrs: TiptapListItemAttrs {
                    block_id: Some(block.block_id.clone()),
                },
                content: vec![TiptapNode::Paragraph {
                    attrs: TiptapParagraphAttrs {
                        block_id: Some(block.block_id.clone()),
                    },
                    content,
                }],
            }],
        }),
        EditorBlockType::Todo => Ok(TiptapNode::TaskList {
            attrs: TiptapListAttrs {
                block_id: Some(block.block_id.clone()),
            },
            content: vec![TiptapNode::TaskItem {
                attrs: TiptapTaskItemAttrs {
                    checked: block.props.checked.unwrap_or(false),
                    block_id: Some(block.block_id.clone()),
                },
                content: vec![TiptapNode::Paragraph {
                    attrs: TiptapParagraphAttrs {
                        block_id: Some(block.block_id.clone()),
                    },
                    content,
                }],
            }],
        }),
        EditorBlockType::Quote => Ok(TiptapNode::Blockquote {
            attrs: TiptapBlockquoteAttrs {
                block_id: Some(block.block_id.clone()),
            },
            content: vec![TiptapNode::Paragraph {
                attrs: TiptapParagraphAttrs {
                    block_id: Some(block.block_id.clone()),
                },
                content,
            }],
        }),
        EditorBlockType::CodeBlock => Ok(TiptapNode::CodeBlock {
            attrs: TiptapCodeBlockAttrs {
                language: block.props.language.clone(),
                block_id: Some(block.block_id.clone()),
            },
            content,
        }),
        _ => Err(EditorBlockDocumentTiptapError::UnsupportedNode(
            "当前块型暂不支持导出到 Tiptap",
        )),
    }
}

fn node_to_block(
    node: &TiptapNode,
    index: usize,
) -> Result<EditorBlock, EditorBlockDocumentTiptapError> {
    let fallback_block_id = format!("block_{}", index + 1);
    match node {
        TiptapNode::Paragraph { attrs, content } => Ok(EditorBlock {
            block_id: attrs.block_id.clone().unwrap_or(fallback_block_id),
            block_type: EditorBlockType::Paragraph,
            props: BlockProps::default(),
            content_nodes: text_nodes_from_tiptap(content)?,
            child_block_ids: vec![],
        }),
        TiptapNode::Heading { attrs, content } => Ok(EditorBlock {
            block_id: attrs.block_id.clone().unwrap_or(fallback_block_id),
            block_type: EditorBlockType::Heading,
            props: BlockProps {
                heading_level: Some(attrs.level),
                ..BlockProps::default()
            },
            content_nodes: text_nodes_from_tiptap(content)?,
            child_block_ids: vec![],
        }),
        TiptapNode::BulletList { attrs, content } => list_node_to_block(
            attrs.block_id.clone().unwrap_or(fallback_block_id),
            EditorBlockType::BulletListItem,
            content,
        ),
        TiptapNode::OrderedList { attrs, content } => list_node_to_block(
            attrs.block_id.clone().unwrap_or(fallback_block_id),
            EditorBlockType::NumberedListItem,
            content,
        ),
        TiptapNode::TaskList { attrs, content } => {
            list_task_node_to_block(attrs.block_id.clone().unwrap_or(fallback_block_id), content)
        }
        TiptapNode::Blockquote { attrs, content } => Ok(EditorBlock {
            block_id: attrs.block_id.clone().unwrap_or(fallback_block_id),
            block_type: EditorBlockType::Quote,
            props: BlockProps::default(),
            content_nodes: text_nodes_from_tiptap(extract_block_container_content(content)?)?,
            child_block_ids: vec![],
        }),
        TiptapNode::CodeBlock { attrs, content } => Ok(EditorBlock {
            block_id: attrs.block_id.clone().unwrap_or(fallback_block_id),
            block_type: EditorBlockType::CodeBlock,
            props: BlockProps {
                language: attrs.language.clone(),
                ..BlockProps::default()
            },
            content_nodes: text_nodes_from_tiptap(content)?,
            child_block_ids: vec![],
        }),
        _ => Err(EditorBlockDocumentTiptapError::UnsupportedNode(
            "当前节点暂不支持导入为 EditorBlockDocument",
        )),
    }
}

fn list_node_to_block(
    block_id: String,
    block_type: EditorBlockType,
    content: &[TiptapNode],
) -> Result<EditorBlock, EditorBlockDocumentTiptapError> {
    Ok(EditorBlock {
        block_id,
        block_type,
        props: BlockProps::default(),
        content_nodes: text_nodes_from_tiptap(extract_list_item_inline_content(content)?)?,
        child_block_ids: vec![],
    })
}

fn list_task_node_to_block(
    block_id: String,
    content: &[TiptapNode],
) -> Result<EditorBlock, EditorBlockDocumentTiptapError> {
    let Some(first) = content.first() else {
        return Err(EditorBlockDocumentTiptapError::InvalidDocument(
            "任务列表节点缺少 task_item",
        ));
    };
    let (checked, item_content) = match first {
        TiptapNode::TaskItem { attrs, content } => (
            Some(attrs.checked),
            extract_block_container_content(content)?,
        ),
        _ => {
            return Err(EditorBlockDocumentTiptapError::InvalidDocument(
                "任务列表节点首子节点类型不正确",
            ))
        }
    };
    Ok(EditorBlock {
        block_id,
        block_type: EditorBlockType::Todo,
        props: BlockProps {
            checked,
            ..BlockProps::default()
        },
        content_nodes: text_nodes_from_tiptap(item_content)?,
        child_block_ids: vec![],
    })
}

fn extract_list_item_inline_content(
    content: &[TiptapNode],
) -> Result<&[TiptapNode], EditorBlockDocumentTiptapError> {
    let Some(first) = content.first() else {
        return Err(EditorBlockDocumentTiptapError::InvalidDocument(
            "列表节点缺少 list_item/task_item",
        ));
    };
    match first {
        TiptapNode::ListItem { content, .. } | TiptapNode::TaskItem { content, .. } => {
            extract_block_container_content(content)
        }
        _ => Err(EditorBlockDocumentTiptapError::InvalidDocument(
            "列表节点首子节点类型不正确",
        )),
    }
}

fn extract_block_container_content(
    content: &[TiptapNode],
) -> Result<&[TiptapNode], EditorBlockDocumentTiptapError> {
    let Some(first) = content.first() else {
        return Ok(content);
    };
    match first {
        TiptapNode::Paragraph { content, .. } => Ok(content),
        _ => Ok(content),
    }
}

fn text_nodes_to_tiptap(
    nodes: &[ContentNode],
) -> Result<Vec<TiptapNode>, EditorBlockDocumentTiptapError> {
    let mut out = Vec::new();
    for node in nodes {
        match &node.payload {
            ContentNodePayload::Text { text, marks } => out.push(TiptapNode::Text {
                text: text.clone(),
                marks: marks.iter().map(text_mark_to_tiptap).collect(),
            }),
            ContentNodePayload::HardBreak => out.push(TiptapNode::HardBreak),
            ContentNodePayload::ReferenceToken { .. } => {
                return Err(EditorBlockDocumentTiptapError::UnsupportedNode(
                    "引用 token 暂不映射到 Tiptap 文本合同",
                ))
            }
        }
    }
    Ok(out)
}

fn text_nodes_from_tiptap(
    nodes: &[TiptapNode],
) -> Result<Vec<ContentNode>, EditorBlockDocumentTiptapError> {
    let mut out = Vec::new();
    for node in nodes {
        match node {
            TiptapNode::Text { text, marks } => out.push(ContentNode {
                payload: ContentNodePayload::Text {
                    text: text.clone(),
                    marks: marks.iter().filter_map(tiptap_mark_to_text_mark).collect(),
                },
                attrs: BTreeMap::new(),
            }),
            TiptapNode::HardBreak => out.push(ContentNode {
                payload: ContentNodePayload::HardBreak,
                attrs: BTreeMap::new(),
            }),
            _ => {
                return Err(EditorBlockDocumentTiptapError::UnsupportedNode(
                    "当前只支持文本节点内容",
                ))
            }
        }
    }
    Ok(out)
}

fn text_mark_to_tiptap(mark: &TextMark) -> TiptapMark {
    let r#type = match mark {
        TextMark::Bold => "bold",
        TextMark::Italic => "italic",
        TextMark::Underline => "underline",
        TextMark::Strike => "strike",
        TextMark::Code => "code",
    };
    TiptapMark {
        r#type: r#type.to_string(),
    }
}

fn tiptap_mark_to_text_mark(mark: &TiptapMark) -> Option<TextMark> {
    match mark.r#type.as_str() {
        "bold" => Some(TextMark::Bold),
        "italic" => Some(TextMark::Italic),
        "underline" => Some(TextMark::Underline),
        "strike" => Some(TextMark::Strike),
        "code" => Some(TextMark::Code),
        _ => None,
    }
}
