//! Input routing — hit-test the UI against the cursor, track hover / press /
//! focus per widget, and tell the host whether the UI captured the frame's
//! input so the game can decide whether to also handle it.
//!
//! Stage 8's UI must *consume input before the game* (PLAN.md): if the
//! cursor is over a button, clicking shouldn't also fire the game-world
//! action bound to that mouse button. The [`Router`] gives the host one
//! object to drive each frame:
//!
//! ```text
//!  game loop:
//!    input.handle_event(e); ...
//!    let frame = router.process(&layout_tree, &input);
//!    if !frame.captured_mouse { /* game receives mouse input */ }
//!    if !frame.captured_keyboard { /* game receives keys */ }
//!    for event in &frame.events { /* run widget callbacks (piece 6) */ }
//! ```
//!
//! The router is purely a state machine over the Stage-7 [`InputState`] and
//! the Stage-8 [`LayoutTree`] — no GPU, no widget callbacks (those land in
//! piece 6). Tests run headlessly.
//!
//! # Hit-test order
//!
//! Hit testing walks [`LayoutTree::nodes`] in **reverse order**. That order
//! matches the paint order (parents-before-children, earlier siblings
//! before later ones — see [`super::paint`]) — so the *last* node drawn
//! is the *first* one tested, which is exactly the topmost interactive
//! widget under the cursor.
//!
//! Anonymous widgets (`WidgetId::default()`) are treated as transparent
//! for hit-test purposes: the router skips them and looks deeper, so a
//! decorative container without an id doesn't block clicks reaching the
//! button inside it.

use std::collections::HashMap;

use serde::{Deserialize, Serialize};
use winit::event::MouseButton;

use super::layout::{LayoutNode, LayoutTree};
use super::widget::WidgetId;
use crate::input::InputState;
use crate::math::Vec2;

/// One event emitted by [`Router::process`] for the current frame.
///
/// Events are ordered: hover changes come first, then per-button press /
/// release / click, then focus changes. Callers in piece 6 will dispatch
/// each event to the matching widget's registered callback.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum RouterEvent {
    /// The cursor moved onto this widget this frame.
    Hovered(WidgetId),
    /// The cursor moved off this widget this frame.
    Unhovered(WidgetId),
    /// A mouse button was pressed while the cursor was over this widget.
    Pressed(WidgetId, MouseButton),
    /// A mouse button was released while the cursor was over this widget.
    /// May or may not be accompanied by a [`Clicked`](Self::Clicked); see
    /// the comment on that variant.
    Released(WidgetId, MouseButton),
    /// A click completed on this widget: the press *and* release happened
    /// over the same widget without the cursor leaving in between.
    /// Dragging off cancels the click.
    Clicked(WidgetId, MouseButton),
    /// This widget became the focused widget.
    FocusGained(WidgetId),
    /// This widget lost focus.
    FocusLost(WidgetId),
}

/// What [`Router::process`] produces for one frame.
#[derive(Debug, Default, Clone, PartialEq)]
pub struct RouterFrame {
    /// Events emitted this frame, in the order they were observed.
    pub events: Vec<RouterEvent>,
    /// `true` if the cursor is over any (non-anonymous) widget — the game
    /// should not also process this frame's mouse input.
    pub captured_mouse: bool,
    /// `true` if a widget currently has keyboard focus — the game should
    /// not also process this frame's key events.
    pub captured_keyboard: bool,
}

impl RouterFrame {
    /// `true` if this frame contains a `Clicked` event on `id` for the
    /// given mouse button. The immediate-mode pattern: game code calls
    /// `if frame.clicked("play", MouseButton::Left) { start_game() }`
    /// instead of registering a callback.
    pub fn clicked(&self, id: impl AsRef<str>, button: MouseButton) -> bool {
        let id = id.as_ref();
        self.events.iter().any(|e| match e {
            RouterEvent::Clicked(w, b) => w.as_str() == id && *b == button,
            _ => false,
        })
    }

    /// Shorthand for [`clicked`](Self::clicked) with the left button.
    pub fn clicked_left(&self, id: impl AsRef<str>) -> bool {
        self.clicked(id, MouseButton::Left)
    }

    /// `true` if this frame contains a `Pressed` event on `id` with the
    /// given mouse button.
    pub fn pressed(&self, id: impl AsRef<str>, button: MouseButton) -> bool {
        let id = id.as_ref();
        self.events.iter().any(|e| match e {
            RouterEvent::Pressed(w, b) => w.as_str() == id && *b == button,
            _ => false,
        })
    }

    /// `true` if this frame contains a `Released` event on `id` with the
    /// given mouse button.
    pub fn released(&self, id: impl AsRef<str>, button: MouseButton) -> bool {
        let id = id.as_ref();
        self.events.iter().any(|e| match e {
            RouterEvent::Released(w, b) => w.as_str() == id && *b == button,
            _ => false,
        })
    }

    /// `true` if the cursor entered `id` this frame.
    pub fn hovered_in(&self, id: impl AsRef<str>) -> bool {
        let id = id.as_ref();
        self.events.iter().any(|e| match e {
            RouterEvent::Hovered(w) => w.as_str() == id,
            _ => false,
        })
    }

    /// `true` if the cursor left `id` this frame.
    pub fn hovered_out(&self, id: impl AsRef<str>) -> bool {
        let id = id.as_ref();
        self.events.iter().any(|e| match e {
            RouterEvent::Unhovered(w) => w.as_str() == id,
            _ => false,
        })
    }

    /// `true` if `id` gained focus this frame.
    pub fn focus_gained(&self, id: impl AsRef<str>) -> bool {
        let id = id.as_ref();
        self.events.iter().any(|e| match e {
            RouterEvent::FocusGained(w) => w.as_str() == id,
            _ => false,
        })
    }

    /// `true` if `id` lost focus this frame.
    pub fn focus_lost(&self, id: impl AsRef<str>) -> bool {
        let id = id.as_ref();
        self.events.iter().any(|e| match e {
            RouterEvent::FocusLost(w) => w.as_str() == id,
            _ => false,
        })
    }
}

/// Per-widget input state machine. Persists hover / focus / pending-press
/// across frames so click-detection (press *and* release on the same
/// widget) works correctly across the multiple frames a click typically
/// spans.
#[derive(Debug, Default)]
pub struct Router {
    hovered: Option<WidgetId>,
    focused: Option<WidgetId>,
    /// Per-button: the widget that received the most recent un-released
    /// press. A click completes if the release happens over the same
    /// widget; otherwise the press is cancelled (drag-off semantics).
    pending: HashMap<MouseButton, WidgetId>,
}

impl Router {
    /// Build an empty router with no hover, no focus, and no pending presses.
    pub fn new() -> Self {
        Self::default()
    }

    /// The currently hovered widget, or `None` when the cursor is not over
    /// any addressable widget.
    pub fn hovered(&self) -> Option<&WidgetId> {
        self.hovered.as_ref()
    }

    /// The currently focused widget, or `None` if none.
    pub fn focused(&self) -> Option<&WidgetId> {
        self.focused.as_ref()
    }

    /// Explicitly focus a widget (e.g., from game code after opening a
    /// menu). Emits no event — the caller decided to do this.
    pub fn set_focused(&mut self, id: Option<WidgetId>) {
        self.focused = id;
    }

    /// Run the input pipeline against one frame's [`InputState`] and the
    /// current [`LayoutTree`]. Updates internal state, returns events plus
    /// the capture flags.
    pub fn process(&mut self, tree: &LayoutTree, input: &InputState) -> RouterFrame {
        let mut frame = RouterFrame::default();
        let new_hover = input
            .cursor()
            .and_then(|c| hit_test(tree, c))
            .map(|node| node.id.clone());

        // Hover transitions.
        if new_hover != self.hovered {
            if let Some(old) = self.hovered.take() {
                frame.events.push(RouterEvent::Unhovered(old));
            }
            if let Some(new) = new_hover.clone() {
                frame.events.push(RouterEvent::Hovered(new));
            }
        }
        self.hovered = new_hover;
        frame.captured_mouse = self.hovered.is_some();

        // Mouse press / release per button. The Stage-7 InputState
        // exposes "buttons held" + per-button edge flags; we walk the
        // currently-relevant buttons (those held this frame *or* present
        // as pending from previous frames).
        let mut buttons = std::collections::HashSet::new();
        buttons.extend(input.mouse_buttons_held());
        buttons.extend(self.pending.keys().copied());
        // Common buttons that may have just pressed/released without being
        // held now (release edge happens after the held set has cleared
        // the button).
        for b in [MouseButton::Left, MouseButton::Right, MouseButton::Middle] {
            if input.mouse_pressed(b) || input.mouse_released(b) {
                buttons.insert(b);
            }
        }

        for button in buttons {
            if input.mouse_pressed(button) {
                if let Some(target) = self.hovered.clone() {
                    frame
                        .events
                        .push(RouterEvent::Pressed(target.clone(), button));
                    self.pending.insert(button, target.clone());
                    self.update_focus(Some(target), &mut frame);
                } else {
                    // Click outside any widget clears focus.
                    self.update_focus(None, &mut frame);
                }
            }
            if input.mouse_released(button) {
                if let Some(pending_id) = self.pending.remove(&button) {
                    if let Some(current) = self.hovered.clone() {
                        frame
                            .events
                            .push(RouterEvent::Released(current.clone(), button));
                        if current == pending_id {
                            frame.events.push(RouterEvent::Clicked(current, button));
                        }
                    } else {
                        // Drag-off then release: cancel the click. No
                        // Released event has a target either, since we
                        // require a hovered widget for that.
                    }
                }
            }
        }

        frame.captured_keyboard = self.focused.is_some();
        frame
    }

    /// Move focus to `next` (or clear it when `None`), emitting `FocusLost`
    /// / `FocusGained` events. Idempotent when `next` matches the current
    /// focus.
    fn update_focus(&mut self, next: Option<WidgetId>, frame: &mut RouterFrame) {
        if next == self.focused {
            return;
        }
        if let Some(old) = self.focused.take() {
            frame.events.push(RouterEvent::FocusLost(old));
        }
        if let Some(new) = next.clone() {
            frame.events.push(RouterEvent::FocusGained(new));
        }
        self.focused = next;
    }
}

/// Hit-test `point` against the laid-out widgets. Returns the topmost
/// (most-recently-painted) [`LayoutNode`] with a non-empty id whose `rect`
/// contains the point, or `None` if no addressable widget is under the
/// point.
///
/// Anonymous widgets (empty `id`) are skipped so a decorative container
/// doesn't block hits on the button it contains. Iteration is in reverse
/// node order — children and later siblings (drawn on top) are tested
/// before their parents.
pub fn hit_test(tree: &LayoutTree, point: Vec2) -> Option<&LayoutNode> {
    for node in tree.nodes().iter().rev() {
        if node.id.is_empty() {
            continue;
        }
        if node.rect.contains_point(point) {
            return Some(node);
        }
    }
    None
}

#[cfg(test)]
mod tests {
    use super::super::layout::layout;
    use super::super::style::{LayoutStyle, Sizing};
    use super::super::widget::Widget;
    use super::*;
    use crate::math::Rect;

    fn viewport(w: f32, h: f32) -> Rect {
        Rect::from_min_size(Vec2::ZERO, Vec2::new(w, h))
    }

    fn make_tree() -> (Widget, LayoutTree) {
        // Root container with two side-by-side leaves: "left" and "right".
        let root = Widget::row()
            .with_id("root")
            .with_style(LayoutStyle {
                width: Sizing::Fixed(200.0),
                height: Sizing::Fixed(100.0),
                ..Default::default()
            })
            .with_child(
                Widget::leaf(Vec2::new(100.0, 100.0))
                    .with_id("left")
                    .with_style(LayoutStyle {
                        width: Sizing::Fixed(100.0),
                        height: Sizing::Fixed(100.0),
                        ..Default::default()
                    }),
            )
            .with_child(
                Widget::leaf(Vec2::new(100.0, 100.0))
                    .with_id("right")
                    .with_style(LayoutStyle {
                        width: Sizing::Fixed(100.0),
                        height: Sizing::Fixed(100.0),
                        ..Default::default()
                    }),
            );
        let tree = layout(&root, viewport(400.0, 200.0), 1.0);
        (root, tree)
    }

    #[test]
    fn hit_test_returns_topmost_widget_with_id() {
        let (_root, tree) = make_tree();
        // Cursor over the left child → returns "left", not "root".
        let hit = hit_test(&tree, Vec2::new(50.0, 50.0)).unwrap();
        assert_eq!(hit.id.as_str(), "left");
        // Cursor over the right child → "right".
        let hit = hit_test(&tree, Vec2::new(150.0, 50.0)).unwrap();
        assert_eq!(hit.id.as_str(), "right");
    }

    #[test]
    fn hit_test_falls_back_to_parent_when_children_dont_cover() {
        // Root 200×100 with 20-pixel padding, containing one 80×60 button.
        // The padding gutter is "root-only" space — clicks there should
        // resolve to "root", not the button.
        let root = Widget::row()
            .with_id("root")
            .with_style(LayoutStyle {
                width: Sizing::Fixed(200.0),
                height: Sizing::Fixed(100.0),
                padding: super::super::style::Insets::all(20.0),
                ..Default::default()
            })
            .with_child(
                Widget::leaf(Vec2::new(80.0, 60.0))
                    .with_id("button")
                    .with_style(LayoutStyle {
                        width: Sizing::Fixed(80.0),
                        height: Sizing::Fixed(60.0),
                        ..Default::default()
                    }),
            );
        let tree = layout(&root, viewport(400.0, 200.0), 1.0);
        // Inside the button.
        let hit = hit_test(&tree, Vec2::new(60.0, 50.0)).unwrap();
        assert_eq!(hit.id.as_str(), "button");
        // Inside root's padding gutter (10, 50) → root, not button.
        let hit = hit_test(&tree, Vec2::new(10.0, 50.0)).unwrap();
        assert_eq!(hit.id.as_str(), "root");
    }

    #[test]
    fn hit_test_skips_anonymous_widgets() {
        // A button buried inside two anonymous containers should still hit.
        let root = Widget::row()
            .with_style(LayoutStyle {
                width: Sizing::Fixed(200.0),
                height: Sizing::Fixed(100.0),
                ..Default::default()
            })
            .with_child(
                Widget::row()
                    .with_style(LayoutStyle {
                        width: Sizing::Fixed(100.0),
                        height: Sizing::Fixed(100.0),
                        ..Default::default()
                    })
                    .with_child(
                        Widget::leaf(Vec2::new(80.0, 80.0))
                            .with_id("button")
                            .with_style(LayoutStyle {
                                width: Sizing::Fixed(80.0),
                                height: Sizing::Fixed(80.0),
                                ..Default::default()
                            }),
                    ),
            );
        let tree = layout(&root, viewport(200.0, 100.0), 1.0);
        let hit = hit_test(&tree, Vec2::new(20.0, 20.0)).unwrap();
        assert_eq!(hit.id.as_str(), "button");
    }

    #[test]
    fn hit_test_returns_none_outside_root() {
        let (_root, tree) = make_tree();
        let hit = hit_test(&tree, Vec2::new(500.0, 500.0));
        assert!(hit.is_none());
    }

    #[test]
    fn cursor_moving_onto_widget_emits_hovered_event() {
        let (_root, tree) = make_tree();
        let mut router = Router::new();
        let mut input = InputState::new();

        // First frame: cursor outside, no hover.
        input.set_cursor(Vec2::new(500.0, 500.0));
        let f = router.process(&tree, &input);
        assert!(f.events.is_empty());
        assert!(!f.captured_mouse);

        // Move into the left widget.
        input.set_cursor(Vec2::new(50.0, 50.0));
        let f = router.process(&tree, &input);
        assert_eq!(f.events, vec![RouterEvent::Hovered("left".into())]);
        assert!(f.captured_mouse);
        assert_eq!(router.hovered(), Some(&"left".into()));
    }

    #[test]
    fn cursor_moving_off_emits_unhovered() {
        let (_root, tree) = make_tree();
        let mut router = Router::new();
        let mut input = InputState::new();
        input.set_cursor(Vec2::new(50.0, 50.0));
        router.process(&tree, &input);

        // Move off the widget.
        input.set_cursor(Vec2::new(500.0, 500.0));
        let f = router.process(&tree, &input);
        assert_eq!(f.events, vec![RouterEvent::Unhovered("left".into())]);
        assert!(!f.captured_mouse);
    }

    #[test]
    fn cursor_moving_between_widgets_swaps_hover() {
        let (_root, tree) = make_tree();
        let mut router = Router::new();
        let mut input = InputState::new();
        input.set_cursor(Vec2::new(50.0, 50.0));
        router.process(&tree, &input);

        input.set_cursor(Vec2::new(150.0, 50.0));
        let f = router.process(&tree, &input);
        // Unhover left, then hover right (both this frame).
        assert_eq!(
            f.events,
            vec![
                RouterEvent::Unhovered("left".into()),
                RouterEvent::Hovered("right".into()),
            ]
        );
    }

    #[test]
    fn pressing_over_widget_emits_pressed_and_focuses() {
        let (_root, tree) = make_tree();
        let mut router = Router::new();
        let mut input = InputState::new();
        input.set_cursor(Vec2::new(50.0, 50.0));
        // Frame 1: hover only.
        router.process(&tree, &input);
        // Frame 2: press the left button while hovering.
        input.press_mouse(MouseButton::Left);
        let f = router.process(&tree, &input);
        assert!(f
            .events
            .contains(&RouterEvent::Pressed("left".into(), MouseButton::Left)));
        assert!(f.events.contains(&RouterEvent::FocusGained("left".into())));
        assert_eq!(router.focused(), Some(&"left".into()));
        assert!(f.captured_keyboard);
    }

    #[test]
    fn press_then_release_on_same_widget_emits_click() {
        let (_root, tree) = make_tree();
        let mut router = Router::new();
        let mut input = InputState::new();
        input.set_cursor(Vec2::new(50.0, 50.0));
        router.process(&tree, &input);
        input.press_mouse(MouseButton::Left);
        router.process(&tree, &input);
        input.end_frame(); // clear the press edge
        input.release_mouse(MouseButton::Left);
        let f = router.process(&tree, &input);
        // Released and Clicked, both on "left".
        assert!(f
            .events
            .contains(&RouterEvent::Released("left".into(), MouseButton::Left)));
        assert!(f
            .events
            .contains(&RouterEvent::Clicked("left".into(), MouseButton::Left)));
    }

    #[test]
    fn press_then_drag_off_then_release_does_not_emit_click() {
        let (_root, tree) = make_tree();
        let mut router = Router::new();
        let mut input = InputState::new();
        input.set_cursor(Vec2::new(50.0, 50.0));
        router.process(&tree, &input);
        input.press_mouse(MouseButton::Left);
        router.process(&tree, &input);
        input.end_frame();

        // Drag onto the right widget, then release.
        input.set_cursor(Vec2::new(150.0, 50.0));
        input.release_mouse(MouseButton::Left);
        let f = router.process(&tree, &input);
        let clicked = f
            .events
            .iter()
            .any(|e| matches!(e, RouterEvent::Clicked(_, _)));
        assert!(!clicked, "drag-off should cancel the click");
    }

    #[test]
    fn pressing_outside_any_widget_clears_focus() {
        let (_root, tree) = make_tree();
        let mut router = Router::new();
        router.set_focused(Some("left".into()));
        let mut input = InputState::new();
        input.set_cursor(Vec2::new(500.0, 500.0));
        input.press_mouse(MouseButton::Left);
        let f = router.process(&tree, &input);
        assert!(f.events.contains(&RouterEvent::FocusLost("left".into())));
        assert_eq!(router.focused(), None);
    }

    #[test]
    fn captured_flags_match_state() {
        let (_root, tree) = make_tree();
        let mut router = Router::new();
        let mut input = InputState::new();
        // No cursor, no focus → nothing captured.
        let f = router.process(&tree, &input);
        assert!(!f.captured_mouse);
        assert!(!f.captured_keyboard);

        // Cursor over a widget → captures mouse.
        input.set_cursor(Vec2::new(50.0, 50.0));
        let f = router.process(&tree, &input);
        assert!(f.captured_mouse);
        assert!(!f.captured_keyboard);

        // Press → focuses, captures keyboard too.
        input.press_mouse(MouseButton::Left);
        let f = router.process(&tree, &input);
        assert!(f.captured_keyboard);
    }

    #[test]
    fn cursor_off_screen_does_not_hover() {
        let (_root, tree) = make_tree();
        let mut router = Router::new();
        let input = InputState::new(); // cursor unset
        let f = router.process(&tree, &input);
        assert!(f.events.is_empty());
        assert!(!f.captured_mouse);
    }

    #[test]
    fn router_frame_clicked_query_matches_button_and_id() {
        let (_root, tree) = make_tree();
        let mut router = Router::new();
        let mut input = InputState::new();
        input.set_cursor(Vec2::new(50.0, 50.0));
        router.process(&tree, &input);
        input.press_mouse(MouseButton::Left);
        router.process(&tree, &input);
        input.end_frame();
        input.release_mouse(MouseButton::Left);
        let f = router.process(&tree, &input);
        // Immediate-mode query: was "left" clicked with Left?
        assert!(f.clicked_left("left"));
        assert!(f.clicked("left", MouseButton::Left));
        // Different id or different button → false.
        assert!(!f.clicked_left("right"));
        assert!(!f.clicked("left", MouseButton::Right));
    }

    #[test]
    fn router_frame_query_methods_cover_each_event_kind() {
        // Build a frame manually with one of each event variant and
        // verify each query method matches exactly one.
        let f = RouterFrame {
            events: vec![
                RouterEvent::Hovered("a".into()),
                RouterEvent::Unhovered("b".into()),
                RouterEvent::Pressed("c".into(), MouseButton::Right),
                RouterEvent::Released("d".into(), MouseButton::Middle),
                RouterEvent::Clicked("e".into(), MouseButton::Left),
                RouterEvent::FocusGained("f".into()),
                RouterEvent::FocusLost("g".into()),
            ],
            captured_mouse: true,
            captured_keyboard: true,
        };
        assert!(f.hovered_in("a"));
        assert!(f.hovered_out("b"));
        assert!(f.pressed("c", MouseButton::Right));
        assert!(f.released("d", MouseButton::Middle));
        assert!(f.clicked("e", MouseButton::Left));
        assert!(f.focus_gained("f"));
        assert!(f.focus_lost("g"));
        // Negative checks.
        assert!(!f.hovered_in("b"));
        assert!(!f.clicked_left("c")); // Pressed, not Clicked
    }
}
