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

use crate::{
    editor_root_element,
    editor_runtime::block_hover_state::{
        block_label_for_element, DropIndicatorState, DropPlacement, HoveredBlockState,
    },
    editor_stage_element,
};
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 hovered_block_from_target(
    target: EventTarget,
    root: &Element,
    handle_selector: &str,
) -> Option<HoveredBlockState> {
    let element = crate::editor_runtime::dom_events::target_element(target)?;
    let index = direct_block_index_from_element(&element, root, handle_selector)?;
    block_state_from_index(index)
}

pub(crate) fn drop_indicator_from_target(
    target: EventTarget,
    client_y: i32,
    handle_selector: &str,
) -> Option<DropIndicatorState> {
    let root = editor_root_element()?;
    let hovered = hovered_block_from_target(target, &root, handle_selector)?;
    let block = root.children().item(hovered.index as u32)?;
    let stage = editor_stage_element()?;
    Some(drop_indicator_from_block_rects(
        hovered.index,
        &block,
        &stage,
        client_y,
    ))
}

pub(crate) fn drop_indicator_from_point(
    client_x: i32,
    client_y: i32,
    handle_selector: &str,
) -> Option<DropIndicatorState> {
    let target = event_target_from_point(client_x, client_y)?;
    drop_indicator_from_target(target, client_y, handle_selector)
}

pub(crate) fn block_state_from_index(index: usize) -> Option<HoveredBlockState> {
    let root = editor_root_element()?;
    let stage = editor_stage_element()?;
    let block = root.children().item(index as u32)?;
    let block_rect = block.get_bounding_client_rect();
    let stage_rect = stage.get_bounding_client_rect();
    let block_height = if block_rect.height() > 0.0 {
        block_rect.height()
    } else {
        28.0
    };

    Some(HoveredBlockState {
        index,
        label: block_label_for_element(&block),
        top: block_rect.top() - stage_rect.top(),
        height: block_height,
    })
}

pub(crate) fn pointer_in_handle_corridor(
    client_x: i32,
    client_y: i32,
    block: &HoveredBlockState,
    handle_selector: &str,
    handle_left_fallback: f64,
    handle_width: f64,
) -> bool {
    let Some(stage) = editor_stage_element() else {
        return false;
    };
    let Some(root) = editor_root_element() else {
        return false;
    };
    let Some(block_element) = root.children().item(block.index as u32) else {
        return false;
    };

    let stage_rect = stage.get_bounding_client_rect();
    let block_rect = block_element.get_bounding_client_rect();
    let handle_rect = window()
        .and_then(|win| win.document())
        .and_then(|document| document.query_selector(handle_selector).ok().flatten())
        .map(|handle| handle.get_bounding_client_rect());

    let block_left = block_rect.left() - stage_rect.left();
    let block_top = block_rect.top() - stage_rect.top();
    let block_bottom = block_rect.bottom() - stage_rect.top();
    let handle_left = handle_rect
        .as_ref()
        .map(|rect| rect.left() - stage_rect.left())
        .unwrap_or(handle_left_fallback);
    let handle_right = handle_rect
        .as_ref()
        .map(|rect| rect.right() - stage_rect.left())
        .unwrap_or_else(|| handle_left + handle_width);

    pointer_in_handle_corridor_geometry(
        client_x,
        client_y,
        &HandleCorridorGeometry {
            stage_left: stage_rect.left(),
            stage_top: stage_rect.top(),
            block_left,
            block_top,
            block_bottom,
            handle_left,
            handle_right,
        },
    )
}

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() {
        // All geometry values are stage-relative (substracted stage_left/top).
        // handle at absolute x≈108 → stage-rel handle_left≈8
        // block starts at absolute x=250 → stage-rel block_left=150
        let geometry = HandleCorridorGeometry {
            stage_left: 100.0,
            stage_top: 20.0,
            block_left: 150.0,
            block_top: 40.0,
            block_bottom: 60.0,
            handle_left: 8.0,
            handle_right: 30.0,
        };

        // (118,80) → stage-rel (18,60): inside corridor ✓
        assert!(pointer_in_handle_corridor_geometry(118, 80, &geometry));
        // (250,80) → stage-rel (150,60): inside block-edge corridor ✓
        assert!(pointer_in_handle_corridor_geometry(250, 80, &geometry));
        // (90,80) → stage-rel (-10,60): x outside corridor (-10 < -4) ✓
        assert!(!pointer_in_handle_corridor_geometry(90, 80, &geometry));
        // (118,28) → stage-rel (18,8): y above block_top-18 (=22) ✓
        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,
            }
        );
    }
}
