use leptos::prelude::{GetUntracked, ReadSignal, Set, WriteSignal};

use crate::{
    editor_runtime::block_hover_state::HoveredBlockState,
    editor_runtime::dom_selection::{active_editor_text_selection, has_active_text_selection},
    editor_runtime::editor_focus::active_editor_stage,
    editor_stage_element,
};
use web_sys::{window, DomRect, Element};

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct FloatingToolbarAnchor {
    pub(crate) top: f64,
    pub(crate) left: f64,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct ImageToolbarAnchor {
    pub(crate) top: f64,
    pub(crate) left: f64,
    pub(crate) align: String,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct TableOverlayAnchor {
    pub(crate) top: f64,
    pub(crate) left: f64,
    pub(crate) width: f64,
    pub(crate) height: f64,
    pub(crate) rows: usize,
    pub(crate) cols: usize,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum TableSelectionKind {
    Row,
    Column,
}

impl TableSelectionKind {
    pub(crate) fn id(self) -> &'static str {
        match self {
            Self::Row => "row",
            Self::Column => "column",
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct TableSelectionOverlayState {
    pub(crate) kind: TableSelectionKind,
    pub(crate) index: usize,
}

pub(crate) fn table_overlay_anchor_from_table(table: &Element) -> Option<TableOverlayAnchor> {
    let stage = editor_stage_element()?;
    let table_rect = table.get_bounding_client_rect();
    let stage_rect = stage.get_bounding_client_rect();
    if table_rect.width() <= 0.0 || table_rect.height() <= 0.0 {
        return None;
    }

    let rows = table.get_elements_by_tag_name("tr").length() as usize;
    let cols = table
        .query_selector("tr")
        .ok()
        .flatten()
        .map(|row| row.children().length() as usize)
        .unwrap_or(0);
    if rows == 0 || cols == 0 {
        return None;
    }

    Some(TableOverlayAnchor {
        top: table_rect.top() - stage_rect.top(),
        left: table_rect.left() - stage_rect.left(),
        width: table_rect.width(),
        height: table_rect.height(),
        rows,
        cols,
    })
}

pub(crate) fn current_table_overlay_anchor() -> Option<TableOverlayAnchor> {
    let table = window()
        .and_then(|win| win.document())
        .and_then(|document| {
            document
                .query_selector(".editor-surface .ProseMirror table")
                .ok()
                .flatten()
        })?;
    table_overlay_anchor_from_table(&table)
}

pub(crate) fn table_row_aux_style(anchor: &TableOverlayAnchor, index: usize) -> String {
    let row_height = anchor.height / anchor.rows.max(1) as f64;
    format!(
        "left:-24px;top:{:.1}px;",
        (row_height * index as f64) + (row_height / 2.0) - 9.0
    )
}

pub(crate) fn table_col_aux_style(anchor: &TableOverlayAnchor, index: usize) -> String {
    let col_width = anchor.width / anchor.cols.max(1) as f64;
    format!(
        "left:{:.1}px;top:-24px;",
        (col_width * index as f64) + (col_width / 2.0) - 9.0
    )
}

pub(crate) fn table_selection_overlay_style(
    anchor: &TableOverlayAnchor,
    selection: &TableSelectionOverlayState,
) -> String {
    match selection.kind {
        TableSelectionKind::Row => {
            let row_height = anchor.height / anchor.rows.max(1) as f64;
            format!(
                "left:0;top:{:.1}px;width:{:.1}px;height:{:.1}px;",
                row_height * selection.index as f64,
                anchor.width,
                row_height
            )
        }
        TableSelectionKind::Column => {
            let col_width = anchor.width / anchor.cols.max(1) as f64;
            format!(
                "left:{:.1}px;top:0;width:{:.1}px;height:{:.1}px;",
                col_width * selection.index as f64,
                col_width,
                anchor.height
            )
        }
    }
}

pub(crate) fn floating_toolbar_anchor_from_selection() -> Option<FloatingToolbarAnchor> {
    let rect = selection_bounding_rect()?;
    if rect.width() <= 0.0 && rect.height() <= 0.0 {
        return None;
    }

    let stage = editor_stage_element()?;
    let stage_rect = stage.get_bounding_client_rect();

    Some(FloatingToolbarAnchor {
        top: (rect.top() - stage_rect.top()).max(60.0),
        left: rect.left() + (rect.width() / 2.0) - stage_rect.left(),
    })
}

pub(crate) fn image_toolbar_anchor_from_image(image: &Element) -> Option<ImageToolbarAnchor> {
    let stage = editor_stage_element()?;
    let rect = image.get_bounding_client_rect();
    if rect.width() <= 0.0 && rect.height() <= 0.0 {
        return None;
    }
    let stage_rect = stage.get_bounding_client_rect();
    let align = image
        .get_attribute("data-align")
        .filter(|value| !value.trim().is_empty())
        .unwrap_or_else(|| "left".to_string());

    Some(ImageToolbarAnchor {
        top: (rect.top() - stage_rect.top()).max(60.0),
        left: rect.left() + (rect.width() / 2.0) - stage_rect.left(),
        align,
    })
}

pub(crate) fn image_element_from_target(target: web_sys::EventTarget) -> Option<Element> {
    let element = crate::editor_runtime::dom_events::target_element(target)?;
    if element.tag_name().eq_ignore_ascii_case("img")
        && element
            .closest(".editor-surface .ProseMirror")
            .ok()
            .flatten()
            .is_some()
    {
        return Some(element);
    }

    element
        .closest(".editor-surface .ProseMirror img[src]")
        .ok()
        .flatten()
}

pub(crate) fn selection_bounding_rect() -> Option<DomRect> {
    let selection = active_editor_text_selection()?;
    let range = selection.get_range_at(0).ok()?;
    Some(range.get_bounding_client_rect())
}

pub(crate) fn viewport_dimensions() -> (f64, f64) {
    let viewport_width = window()
        .and_then(|win| win.inner_width().ok())
        .and_then(|value| value.as_f64())
        .unwrap_or(1024.0)
        .max(320.0);
    let viewport_height = window()
        .and_then(|win| win.inner_height().ok())
        .and_then(|value| value.as_f64())
        .unwrap_or(768.0)
        .max(240.0);
    (viewport_width, viewport_height)
}

pub(crate) fn clamp_overlay_anchor(
    raw_top: f64,
    raw_left: f64,
    raw_anchor_top: f64,
    overlay_width: f64,
    overlay_height: f64,
    gap: f64,
) -> (f64, f64) {
    let (viewport_width, viewport_height) = viewport_dimensions();
    let clamped_left = raw_left.clamp(gap, (viewport_width - overlay_width - gap).max(gap));
    let below_limit = viewport_height - gap;
    let clamped_top = if raw_top + overlay_height > below_limit {
        (raw_anchor_top - overlay_height - gap).clamp(gap, below_limit - 120.0)
    } else {
        raw_top.clamp(gap, below_limit - 120.0)
    };
    (clamped_top, clamped_left)
}

pub(crate) fn sync_text_selection_overlay(
    set_text_selection_active: WriteSignal<bool>,
    set_floating_toolbar_anchor: WriteSignal<Option<FloatingToolbarAnchor>>,
) -> bool {
    let toolbar_anchor = floating_toolbar_anchor_from_selection();
    let has_text_selection = toolbar_anchor.is_some();
    set_text_selection_active.set(has_text_selection);
    set_floating_toolbar_anchor.set(toolbar_anchor);
    has_text_selection
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn try_sync_editor_overlay_state(
    set_editor_focused: WriteSignal<bool>,
    set_text_selection_active: WriteSignal<bool>,
    set_floating_toolbar_anchor: WriteSignal<Option<FloatingToolbarAnchor>>,
    set_slash_open: WriteSignal<bool>,
    set_turn_into_open: WriteSignal<bool>,
    set_hovered_block: WriteSignal<Option<HoveredBlockState>>,
    set_block_menu_anchor: WriteSignal<Option<HoveredBlockState>>,
    set_block_menu_open: WriteSignal<bool>,
) -> bool {
    let focused = active_editor_stage();
    if set_editor_focused.try_set(focused).is_none() {
        return false;
    }

    let toolbar_anchor = floating_toolbar_anchor_from_selection();
    let has_text_selection = toolbar_anchor.is_some();
    if set_text_selection_active
        .try_set(has_text_selection)
        .is_none()
    {
        return false;
    }
    if set_floating_toolbar_anchor
        .try_set(toolbar_anchor)
        .is_none()
    {
        return false;
    }

    if focused && has_text_selection {
        if !try_close_block_menu_overlays(
            set_slash_open,
            set_turn_into_open,
            set_hovered_block,
            set_block_menu_anchor,
            set_block_menu_open,
        ) {
            return false;
        }
    }

    true
}

pub(crate) fn toolbar_overlay_locked(
    turn_into_open: bool,
    color_menu_open: bool,
    more_menu_open: bool,
) -> bool {
    turn_into_open || color_menu_open || more_menu_open
}

/// 判断是否应自动关闭 toolbar overlay。
/// 当编辑器失焦、无选区且无锁定 toolbar，或 slash/block menu 打开时返回 true。
pub(crate) fn should_auto_close_toolbar_overlays(
    editor_focused: bool,
    text_selection_active: bool,
    turn_into_open: bool,
    color_menu_open: bool,
    more_menu_open: bool,
    slash_open: bool,
    block_menu_open: bool,
) -> bool {
    let toolbar_locked = toolbar_overlay_locked(turn_into_open, color_menu_open, more_menu_open);
    ((!editor_focused || !text_selection_active) && !toolbar_locked)
        || slash_open
        || block_menu_open
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn sync_overlays_on_selection_change(
    turn_into_open: ReadSignal<bool>,
    color_menu_open: ReadSignal<bool>,
    more_menu_open: ReadSignal<bool>,
    text_selection_active: ReadSignal<bool>,
    set_text_selection_active: WriteSignal<bool>,
    set_floating_toolbar_anchor: WriteSignal<Option<FloatingToolbarAnchor>>,
    set_image_toolbar_anchor: WriteSignal<Option<ImageToolbarAnchor>>,
    set_turn_into_open: WriteSignal<bool>,
    set_block_menu_open: WriteSignal<bool>,
    set_block_menu_anchor: WriteSignal<Option<HoveredBlockState>>,
    set_hovered_block: WriteSignal<Option<HoveredBlockState>>,
) {
    let toolbar_locked = toolbar_overlay_locked(
        turn_into_open.get_untracked(),
        color_menu_open.get_untracked(),
        more_menu_open.get_untracked(),
    );
    let has_text_selection = if has_active_text_selection() {
        sync_text_selection_overlay(set_text_selection_active, set_floating_toolbar_anchor)
    } else if toolbar_locked {
        text_selection_active.get_untracked()
    } else {
        set_text_selection_active.set(false);
        set_floating_toolbar_anchor.set(None);
        false
    };
    if has_text_selection {
        set_image_toolbar_anchor.set(None);
        if !toolbar_locked {
            set_turn_into_open.set(false);
        }
        set_block_menu_open.set(false);
        set_block_menu_anchor.set(None);
        set_hovered_block.set(None);
    }
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn close_editor_floating_overlays(
    set_slash_open: WriteSignal<bool>,
    set_slash_index: WriteSignal<usize>,
    set_slash_query: WriteSignal<String>,
    set_turn_into_open: WriteSignal<bool>,
    set_color_menu_open: WriteSignal<bool>,
    set_more_menu_open: WriteSignal<bool>,
    set_locked_toolbar_anchor: WriteSignal<Option<FloatingToolbarAnchor>>,
    set_text_selection_active: WriteSignal<bool>,
    set_floating_toolbar_anchor: WriteSignal<Option<FloatingToolbarAnchor>>,
    set_image_toolbar_anchor: WriteSignal<Option<ImageToolbarAnchor>>,
    set_table_toolbar_open: WriteSignal<bool>,
    set_table_options_open: WriteSignal<bool>,
    set_table_overlay_anchor: WriteSignal<Option<TableOverlayAnchor>>,
    set_table_selection_overlay: WriteSignal<Option<TableSelectionOverlayState>>,
    set_block_menu_open: WriteSignal<bool>,
    set_block_menu_anchor: WriteSignal<Option<HoveredBlockState>>,
    set_block_turn_into_open: WriteSignal<bool>,
    set_block_folded_title_open: WriteSignal<bool>,
) {
    set_slash_open.set(false);
    set_slash_index.set(0);
    set_slash_query.set(String::new());
    set_turn_into_open.set(false);
    set_color_menu_open.set(false);
    set_more_menu_open.set(false);
    set_locked_toolbar_anchor.set(None);
    set_text_selection_active.set(false);
    set_floating_toolbar_anchor.set(None);
    set_image_toolbar_anchor.set(None);
    set_table_toolbar_open.set(false);
    set_table_options_open.set(false);
    set_table_overlay_anchor.set(None);
    set_table_selection_overlay.set(None);
    set_block_menu_open.set(false);
    set_block_menu_anchor.set(None);
    set_block_turn_into_open.set(false);
    set_block_folded_title_open.set(false);
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn close_editor_floating_overlays_if_escape(
    slash_open: ReadSignal<bool>,
    turn_into_open: ReadSignal<bool>,
    color_menu_open: ReadSignal<bool>,
    more_menu_open: ReadSignal<bool>,
    image_toolbar_anchor: ReadSignal<Option<ImageToolbarAnchor>>,
    table_toolbar_open: ReadSignal<bool>,
    table_options_open: ReadSignal<bool>,
    block_menu_open: ReadSignal<bool>,
    set_slash_open: WriteSignal<bool>,
    set_slash_index: WriteSignal<usize>,
    set_slash_query: WriteSignal<String>,
    set_turn_into_open: WriteSignal<bool>,
    set_color_menu_open: WriteSignal<bool>,
    set_more_menu_open: WriteSignal<bool>,
    set_locked_toolbar_anchor: WriteSignal<Option<FloatingToolbarAnchor>>,
    set_text_selection_active: WriteSignal<bool>,
    set_floating_toolbar_anchor: WriteSignal<Option<FloatingToolbarAnchor>>,
    set_image_toolbar_anchor: WriteSignal<Option<ImageToolbarAnchor>>,
    set_table_toolbar_open: WriteSignal<bool>,
    set_table_options_open: WriteSignal<bool>,
    set_table_overlay_anchor: WriteSignal<Option<TableOverlayAnchor>>,
    set_table_selection_overlay: WriteSignal<Option<TableSelectionOverlayState>>,
    set_block_menu_open: WriteSignal<bool>,
    set_block_menu_anchor: WriteSignal<Option<HoveredBlockState>>,
    set_block_turn_into_open: WriteSignal<bool>,
    set_block_folded_title_open: WriteSignal<bool>,
) -> bool {
    let overlay_open = slash_open.try_get_untracked().unwrap_or(false)
        || turn_into_open.try_get_untracked().unwrap_or(false)
        || color_menu_open.try_get_untracked().unwrap_or(false)
        || more_menu_open.try_get_untracked().unwrap_or(false)
        || image_toolbar_anchor.try_get_untracked().flatten().is_some()
        || table_toolbar_open.try_get_untracked().unwrap_or(false)
        || table_options_open.try_get_untracked().unwrap_or(false)
        || block_menu_open.try_get_untracked().unwrap_or(false);

    if overlay_open {
        close_editor_floating_overlays(
            set_slash_open,
            set_slash_index,
            set_slash_query,
            set_turn_into_open,
            set_color_menu_open,
            set_more_menu_open,
            set_locked_toolbar_anchor,
            set_text_selection_active,
            set_floating_toolbar_anchor,
            set_image_toolbar_anchor,
            set_table_toolbar_open,
            set_table_options_open,
            set_table_overlay_anchor,
            set_table_selection_overlay,
            set_block_menu_open,
            set_block_menu_anchor,
            set_block_turn_into_open,
            set_block_folded_title_open,
        );
    }

    overlay_open
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn open_slash_menu_overlay(
    set_slash_open: WriteSignal<bool>,
    set_slash_index: WriteSignal<usize>,
    set_slash_query: WriteSignal<String>,
    set_turn_into_open: WriteSignal<bool>,
    set_color_menu_open: WriteSignal<bool>,
    set_more_menu_open: WriteSignal<bool>,
    set_locked_toolbar_anchor: WriteSignal<Option<FloatingToolbarAnchor>>,
    set_text_selection_active: WriteSignal<bool>,
    set_floating_toolbar_anchor: WriteSignal<Option<FloatingToolbarAnchor>>,
    set_image_toolbar_anchor: WriteSignal<Option<ImageToolbarAnchor>>,
    set_table_toolbar_open: WriteSignal<bool>,
    set_table_options_open: WriteSignal<bool>,
    set_table_overlay_anchor: WriteSignal<Option<TableOverlayAnchor>>,
    set_table_selection_overlay: WriteSignal<Option<TableSelectionOverlayState>>,
    set_block_menu_open: WriteSignal<bool>,
    set_block_menu_anchor: WriteSignal<Option<HoveredBlockState>>,
    set_block_turn_into_open: WriteSignal<bool>,
    set_block_folded_title_open: WriteSignal<bool>,
) {
    close_editor_floating_overlays(
        set_slash_open,
        set_slash_index,
        set_slash_query,
        set_turn_into_open,
        set_color_menu_open,
        set_more_menu_open,
        set_locked_toolbar_anchor,
        set_text_selection_active,
        set_floating_toolbar_anchor,
        set_image_toolbar_anchor,
        set_table_toolbar_open,
        set_table_options_open,
        set_table_overlay_anchor,
        set_table_selection_overlay,
        set_block_menu_open,
        set_block_menu_anchor,
        set_block_turn_into_open,
        set_block_folded_title_open,
    );
    set_slash_open.set(true);
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn open_image_toolbar_overlay(
    image_anchor: Option<ImageToolbarAnchor>,
    set_block_keyboard_mode: WriteSignal<bool>,
    set_hovered_block: WriteSignal<Option<HoveredBlockState>>,
    set_slash_open: WriteSignal<bool>,
    set_slash_index: WriteSignal<usize>,
    set_slash_query: WriteSignal<String>,
    set_turn_into_open: WriteSignal<bool>,
    set_color_menu_open: WriteSignal<bool>,
    set_more_menu_open: WriteSignal<bool>,
    set_locked_toolbar_anchor: WriteSignal<Option<FloatingToolbarAnchor>>,
    set_text_selection_active: WriteSignal<bool>,
    set_floating_toolbar_anchor: WriteSignal<Option<FloatingToolbarAnchor>>,
    set_image_toolbar_anchor: WriteSignal<Option<ImageToolbarAnchor>>,
    set_table_toolbar_open: WriteSignal<bool>,
    set_table_options_open: WriteSignal<bool>,
    set_table_overlay_anchor: WriteSignal<Option<TableOverlayAnchor>>,
    set_table_selection_overlay: WriteSignal<Option<TableSelectionOverlayState>>,
    set_block_menu_open: WriteSignal<bool>,
    set_block_menu_anchor: WriteSignal<Option<HoveredBlockState>>,
    set_block_turn_into_open: WriteSignal<bool>,
    set_block_folded_title_open: WriteSignal<bool>,
) {
    close_editor_floating_overlays(
        set_slash_open,
        set_slash_index,
        set_slash_query,
        set_turn_into_open,
        set_color_menu_open,
        set_more_menu_open,
        set_locked_toolbar_anchor,
        set_text_selection_active,
        set_floating_toolbar_anchor,
        set_image_toolbar_anchor,
        set_table_toolbar_open,
        set_table_options_open,
        set_table_overlay_anchor,
        set_table_selection_overlay,
        set_block_menu_open,
        set_block_menu_anchor,
        set_block_turn_into_open,
        set_block_folded_title_open,
    );
    set_block_keyboard_mode.set(false);
    set_hovered_block.set(None);
    set_image_toolbar_anchor.set(image_anchor);
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn open_block_menu_overlay(
    anchor: HoveredBlockState,
    same_anchor: bool,
    block_menu_open: ReadSignal<bool>,
    set_slash_open: WriteSignal<bool>,
    set_slash_index: WriteSignal<usize>,
    set_slash_query: WriteSignal<String>,
    set_turn_into_open: WriteSignal<bool>,
    set_color_menu_open: WriteSignal<bool>,
    set_more_menu_open: WriteSignal<bool>,
    set_locked_toolbar_anchor: WriteSignal<Option<FloatingToolbarAnchor>>,
    set_text_selection_active: WriteSignal<bool>,
    set_floating_toolbar_anchor: WriteSignal<Option<FloatingToolbarAnchor>>,
    set_image_toolbar_anchor: WriteSignal<Option<ImageToolbarAnchor>>,
    set_table_toolbar_open: WriteSignal<bool>,
    set_table_options_open: WriteSignal<bool>,
    set_table_overlay_anchor: WriteSignal<Option<TableOverlayAnchor>>,
    set_table_selection_overlay: WriteSignal<Option<TableSelectionOverlayState>>,
    set_block_menu_open: WriteSignal<bool>,
    set_block_menu_anchor: WriteSignal<Option<HoveredBlockState>>,
    set_block_turn_into_open: WriteSignal<bool>,
    set_block_folded_title_open: WriteSignal<bool>,
) -> bool {
    let should_open = !block_menu_open.get_untracked() || !same_anchor;
    close_editor_floating_overlays(
        set_slash_open,
        set_slash_index,
        set_slash_query,
        set_turn_into_open,
        set_color_menu_open,
        set_more_menu_open,
        set_locked_toolbar_anchor,
        set_text_selection_active,
        set_floating_toolbar_anchor,
        set_image_toolbar_anchor,
        set_table_toolbar_open,
        set_table_options_open,
        set_table_overlay_anchor,
        set_table_selection_overlay,
        set_block_menu_open,
        set_block_menu_anchor,
        set_block_turn_into_open,
        set_block_folded_title_open,
    );
    if should_open {
        set_block_menu_open.set(true);
        set_block_menu_anchor.set(Some(anchor));
    }
    should_open
}

pub(crate) fn try_close_block_menu_overlays(
    set_slash_open: WriteSignal<bool>,
    set_turn_into_open: WriteSignal<bool>,
    set_hovered_block: WriteSignal<Option<HoveredBlockState>>,
    set_block_menu_anchor: WriteSignal<Option<HoveredBlockState>>,
    set_block_menu_open: WriteSignal<bool>,
) -> bool {
    if set_slash_open.try_set(false).is_none() {
        return false;
    }
    if set_turn_into_open.try_set(false).is_none() {
        return false;
    }
    if set_block_menu_open.try_set(false).is_none() {
        return false;
    }
    if set_block_menu_anchor.try_set(None).is_none() {
        return false;
    }
    if set_hovered_block.try_set(None).is_none() {
        return false;
    }
    true
}
