use crate::layout::{HitTarget, InteractionTree};
use crate::scene::Point;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PointerButton {
    Primary,
    Middle,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PointerEventKind {
    Move,
    Down { button: PointerButton },
    Up { button: PointerButton },
    Scroll { delta: Point },
    LeaveWindow,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PointerEvent {
    pub pointer_id: u32,
    pub position: Point,
    pub kind: PointerEventKind,
}

impl PointerEvent {
    pub const fn new(pointer_id: u32, position: Point, kind: PointerEventKind) -> Self {
        Self {
            pointer_id,
            position,
            kind,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RoutedPointerEventKind {
    Enter,
    Leave,
    Move,
    Down { button: PointerButton },
    Up { button: PointerButton },
    Scroll { delta: Point },
}

#[derive(Clone, Debug, PartialEq)]
pub struct RoutedPointerEvent {
    pub kind: RoutedPointerEventKind,
    pub target: HitTarget,
    pub pointer_id: u32,
    pub position: Point,
}

#[derive(Default)]
pub struct PointerRouter {
    hovered: Vec<HitTarget>,
    pressed: Option<Vec<HitTarget>>,
}

impl PointerRouter {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn hovered_target(&self) -> Option<&HitTarget> {
        self.hovered.last()
    }

    pub fn hovered_targets(&self) -> &[HitTarget] {
        &self.hovered
    }

    pub fn pressed_target(&self) -> Option<&HitTarget> {
        self.pressed.as_ref().and_then(|targets| targets.last())
    }

    pub fn pressed_targets(&self) -> &[HitTarget] {
        self.pressed.as_deref().unwrap_or(&[])
    }

    pub fn route(
        &mut self,
        interaction_tree: &InteractionTree,
        event: PointerEvent,
    ) -> Vec<RoutedPointerEvent> {
        let hovered_targets = match event.kind {
            PointerEventKind::LeaveWindow => Vec::new(),
            _ => interaction_tree.hit_path(event.position),
        };
        let hit_target = hovered_targets.last().cloned();

        let mut routed = Vec::new();
        let shared_depth = self
            .hovered
            .iter()
            .zip(hovered_targets.iter())
            .take_while(|(previous, next)| previous == next)
            .count();
        if shared_depth != self.hovered.len() || shared_depth != hovered_targets.len() {
            for previous in self.hovered[shared_depth..].iter().rev().cloned() {
                routed.push(RoutedPointerEvent {
                    kind: RoutedPointerEventKind::Leave,
                    target: previous,
                    pointer_id: event.pointer_id,
                    position: event.position,
                });
            }
            for target in hovered_targets[shared_depth..].iter().cloned() {
                routed.push(RoutedPointerEvent {
                    kind: RoutedPointerEventKind::Enter,
                    target,
                    pointer_id: event.pointer_id,
                    position: event.position,
                });
            }
            self.hovered = hovered_targets;
        }

        match event.kind {
            PointerEventKind::Move => {
                if let Some(target) = self
                    .pressed
                    .as_ref()
                    .and_then(|targets| targets.last().cloned())
                    .or(hit_target.clone())
                {
                    routed.push(RoutedPointerEvent {
                        kind: RoutedPointerEventKind::Move,
                        target,
                        pointer_id: event.pointer_id,
                        position: event.position,
                    });
                }
            }
            PointerEventKind::Down { button } => {
                if let Some(target) = hit_target {
                    self.pressed = Some(self.hovered.clone());
                    routed.push(RoutedPointerEvent {
                        kind: RoutedPointerEventKind::Down { button },
                        target,
                        pointer_id: event.pointer_id,
                        position: event.position,
                    });
                }
            }
            PointerEventKind::Up { button } => {
                if let Some(target) = self
                    .pressed
                    .take()
                    .and_then(|targets| targets.last().cloned())
                    .or(hit_target)
                {
                    routed.push(RoutedPointerEvent {
                        kind: RoutedPointerEventKind::Up { button },
                        target,
                        pointer_id: event.pointer_id,
                        position: event.position,
                    });
                }
            }
            PointerEventKind::Scroll { delta } => {
                if let Some(target) = hit_target {
                    routed.push(RoutedPointerEvent {
                        kind: RoutedPointerEventKind::Scroll { delta },
                        target,
                        pointer_id: event.pointer_id,
                        position: event.position,
                    });
                }
            }
            PointerEventKind::LeaveWindow => {}
        }

        routed
    }
}

#[cfg(test)]
mod tests {
    use super::{
        PointerButton, PointerEvent, PointerEventKind, PointerRouter, RoutedPointerEventKind,
    };
    use crate::layout::{InteractionTree, LayoutNode, LayoutPath};
    use crate::scene::{Point, Rect};
    use crate::tree::{CursorIcon, ElementId};

    fn interaction_tree() -> InteractionTree {
        InteractionTree {
            root: LayoutNode {
                path: LayoutPath::root(),
                element_id: None,
                rect: Rect::new(0.0, 0.0, 200.0, 120.0),
                corner_radius: 0.0,
                clip_rect: None,
                pointer_events: false,
                focusable: false,
                cursor: CursorIcon::Default,
                scroll_metrics: None,
                prepared_image: None,
                prepared_text: None,
                children: vec![
                    LayoutNode {
                        path: LayoutPath::root().child(0),
                        element_id: Some(ElementId::new(1)),
                        rect: Rect::new(0.0, 0.0, 120.0, 120.0),
                        corner_radius: 0.0,
                        clip_rect: None,
                        pointer_events: true,
                        focusable: false,
                        cursor: CursorIcon::Default,
                        scroll_metrics: None,
                        prepared_image: None,
                        prepared_text: None,
                        children: Vec::new(),
                    },
                    LayoutNode {
                        path: LayoutPath::root().child(1),
                        element_id: Some(ElementId::new(2)),
                        rect: Rect::new(80.0, 0.0, 120.0, 120.0),
                        corner_radius: 0.0,
                        clip_rect: None,
                        pointer_events: true,
                        focusable: false,
                        cursor: CursorIcon::Default,
                        scroll_metrics: None,
                        prepared_image: None,
                        prepared_text: None,
                        children: Vec::new(),
                    },
                ],
            },
        }
    }

    fn nested_interaction_tree() -> InteractionTree {
        InteractionTree {
            root: LayoutNode {
                path: LayoutPath::root(),
                element_id: None,
                rect: Rect::new(0.0, 0.0, 200.0, 120.0),
                corner_radius: 0.0,
                clip_rect: None,
                pointer_events: false,
                focusable: false,
                cursor: CursorIcon::Default,
                scroll_metrics: None,
                prepared_image: None,
                prepared_text: None,
                children: vec![LayoutNode {
                    path: LayoutPath::root().child(0),
                    element_id: Some(ElementId::new(1)),
                    rect: Rect::new(0.0, 0.0, 160.0, 120.0),
                    corner_radius: 0.0,
                    clip_rect: None,
                    pointer_events: true,
                    focusable: false,
                    cursor: CursorIcon::Default,
                    scroll_metrics: None,
                    prepared_image: None,
                    prepared_text: None,
                    children: vec![LayoutNode {
                        path: LayoutPath::root().child(0).child(0),
                        element_id: Some(ElementId::new(2)),
                        rect: Rect::new(16.0, 16.0, 80.0, 40.0),
                        corner_radius: 0.0,
                        clip_rect: None,
                        pointer_events: true,
                        focusable: false,
                        cursor: CursorIcon::Default,
                        scroll_metrics: None,
                        prepared_image: None,
                        prepared_text: None,
                        children: Vec::new(),
                    }],
                }],
            },
        }
    }

    #[test]
    fn router_emits_enter_and_move_for_new_hover_target() {
        let mut router = PointerRouter::new();
        let routed = router.route(
            &interaction_tree(),
            PointerEvent::new(0, Point::new(20.0, 20.0), PointerEventKind::Move),
        );

        assert_eq!(routed.len(), 2);
        assert_eq!(routed[0].kind, RoutedPointerEventKind::Enter);
        assert_eq!(routed[1].kind, RoutedPointerEventKind::Move);
        assert_eq!(routed[1].target.element_id, Some(ElementId::new(1)));
    }

    #[test]
    fn router_prefers_pressed_target_on_pointer_up() {
        let mut router = PointerRouter::new();
        let tree = interaction_tree();
        let _ = router.route(
            &tree,
            PointerEvent::new(
                0,
                Point::new(20.0, 20.0),
                PointerEventKind::Down {
                    button: PointerButton::Primary,
                },
            ),
        );
        let routed = router.route(
            &tree,
            PointerEvent::new(
                0,
                Point::new(160.0, 20.0),
                PointerEventKind::Up {
                    button: PointerButton::Primary,
                },
            ),
        );

        assert!(routed.iter().any(|event| matches!(
            event.kind,
            RoutedPointerEventKind::Up {
                button: PointerButton::Primary
            }
        )));
        assert_eq!(
            routed.last().unwrap().target.element_id,
            Some(ElementId::new(1))
        );
    }

    #[test]
    fn router_prefers_pressed_target_on_pointer_move() {
        let mut router = PointerRouter::new();
        let tree = interaction_tree();
        let _ = router.route(
            &tree,
            PointerEvent::new(
                0,
                Point::new(20.0, 20.0),
                PointerEventKind::Down {
                    button: PointerButton::Primary,
                },
            ),
        );
        let routed = router.route(
            &tree,
            PointerEvent::new(0, Point::new(160.0, 20.0), PointerEventKind::Move),
        );

        assert!(
            routed
                .iter()
                .any(|event| event.kind == RoutedPointerEventKind::Move)
        );
        assert_eq!(
            routed.last().unwrap().target.element_id,
            Some(ElementId::new(1))
        );
    }

    #[test]
    fn router_routes_scroll_to_deepest_hover_target() {
        let mut router = PointerRouter::new();
        let tree = nested_interaction_tree();
        let _ = router.route(
            &tree,
            PointerEvent::new(0, Point::new(24.0, 24.0), PointerEventKind::Move),
        );

        let routed = router.route(
            &tree,
            PointerEvent::new(
                0,
                Point::new(24.0, 24.0),
                PointerEventKind::Scroll {
                    delta: Point::new(0.0, 18.0),
                },
            ),
        );

        assert!(routed.iter().any(|event| {
            event.kind
                == RoutedPointerEventKind::Scroll {
                    delta: Point::new(0.0, 18.0),
                }
                && event.target.element_id == Some(ElementId::new(2))
        }));
    }

    #[test]
    fn router_tracks_hovered_ancestors_for_nested_hits() {
        let mut router = PointerRouter::new();
        let routed = router.route(
            &nested_interaction_tree(),
            PointerEvent::new(0, Point::new(24.0, 24.0), PointerEventKind::Move),
        );

        assert_eq!(
            routed
                .iter()
                .filter(|event| event.kind == RoutedPointerEventKind::Enter)
                .map(|event| event.target.element_id)
                .collect::<Vec<_>>(),
            vec![Some(ElementId::new(1)), Some(ElementId::new(2))]
        );
        assert_eq!(
            router
                .hovered_targets()
                .iter()
                .map(|target| target.element_id)
                .collect::<Vec<_>>(),
            vec![Some(ElementId::new(1)), Some(ElementId::new(2))]
        );
        assert_eq!(
            router.hovered_target().unwrap().element_id,
            Some(ElementId::new(2))
        );
    }

    #[test]
    fn router_leaves_only_nested_child_when_pointer_moves_to_parent() {
        let mut router = PointerRouter::new();
        let tree = nested_interaction_tree();
        let _ = router.route(
            &tree,
            PointerEvent::new(0, Point::new(24.0, 24.0), PointerEventKind::Move),
        );

        let routed = router.route(
            &tree,
            PointerEvent::new(0, Point::new(140.0, 24.0), PointerEventKind::Move),
        );

        assert_eq!(
            routed
                .iter()
                .filter(|event| event.kind == RoutedPointerEventKind::Leave)
                .map(|event| event.target.element_id)
                .collect::<Vec<_>>(),
            vec![Some(ElementId::new(2))]
        );
        assert_eq!(
            router
                .hovered_targets()
                .iter()
                .map(|target| target.element_id)
                .collect::<Vec<_>>(),
            vec![Some(ElementId::new(1))]
        );
    }
}
