use leptos_tiptap::{TiptapContent, TiptapEditorHandle, TiptapInsertContentOptions, TiptapRange};
use serde_json::json;
use serde_json::Value;

use crate::editor_runtime::attachment_links;
use crate::editor_runtime::block_hover_state::DropPlacement;

/// 通过 Tiptap `delete_selection()` 删除当前选中的本地附件链接，保留撤销/重做历史。
///
/// 删除成功返回 `Ok(())`；当前选区不是本地附件链接或删除失败时返回 `Err(message)`。
pub(crate) fn delete_selected_attachment_link_with_history(
    editor: &TiptapEditorHandle,
) -> Result<(), String> {
    // 先确认当前选区确实是本地附件链接。
    if !attachment_links::is_local_attachment_link_selected() {
        return Err("当前选区不是本地附件链接".to_string());
    }
    if let Some(range) = attachment_links::selected_local_attachment_link_range() {
        let _ = editor.set_text_selection(range);
    }
    // 继续走 Tiptap 命令路径，保留撤销/重做历史。
    editor.delete_selection().map_err(|error| error.to_string())
}

fn prosemirror_node_size(node: &Value) -> Option<u32> {
    match node.get("type").and_then(Value::as_str) {
        Some("text") => Some(
            node.get("text")
                .and_then(Value::as_str)
                .map(|text| text.encode_utf16().count() as u32)
                .unwrap_or(0),
        ),
        Some("hardBreak") | Some("horizontalRule") => Some(1),
        _ => {
            let Some(children) = node.get("content").and_then(Value::as_array) else {
                return Some(match node.get("type").and_then(Value::as_str) {
                    Some("paragraph") | Some("heading") | Some("blockquote")
                    | Some("codeBlock") | Some("bulletList") | Some("orderedList")
                    | Some("taskList") | Some("listItem") | Some("taskItem") | Some("table")
                    | Some("tableRow") | Some("tableCell") | Some("tableHeader") => 2,
                    _ => 1,
                });
            };

            let content_size = children.iter().try_fold(0_u32, |acc, child| {
                prosemirror_node_size(child).map(|size| acc + size)
            })?;
            Some(content_size + 2)
        }
    }
}

fn top_level_block_full_range(document: &Value, index: usize) -> Option<TiptapRange> {
    let content = document.get("content").and_then(Value::as_array)?;
    let mut position = 0_u32;

    for (current_index, node) in content.iter().enumerate() {
        let node_size = prosemirror_node_size(node)?;
        if current_index == index {
            return Some(TiptapRange {
                from: position,
                to: position + node_size,
            });
        }
        position += node_size;
    }

    None
}

/// 通过 Tiptap `delete_range()` 删除顶层块，避免使用 `set_content()` 整文替换绕过 history。
pub(crate) fn delete_top_level_block_with_history(
    editor: &TiptapEditorHandle,
    index: usize,
) -> Result<(), String> {
    let document = editor
        .get_json()
        .map_err(|err| format!("读取当前 JSON 失败：{err}"))?;
    let content_len = document
        .get("content")
        .and_then(Value::as_array)
        .map(Vec::len)
        .unwrap_or(0);

    if index >= content_len {
        return Err(format!("找不到第 {index} 个块"));
    }

    let range = top_level_block_full_range(&document, index)
        .ok_or_else(|| format!("找不到第 {} 个块的删除范围", index + 1))?;

    if content_len <= 1 {
        editor
            .insert_content_at(
                range,
                TiptapContent::json(json!({ "type": "paragraph" })),
                Some(TiptapInsertContentOptions {
                    update_selection: Some(true),
                    ..Default::default()
                }),
            )
            .map_err(|err| format!("删除当前块失败：{err}"))?;
    } else {
        editor
            .delete_range(range)
            .map_err(|err| format!("删除当前块失败：{err}"))?;
    }

    editor
        .focus()
        .map_err(|err| format!("聚焦编辑器失败：{err}"))
}

/// 通过 Tiptap transaction 移动顶层块，保留撤销/重做历史。
///
/// 先删除源块，再在调整后的目标位置插入。
pub(crate) fn move_top_level_block_with_history(
    editor: &TiptapEditorHandle,
    source_index: usize,
    target_index: usize,
    placement: DropPlacement,
) -> Result<(), String> {
    let document = editor
        .get_json()
        .map_err(|err| format!("读取当前 JSON 失败：{err}"))?;
    let content = document
        .get("content")
        .and_then(Value::as_array)
        .ok_or_else(|| "文档content 数组缺失".to_string())?;
    if source_index >= content.len() || target_index >= content.len() {
        return Err("移动块索引超出范围".to_string());
    }
    let source_block = content[source_index].clone();

    let mut insert_at = match placement {
        DropPlacement::Before => target_index,
        DropPlacement::After => target_index + 1,
    };
    if source_index < insert_at {
        insert_at = insert_at.saturating_sub(1);
    }

    if insert_at == source_index {
        return editor
            .focus()
            .map_err(|err| format!("聚焦编辑器失败：{err}"));
    }

    delete_top_level_block_with_history(editor, source_index)?;

    let document = editor
        .get_json()
        .map_err(|err| format!("重新读取 JSON 失败：{err}"))?;
    let content_len_after_move = document
        .get("content")
        .and_then(Value::as_array)
        .map(Vec::len)
        .unwrap_or(0);
    let insert_position = if content_len_after_move == 0 {
        0
    } else if insert_at >= content_len_after_move {
        top_level_block_full_range(&document, content_len_after_move - 1)
            .map(|range| range.to)
            .ok_or_else(|| "无法计算文档末尾插入位置".to_string())?
    } else {
        top_level_block_full_range(&document, insert_at)
            .map(|range| range.from)
            .ok_or_else(|| format!("无法计算第 {insert_at} 个块的插入位置"))?
    };

    editor
        .insert_content_at(
            TiptapRange {
                from: insert_position,
                to: insert_position,
            },
            TiptapContent::json(source_block),
            Some(TiptapInsertContentOptions {
                update_selection: Some(true),
                ..Default::default()
            }),
        )
        .map_err(|err| format!("移动块失败：{err}"))?;

    editor
        .focus()
        .map_err(|err| format!("聚焦编辑器失败：{err}"))
}

/// 通过 Tiptap transaction 复制顶层块并插入其后，保留撤销/重做历史。
pub(crate) fn duplicate_top_level_block_with_history(
    editor: &TiptapEditorHandle,
    source_index: usize,
) -> Result<(), String> {
    let document = editor
        .get_json()
        .map_err(|err| format!("读取当前 JSON 失败：{err}"))?;
    let content = document
        .get("content")
        .and_then(Value::as_array)
        .ok_or_else(|| "文档content 数组缺失".to_string())?;
    if source_index >= content.len() {
        return Err("复制块索引超出范围".to_string());
    }
    let source_block = content[source_index].clone();

    let range = top_level_block_full_range(&document, source_index)
        .ok_or_else(|| format!("无法计算第 {source_index} 个块的位置"))?;

    // 插入到源块之后
    editor
        .insert_content_at(
            TiptapRange {
                from: range.to,
                to: range.to,
            },
            TiptapContent::json(source_block),
            Some(TiptapInsertContentOptions {
                update_selection: Some(true),
                ..Default::default()
            }),
        )
        .map_err(|err| format!("复制块失败：{err}"))?;

    editor
        .focus()
        .map_err(|err| format!("聚焦编辑器失败：{err}"))
}
