use leptos_tiptap::TiptapSelectionState;
use serde::Serialize;
use serde_json::Value;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{window, Element, Node};

use crate::{
    editor_root_element,
    editor_runtime::block_dnd::{direct_block_from_element, top_level_block_index},
};

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SelectionPayload {
    pub(crate) selection: TiptapSelectionState,
    pub(crate) summary: String,
    pub(crate) editor_focused: bool,
    pub(crate) current_block_index: Option<usize>,
    pub(crate) current_block_id: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct CurrentBlockInfo {
    pub(crate) index: Option<usize>,
    pub(crate) block_id: Option<String>,
}

pub(crate) fn node_is_within_root(node: Node, root: &Element) -> bool {
    let mut current = Some(node);
    while let Some(node) = current {
        if node.is_same_node(Some(root)) {
            return true;
        }
        current = node.parent_node();
    }
    false
}

pub(crate) fn active_editor_text_selection() -> Option<web_sys::Selection> {
    let selection = window().and_then(|win| win.get_selection().ok().flatten())?;
    if selection.is_collapsed() {
        return None;
    }

    let root = editor_root_element()?;
    let anchor_inside = selection
        .anchor_node()
        .map(|node| node_is_within_root(node, &root))
        .unwrap_or(false);
    let focus_inside = selection
        .focus_node()
        .map(|node| node_is_within_root(node, &root))
        .unwrap_or(false);

    if anchor_inside || focus_inside {
        Some(selection)
    } else {
        None
    }
}

pub(crate) fn has_active_text_selection() -> bool {
    active_editor_text_selection().is_some()
}

pub(crate) fn block_index_from_selection() -> Option<usize> {
    let selection = window().and_then(|win| win.get_selection().ok().flatten())?;
    let anchor_node = selection.anchor_node()?;
    let element = anchor_node
        .dyn_ref::<Element>()
        .cloned()
        .or_else(|| anchor_node.parent_element())?;
    let root = editor_root_element()?;
    let block = direct_block_from_element(element, &root)?;
    top_level_block_index(&root, &block)
}

pub(crate) fn selection_summary(selection: &TiptapSelectionState) -> String {
    let block = if selection.h1 {
        "一级标题"
    } else if selection.h2 {
        "二级标题"
    } else if selection.h3 {
        "三级标题"
    } else if selection.task_list {
        "Todo 列表"
    } else if selection.ordered_list {
        "有序列表"
    } else if selection.bullet_list {
        "无序列表"
    } else if selection.blockquote {
        "引用块"
    } else if selection.paragraph {
        "段落"
    } else {
        "未定位"
    };

    let mut marks = Vec::new();
    if selection.bold {
        marks.push("Bold");
    }
    if selection.italic {
        marks.push("Italic");
    }
    if selection.underline {
        marks.push("Underline");
    }
    if selection.strike {
        marks.push("Strike");
    }
    if selection.highlight {
        marks.push("Highlight");
    }
    if selection.text_style {
        marks.push("Color");
    }
    if selection.link {
        marks.push("Link");
    }

    let mark_text = if marks.is_empty() {
        "无行内样式".to_string()
    } else {
        marks.join(" / ")
    };

    format!("当前块：{block} | 行内样式：{mark_text}")
}

pub(crate) fn block_id_from_element(block: &Element) -> Option<String> {
    block
        .get_attribute("data-block-id")
        .map(|value| value.trim().to_string())
        .filter(|value| !value.is_empty())
}

pub(crate) fn block_id_from_json_node(node: &Value) -> Option<String> {
    node.get("attrs")
        .and_then(|attrs| attrs.get("blockId"))
        .and_then(Value::as_str)
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(str::to_string)
}

pub(crate) fn runtime_block_id_from_index(index: usize) -> Option<String> {
    editor_root_element()
        .and_then(|root| root.children().item(index as u32))
        .and_then(|block| block_id_from_element(&block))
        .or_else(|| {
            editor_root_element()
                .and_then(|root| {
                    js_sys::Reflect::get(root.as_ref(), &JsValue::from_str("editor")).ok()
                })
                .and_then(|editor_value| {
                    js_sys::Reflect::get(&editor_value, &JsValue::from_str("getJSON"))
                        .ok()
                        .and_then(|callback| callback.dyn_into::<js_sys::Function>().ok())
                        .and_then(|callback| callback.call0(&editor_value).ok())
                })
                .and_then(|value| serde_wasm_bindgen::from_value::<Value>(value).ok())
                .and_then(|document| {
                    document
                        .get("content")
                        .and_then(Value::as_array)
                        .and_then(|content| content.get(index))
                        .and_then(block_id_from_json_node)
                })
        })
}

pub(crate) fn selection_payload(
    selection: &TiptapSelectionState,
    editor_focused: bool,
    current_block_index: Option<usize>,
) -> SelectionPayload {
    SelectionPayload {
        selection: selection.clone(),
        summary: selection_summary(selection),
        editor_focused,
        current_block_index,
        current_block_id: current_block_index.and_then(runtime_block_id_from_index),
    }
}

pub(crate) fn current_block_info_from_index(index: Option<usize>) -> CurrentBlockInfo {
    index
        .map(|value| CurrentBlockInfo {
            index: Some(value),
            block_id: runtime_block_id_from_index(value),
        })
        .unwrap_or(CurrentBlockInfo {
            index: None,
            block_id: None,
        })
}
