//! Block handle 拖拽命中的 runtime helper。
//!
//! 本模块只承接拖拽命中所需的窄 helper，不处理 Leptos signal，
//! 也不分发 editor command；调用方负责保留编辑器状态编排。

use crate::editor_runtime::block_hover_state::{DropIndicatorState, DropPlacement};
use web_sys::{window, Element, EventTarget};

pub(crate) struct HandleCorridorGeometry {
    pub(crate) stage_left: f64,
    pub(crate) stage_top: f64,
    pub(crate) block_left: f64,
    pub(crate) block_top: f64,
    pub(crate) block_bottom: f64,
    pub(crate) handle_left: f64,
    pub(crate) handle_right: f64,
}

pub(crate) fn pointer_in_handle_corridor_geometry(
    client_x: i32,
    client_y: i32,
    geometry: &HandleCorridorGeometry,
) -> bool {
    let x = f64::from(client_x) - geometry.stage_left;
    let y = f64::from(client_y) - geometry.stage_top;
    let corridor_left = (geometry.handle_left - 12.0).min(geometry.block_left);
    let corridor_right = (geometry.block_left + 18.0).max(geometry.handle_right + 12.0);

    x >= corridor_left
        && x <= corridor_right
        && y >= geometry.block_top - 18.0
        && y <= geometry.block_bottom + 18.0
}

pub(crate) fn drop_placement_from_block_point(
    client_y: i32,
    block_top: f64,
    block_height: f64,
) -> DropPlacement {
    let midpoint = block_top + (block_height / 2.0);
    if f64::from(client_y) <= midpoint {
        DropPlacement::Before
    } else {
        DropPlacement::After
    }
}

pub(crate) fn drop_indicator_top(
    stage_top: f64,
    block_top: f64,
    block_bottom: f64,
    placement: DropPlacement,
) -> f64 {
    match placement {
        DropPlacement::Before => block_top - stage_top,
        DropPlacement::After => block_bottom - stage_top,
    }
}

pub(crate) fn drop_indicator_from_geometry(
    block_index: usize,
    block_top: f64,
    block_height: f64,
    stage_top: f64,
    client_y: i32,
) -> DropIndicatorState {
    let placement = drop_placement_from_block_point(client_y, block_top, block_height);
    let block_bottom = block_top + block_height;
    let top = drop_indicator_top(stage_top, block_top, block_bottom, placement);
    DropIndicatorState {
        index: block_index,
        top,
        placement,
    }
}

pub(crate) fn event_target_from_point(client_x: i32, client_y: i32) -> Option<EventTarget> {
    window()
        .and_then(|win| win.document())
        .and_then(|document| document.element_from_point(client_x as f32, client_y as f32))
        .map(Into::into)
}

pub(crate) fn element_within_handle_shell(element: &Element, handle_selector: &str) -> bool {
    element.closest(handle_selector).ok().flatten().is_some()
}

pub(crate) fn direct_block_from_element(mut element: Element, root: &Element) -> Option<Element> {
    loop {
        let parent = element.parent_element()?;
        if parent.is_same_node(Some(root)) {
            return Some(element);
        }
        element = parent;
    }
}

pub(crate) fn direct_block_index_from_element(
    element: &Element,
    root: &Element,
    handle_selector: &str,
) -> Option<usize> {
    if element_within_handle_shell(element, handle_selector) {
        return None;
    }
    let block = direct_block_from_element(element.clone(), root)?;
    top_level_block_index(root, &block)
}

pub(crate) fn drop_indicator_from_block_rects(
    block_index: usize,
    block: &Element,
    stage: &Element,
    client_y: i32,
) -> DropIndicatorState {
    let block_rect = block.get_bounding_client_rect();
    let stage_rect = stage.get_bounding_client_rect();
    drop_indicator_from_geometry(
        block_index,
        block_rect.top(),
        block_rect.height(),
        stage_rect.top(),
        client_y,
    )
}

pub(crate) fn top_level_block_index(root: &Element, block: &Element) -> Option<usize> {
    let children = root.children();
    (0..children.length()).find_map(|index| {
        children
            .item(index)
            .filter(|child| child.is_same_node(Some(block)))
            .map(|_| index as usize)
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn pointer_corridor_accepts_handle_lane_and_block_edge() {
        let geometry = HandleCorridorGeometry {
            stage_left: 100.0,
            stage_top: 20.0,
            block_left: 140.0,
            block_top: 40.0,
            block_bottom: 80.0,
            handle_left: 108.0,
            handle_right: 130.0,
        };

        assert!(pointer_in_handle_corridor_geometry(118, 80, &geometry));
        assert!(pointer_in_handle_corridor_geometry(250, 80, &geometry));
        assert!(!pointer_in_handle_corridor_geometry(90, 80, &geometry));
        assert!(!pointer_in_handle_corridor_geometry(118, 28, &geometry));
    }

    #[test]
    fn drop_placement_splits_block_at_midpoint() {
        assert_eq!(
            drop_placement_from_block_point(50, 40.0, 20.0),
            DropPlacement::Before
        );
        assert_eq!(
            drop_placement_from_block_point(51, 40.0, 20.0),
            DropPlacement::After
        );
    }

    #[test]
    fn drop_indicator_geometry_returns_stage_relative_top() {
        assert_eq!(
            drop_indicator_from_geometry(3, 140.0, 40.0, 100.0, 150),
            DropIndicatorState {
                index: 3,
                top: 40.0,
                placement: DropPlacement::Before,
            }
        );
        assert_eq!(
            drop_indicator_from_geometry(3, 140.0, 40.0, 100.0, 161),
            DropIndicatorState {
                index: 3,
                top: 80.0,
                placement: DropPlacement::After,
            }
        );
    }
}
