//! Input state, modeled on XNA's polling snapshots.
//!
//! `InputState` is deliberately winit-free: it uses Mawida's own [`Key`] and
//! [`MouseButton`] enums. The `app` layer translates winit events into calls on
//! this struct. This keeps the core testable and the public API stable even if
//! the windowing backend changes.
//!
//! Usage pattern each frame:
//! 1. the runtime calls [`InputState::begin_frame`] (rolls "current" into "previous")
//! 2. the runtime feeds events via the `pub(crate)` press/release methods
//! 3. your `update` reads [`is_down`], [`just_pressed`], [`just_released`]

use crate::math::Vec2;
use std::collections::HashSet;

/// A keyboard key. A pragmatic subset; extend as needed. The `app` layer maps
/// `winit::keyboard::KeyCode` into these.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Key {
    // letters
    A, B, C, D, E, F, G, H, I, J, K, L, M,
    N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
    // digits
    Num0, Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9,
    // arrows
    Up, Down, Left, Right,
    // common
    Space, Enter, Escape, Tab, Backspace,
    LShift, RShift, LCtrl, RCtrl, LAlt, RAlt,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MouseButton {
    Left,
    Right,
    Middle,
}

/// Polled input for the current frame.
#[derive(Debug, Default, Clone)]
pub struct InputState {
    keys: HashSet<Key>,
    keys_prev: HashSet<Key>,
    mouse: HashSet<MouseButton>,
    mouse_prev: HashSet<MouseButton>,
    /// Cursor position in logical pixels, origin top-left.
    pub mouse_position: Vec2,
    /// Accumulated scroll delta for this frame (reset on `begin_frame`).
    pub scroll_delta: f32,
}

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

    /// True while the key is held.
    pub fn is_down(&self, key: Key) -> bool {
        self.keys.contains(&key)
    }

    /// True only on the frame the key went down.
    pub fn just_pressed(&self, key: Key) -> bool {
        self.keys.contains(&key) && !self.keys_prev.contains(&key)
    }

    /// True only on the frame the key came up.
    pub fn just_released(&self, key: Key) -> bool {
        !self.keys.contains(&key) && self.keys_prev.contains(&key)
    }

    pub fn mouse_down(&self, button: MouseButton) -> bool {
        self.mouse.contains(&button)
    }

    pub fn mouse_just_pressed(&self, button: MouseButton) -> bool {
        self.mouse.contains(&button) && !self.mouse_prev.contains(&button)
    }

    // --- driven by the runtime (`app` layer) ---

    /// Roll the current state into "previous" so the just_* queries work, and
    /// clear per-frame accumulators. Call once at the top of each frame.
    pub fn begin_frame(&mut self) {
        self.keys_prev = self.keys.clone();
        self.mouse_prev = self.mouse.clone();
        self.scroll_delta = 0.0;
    }

    pub(crate) fn press_key(&mut self, key: Key) {
        self.keys.insert(key);
    }

    pub(crate) fn release_key(&mut self, key: Key) {
        self.keys.remove(&key);
    }

    pub(crate) fn press_mouse(&mut self, button: MouseButton) {
        self.mouse.insert(button);
    }

    pub(crate) fn release_mouse(&mut self, button: MouseButton) {
        self.mouse.remove(&button);
    }
}

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

    #[test]
    fn just_pressed_is_edge_triggered() {
        let mut input = InputState::new();
        input.begin_frame();
        input.press_key(Key::Space);
        assert!(input.is_down(Key::Space));
        assert!(input.just_pressed(Key::Space));

        // next frame, still held: down but not "just" pressed
        input.begin_frame();
        assert!(input.is_down(Key::Space));
        assert!(!input.just_pressed(Key::Space));

        // release
        input.begin_frame();
        input.release_key(Key::Space);
        assert!(!input.is_down(Key::Space));
        assert!(input.just_released(Key::Space));
    }
}
