//! [`Binding`] — one physical input that can drive a named action.
//!
//! A binding is the smallest unit an [`ActionMap`](super::ActionMap) maps
//! action names to. The enum is intentionally small (keys and mouse buttons
//! today; gamepad / pointer-axis variants will be added without breaking
//! existing serialized maps as long as new variants are appended).

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

use super::InputState;

/// One physical input that can be bound to a named action.
///
/// Two bindings compare equal only if they refer to the exact same physical
/// input — the enum derives `Hash`/`Eq` so a `HashSet<Binding>` can be used
/// to deduplicate a key's contribution to multiple actions without
/// allocating per-action sets.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Binding {
    /// A keyboard key, identified by layout-independent physical position
    /// (the same `KeyCode` an [`InputState`] query takes).
    Key(KeyCode),
    /// A mouse button.
    Mouse(MouseButton),
}

impl Binding {
    /// `true` if this binding's `pressed` edge fired in `input` this frame.
    pub fn pressed(&self, input: &InputState) -> bool {
        match *self {
            Binding::Key(k) => input.pressed(k),
            Binding::Mouse(b) => input.mouse_pressed(b),
        }
    }

    /// `true` if this binding's `released` edge fired in `input` this frame.
    pub fn released(&self, input: &InputState) -> bool {
        match *self {
            Binding::Key(k) => input.released(k),
            Binding::Mouse(b) => input.mouse_released(b),
        }
    }

    /// `true` if this binding is currently held down in `input`.
    pub fn held(&self, input: &InputState) -> bool {
        match *self {
            Binding::Key(k) => input.held(k),
            Binding::Mouse(b) => input.mouse_held(b),
        }
    }

    /// `true` if this binding was held *going into* this frame — i.e. it was
    /// held continuously from before the current frame's events arrived.
    /// Used by [`ActionMap`](super::ActionMap) to recover prior-frame state
    /// from the current frame's snapshot alone, without storing a previous
    /// `InputState`.
    ///
    /// Derivation: a binding was held before the frame iff it is currently
    /// held or was released this frame (either way it was down going in),
    /// **except** when it was also pressed this frame — a same-frame tap
    /// goes idle → pressed → released, so it was not held going in.
    pub(crate) fn held_before_frame(&self, input: &InputState) -> bool {
        (self.held(input) || self.released(input)) && !self.pressed(input)
    }
}

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

    #[test]
    fn key_binding_routes_to_keyboard_queries() {
        let mut input = InputState::new();
        let b = Binding::Key(KeyCode::Space);

        input.press_key(KeyCode::Space);
        assert!(b.pressed(&input));
        assert!(b.held(&input));
        assert!(!b.released(&input));

        input.end_frame();
        assert!(!b.pressed(&input));
        assert!(b.held(&input));

        input.release_key(KeyCode::Space);
        assert!(b.released(&input));
        assert!(!b.held(&input));
    }

    #[test]
    fn mouse_binding_routes_to_mouse_queries() {
        let mut input = InputState::new();
        let b = Binding::Mouse(MouseButton::Right);

        input.press_mouse(MouseButton::Right);
        assert!(b.pressed(&input));
        assert!(b.held(&input));

        input.end_frame();
        input.release_mouse(MouseButton::Right);
        assert!(b.released(&input));
        assert!(!b.held(&input));
    }

    #[test]
    fn held_before_frame_distinguishes_press_release_tap() {
        let b = Binding::Key(KeyCode::KeyJ);

        // Idle → pressed this frame. Not held before.
        let mut input = InputState::new();
        input.press_key(KeyCode::KeyJ);
        assert!(!b.held_before_frame(&input));

        // Held continuously. Held before.
        let mut input = InputState::new();
        input.press_key(KeyCode::KeyJ);
        input.end_frame();
        assert!(b.held_before_frame(&input));

        // Held → released this frame. Held before.
        let mut input = InputState::new();
        input.press_key(KeyCode::KeyJ);
        input.end_frame();
        input.release_key(KeyCode::KeyJ);
        assert!(b.held_before_frame(&input));

        // Same-frame tap (idle → pressed → released). Not held before.
        let mut input = InputState::new();
        input.press_key(KeyCode::KeyJ);
        input.release_key(KeyCode::KeyJ);
        assert!(!b.held_before_frame(&input));
    }

    #[test]
    fn ron_round_trip_preserves_key_and_mouse_variants() {
        let bindings = vec![
            Binding::Key(KeyCode::Space),
            Binding::Mouse(MouseButton::Left),
            Binding::Key(KeyCode::ShiftLeft),
        ];
        let s = ron::to_string(&bindings).unwrap();
        let parsed: Vec<Binding> = ron::from_str(&s).unwrap();
        assert_eq!(parsed, bindings);
    }
}
