//! [`ActionMap`] — named actions ↔ physical [`Binding`]s, with defaults,
//! runtime remapping, and RON-persistable user overrides.
//!
//! Game code addresses actions by name (`"Jump"`, `"Fire"`, …) and never
//! the physical key, so the user-facing settings screen can rebind any
//! action without touching gameplay code. Each action carries:
//!
//! - **`defaults`** — the code-defined initial bindings registered when
//!   the action is created. They never change after registration.
//! - **`current`** — the bindings actually queried each frame, initially a
//!   clone of `defaults`. The "Restore defaults" button copies `defaults`
//!   back over `current`.
//!
//! Persistence saves only `current`. On load, the program first registers
//! actions with their defaults from code, then applies the loaded overrides
//! on top — unknown actions in the saved file are skipped (so removing an
//! action in code never breaks an old settings file).
//!
//! # Multi-bind and one-key-many-actions
//!
//! An action can list more than one binding (e.g. `Jump` → `[Space, Mouse4]`)
//! and one physical input can drive more than one action (e.g. `Space` →
//! `Jump` and `Confirm` simultaneously). Edge semantics combine bindings
//! with OR logic but with hysteresis applied at the action level, not the
//! binding level: an already-engaged multi-bind action does not re-fire
//! `action_pressed` when a *second* binding goes down, and does not fire
//! `action_released` until **every** binding has been released. See
//! [`ActionMap::action_pressed`] / [`action_released`](ActionMap::action_released)
//! for the precise definition.
//!
//! ```
//! use oxide_engine::input::{ActionMap, Binding, InputState};
//! use oxide_engine::winit::keyboard::KeyCode;
//!
//! let mut actions = ActionMap::new();
//! actions.register("Jump", [Binding::Key(KeyCode::Space)]);
//!
//! let mut input = InputState::new();
//! input.press_key(KeyCode::Space);
//! assert!(actions.action_pressed("Jump", &input));
//! assert!(actions.action_held("Jump", &input));
//!
//! // Runtime remap. Game code keeps querying "Jump" and is unaffected.
//! actions.set_bindings("Jump", vec![Binding::Key(KeyCode::KeyW)]);
//! assert!(!actions.action_held("Jump", &input)); // Space is no longer "Jump"
//! ```

use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

use super::{Axis2DBinding, AxisBinding, Binding, InputState};
use crate::math::Vec2;

/// One named action and its two binding lists (immutable defaults +
/// runtime-mutable current).
#[derive(Debug, Clone)]
struct Action {
    defaults: Vec<Binding>,
    current: Vec<Binding>,
}

/// One named 1D axis with default + current bindings.
#[derive(Debug, Clone)]
struct AxisAction {
    defaults: AxisBinding,
    current: AxisBinding,
}

/// One named 2D axis with default + current bindings.
#[derive(Debug, Clone)]
struct Axis2DAction {
    defaults: Axis2DBinding,
    current: Axis2DBinding,
}

/// Maps named actions to physical [`Binding`]s, with separate default and
/// current binding lists per action and RON-persistable user overrides.
///
/// Three action kinds live in the same map under disjoint name spaces:
/// **buttons** (one-shot events, [`register`](Self::register) /
/// [`action_pressed`](Self::action_pressed)), **1D axes** (float values
/// composed from + / − binding sets, [`register_axis`](Self::register_axis) /
/// [`axis`](Self::axis)), and **2D axes** (a pair of 1D axes returning a
/// [`Vec2`], [`register_axis_2d`](Self::register_axis_2d) /
/// [`axis_2d`](Self::axis_2d)). The same string name can be reused across
/// kinds without conflict — `Move` can be both a 2D axis and a button if
/// that's what a project wants.
///
/// See the [module docs](self) for the rationale behind defaults vs current
/// bindings and the action-level edge hysteresis.
#[derive(Debug, Default, Clone)]
pub struct ActionMap {
    actions: BTreeMap<String, Action>,
    axes: BTreeMap<String, AxisAction>,
    axes_2d: BTreeMap<String, Axis2DAction>,
}

impl ActionMap {
    /// A new empty map. Register actions with [`register`](Self::register).
    pub fn new() -> Self {
        Self::default()
    }

    /// Registers an action `name` with its code-defined default bindings.
    /// The current bindings start as a clone of the defaults.
    ///
    /// If `name` is already registered, the existing `current` bindings are
    /// preserved (so a programmer adding a new default binding mid-project
    /// does not overwrite a user's remap), but the `defaults` list is
    /// replaced — restoring defaults from this point on uses the new list.
    pub fn register<I>(&mut self, name: impl Into<String>, defaults: I) -> &mut Self
    where
        I: IntoIterator<Item = Binding>,
    {
        let name = name.into();
        let defaults: Vec<Binding> = defaults.into_iter().collect();
        self.actions
            .entry(name)
            .and_modify(|a| a.defaults = defaults.clone())
            .or_insert_with(|| Action {
                current: defaults.clone(),
                defaults,
            });
        self
    }

    /// Removes the action. Returns `true` if it existed.
    pub fn unregister(&mut self, name: &str) -> bool {
        self.actions.remove(name).is_some()
    }

    /// `true` if `name` is registered.
    pub fn has(&self, name: &str) -> bool {
        self.actions.contains_key(name)
    }

    /// Iterates the registered action names in sorted order.
    pub fn actions(&self) -> impl Iterator<Item = &str> + '_ {
        self.actions.keys().map(String::as_str)
    }

    /// The current bindings driving `name`, or `&[]` if unregistered.
    pub fn bindings(&self, name: &str) -> &[Binding] {
        self.actions
            .get(name)
            .map(|a| a.current.as_slice())
            .unwrap_or(&[])
    }

    /// The code-defined default bindings for `name`, or `&[]` if unregistered.
    pub fn defaults(&self, name: &str) -> &[Binding] {
        self.actions
            .get(name)
            .map(|a| a.defaults.as_slice())
            .unwrap_or(&[])
    }

    /// Replaces the current bindings for `name`. No-op if unregistered.
    pub fn set_bindings(&mut self, name: &str, bindings: Vec<Binding>) {
        if let Some(action) = self.actions.get_mut(name) {
            action.current = bindings;
        }
    }

    /// Appends one binding to `name`'s current list (no-op if unregistered;
    /// duplicates are skipped so adding the same binding twice is idempotent).
    pub fn add_binding(&mut self, name: &str, binding: Binding) {
        if let Some(action) = self.actions.get_mut(name) {
            if !action.current.contains(&binding) {
                action.current.push(binding);
            }
        }
    }

    /// Removes one binding from `name`'s current list. Returns whether it was
    /// present. No-op (and `false`) if the action is unregistered.
    pub fn remove_binding(&mut self, name: &str, binding: Binding) -> bool {
        let Some(action) = self.actions.get_mut(name) else {
            return false;
        };
        let before = action.current.len();
        action.current.retain(|b| *b != binding);
        action.current.len() != before
    }

    /// Empties `name`'s current list (so the action becomes unbindable until
    /// new bindings are set or defaults are restored).
    pub fn clear_bindings(&mut self, name: &str) {
        if let Some(action) = self.actions.get_mut(name) {
            action.current.clear();
        }
    }

    /// Resets `name`'s current bindings back to its defaults.
    pub fn restore_defaults(&mut self, name: &str) {
        if let Some(action) = self.actions.get_mut(name) {
            action.current = action.defaults.clone();
        }
    }

    /// Resets every action's current bindings back to its defaults — across
    /// all three action kinds (buttons, 1D axes, 2D axes).
    pub fn restore_all_defaults(&mut self) {
        for action in self.actions.values_mut() {
            action.current = action.defaults.clone();
        }
        for axis in self.axes.values_mut() {
            axis.current = axis.defaults.clone();
        }
        for axis in self.axes_2d.values_mut() {
            axis.current = axis.defaults.clone();
        }
    }

    // --- 1D axes ----------------------------------------------------------

    /// Registers a 1D axis `name` with code-defined default bindings. As
    /// with buttons, re-registering preserves the user's current bindings
    /// but updates the defaults list.
    pub fn register_axis(&mut self, name: impl Into<String>, defaults: AxisBinding) -> &mut Self {
        let name = name.into();
        self.axes
            .entry(name)
            .and_modify(|a| a.defaults = defaults.clone())
            .or_insert_with(|| AxisAction {
                current: defaults.clone(),
                defaults,
            });
        self
    }

    /// Removes the 1D axis `name`. Returns whether it existed.
    pub fn unregister_axis(&mut self, name: &str) -> bool {
        self.axes.remove(name).is_some()
    }

    /// `true` if a 1D axis `name` is registered.
    pub fn has_axis(&self, name: &str) -> bool {
        self.axes.contains_key(name)
    }

    /// Iterates registered 1D axis names in sorted order.
    pub fn axes(&self) -> impl Iterator<Item = &str> + '_ {
        self.axes.keys().map(String::as_str)
    }

    /// The current bindings for `name`, or `None` if unregistered.
    pub fn axis_bindings(&self, name: &str) -> Option<&AxisBinding> {
        self.axes.get(name).map(|a| &a.current)
    }

    /// The default bindings for `name`, or `None` if unregistered.
    pub fn axis_defaults(&self, name: &str) -> Option<&AxisBinding> {
        self.axes.get(name).map(|a| &a.defaults)
    }

    /// Replaces the current bindings for the 1D axis `name`. No-op if
    /// unregistered.
    pub fn set_axis_bindings(&mut self, name: &str, bindings: AxisBinding) {
        if let Some(axis) = self.axes.get_mut(name) {
            axis.current = bindings;
        }
    }

    /// Resets axis `name`'s current bindings back to its defaults.
    pub fn restore_axis_defaults(&mut self, name: &str) {
        if let Some(axis) = self.axes.get_mut(name) {
            axis.current = axis.defaults.clone();
        }
    }

    /// Evaluates 1D axis `name` against `input`. Returns 0.0 for
    /// unregistered axes.
    pub fn axis(&self, name: &str, input: &InputState) -> f32 {
        self.axes
            .get(name)
            .map(|a| a.current.value(input))
            .unwrap_or(0.0)
    }

    // --- 2D axes ----------------------------------------------------------

    /// Registers a 2D axis `name` with code-defined default bindings.
    pub fn register_axis_2d(
        &mut self,
        name: impl Into<String>,
        defaults: Axis2DBinding,
    ) -> &mut Self {
        let name = name.into();
        self.axes_2d
            .entry(name)
            .and_modify(|a| a.defaults = defaults.clone())
            .or_insert_with(|| Axis2DAction {
                current: defaults.clone(),
                defaults,
            });
        self
    }

    /// Removes the 2D axis `name`. Returns whether it existed.
    pub fn unregister_axis_2d(&mut self, name: &str) -> bool {
        self.axes_2d.remove(name).is_some()
    }

    /// `true` if a 2D axis `name` is registered.
    pub fn has_axis_2d(&self, name: &str) -> bool {
        self.axes_2d.contains_key(name)
    }

    /// Iterates registered 2D axis names in sorted order.
    pub fn axes_2d(&self) -> impl Iterator<Item = &str> + '_ {
        self.axes_2d.keys().map(String::as_str)
    }

    /// The current bindings for `name`, or `None` if unregistered.
    pub fn axis_2d_bindings(&self, name: &str) -> Option<&Axis2DBinding> {
        self.axes_2d.get(name).map(|a| &a.current)
    }

    /// The default bindings for `name`, or `None` if unregistered.
    pub fn axis_2d_defaults(&self, name: &str) -> Option<&Axis2DBinding> {
        self.axes_2d.get(name).map(|a| &a.defaults)
    }

    /// Replaces the current bindings for the 2D axis `name`. No-op if
    /// unregistered.
    pub fn set_axis_2d_bindings(&mut self, name: &str, bindings: Axis2DBinding) {
        if let Some(axis) = self.axes_2d.get_mut(name) {
            axis.current = bindings;
        }
    }

    /// Resets 2D axis `name`'s current bindings back to its defaults.
    pub fn restore_axis_2d_defaults(&mut self, name: &str) {
        if let Some(axis) = self.axes_2d.get_mut(name) {
            axis.current = axis.defaults.clone();
        }
    }

    /// Evaluates 2D axis `name` against `input`. Returns [`Vec2::ZERO`] for
    /// unregistered axes. Diagonals are unnormalized — see [`Axis2DBinding`].
    pub fn axis_2d(&self, name: &str, input: &InputState) -> Vec2 {
        self.axes_2d
            .get(name)
            .map(|a| a.current.value(input))
            .unwrap_or(Vec2::ZERO)
    }

    // --- Action edge semantics --------------------------------------------

    /// `true` if `name`'s `pressed` edge fired this frame.
    ///
    /// Defined so the edge fires only when the action *transitions* from
    /// not-held to held: any current binding became pressed this frame and
    /// no current binding was held going into the frame. A second binding
    /// going down while the action is already engaged does **not** retrigger
    /// the edge.
    ///
    /// Returns `false` for unregistered actions.
    pub fn action_pressed(&self, name: &str, input: &InputState) -> bool {
        let Some(action) = self.actions.get(name) else {
            return false;
        };
        let any_pressed = action.current.iter().any(|b| b.pressed(input));
        let any_held_before = action.current.iter().any(|b| b.held_before_frame(input));
        any_pressed && !any_held_before
    }

    /// `true` if `name`'s `released` edge fired this frame.
    ///
    /// Defined so the edge fires only when the action *transitions* from
    /// held to not-held: any current binding was released this frame and no
    /// current binding remains held. Releasing one binding of a multi-bind
    /// action while another is still held does **not** trigger the edge.
    ///
    /// Returns `false` for unregistered actions.
    pub fn action_released(&self, name: &str, input: &InputState) -> bool {
        let Some(action) = self.actions.get(name) else {
            return false;
        };
        let any_released = action.current.iter().any(|b| b.released(input));
        let any_held_now = action.current.iter().any(|b| b.held(input));
        any_released && !any_held_now
    }

    /// `true` if any current binding of `name` is held right now.
    ///
    /// Returns `false` for unregistered actions.
    pub fn action_held(&self, name: &str, input: &InputState) -> bool {
        let Some(action) = self.actions.get(name) else {
            return false;
        };
        action.current.iter().any(|b| b.held(input))
    }

    // --- Persistence ------------------------------------------------------

    /// Captures the **current** (possibly remapped) bindings as a
    /// serializable [`ActionOverrides`] across all three action kinds.
    ///
    /// Defaults are deliberately excluded: defaults live in code (the
    /// program calls [`register`](Self::register) /
    /// [`register_axis`](Self::register_axis) /
    /// [`register_axis_2d`](Self::register_axis_2d) at startup), so on
    /// load the program re-registers actions and then applies the saved
    /// overrides on top. That keeps the saved file small and lets the
    /// program evolve its default bindings without invalidating user data.
    pub fn overrides(&self) -> ActionOverrides {
        ActionOverrides {
            bindings: self
                .actions
                .iter()
                .map(|(name, a)| (name.clone(), a.current.clone()))
                .collect(),
            axes: self
                .axes
                .iter()
                .map(|(name, a)| (name.clone(), a.current.clone()))
                .collect(),
            axes_2d: self
                .axes_2d
                .iter()
                .map(|(name, a)| (name.clone(), a.current.clone()))
                .collect(),
        }
    }

    /// Applies saved [`ActionOverrides`] on top of the currently-registered
    /// actions. Unknown action names (in any kind) are skipped — removing
    /// an action from code never breaks an old settings file — and
    /// registered actions absent from `overrides` keep whatever current
    /// bindings they already have.
    pub fn apply_overrides(&mut self, overrides: &ActionOverrides) {
        for (name, bindings) in &overrides.bindings {
            if let Some(action) = self.actions.get_mut(name) {
                action.current = bindings.clone();
            }
        }
        for (name, axis) in &overrides.axes {
            if let Some(entry) = self.axes.get_mut(name) {
                entry.current = axis.clone();
            }
        }
        for (name, axis) in &overrides.axes_2d {
            if let Some(entry) = self.axes_2d.get_mut(name) {
                entry.current = axis.clone();
            }
        }
    }
}

/// A serializable snapshot of an [`ActionMap`]'s current bindings — across
/// all three action kinds (buttons, 1D axes, 2D axes).
///
/// Round-trips through RON for storage in the settings framework, but is
/// also useful as a standalone copy/paste payload (export bindings from one
/// install, import on another).
///
/// Missing kind sub-maps deserialize as empty (via `#[serde(default)]`), so
/// an older settings file that only stored button overrides still loads
/// cleanly after axes are added to a project.
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActionOverrides {
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    bindings: BTreeMap<String, Vec<Binding>>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    axes: BTreeMap<String, AxisBinding>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    axes_2d: BTreeMap<String, Axis2DBinding>,
}

impl ActionOverrides {
    /// A new empty overrides set.
    pub fn new() -> Self {
        Self::default()
    }

    /// `true` if no overrides are stored in any kind.
    pub fn is_empty(&self) -> bool {
        self.bindings.is_empty() && self.axes.is_empty() && self.axes_2d.is_empty()
    }

    /// Total number of override entries across all kinds.
    pub fn len(&self) -> usize {
        self.bindings.len() + self.axes.len() + self.axes_2d.len()
    }

    /// The override button bindings for `name`, or `&[]` if none.
    pub fn get(&self, name: &str) -> &[Binding] {
        self.bindings.get(name).map(Vec::as_slice).unwrap_or(&[])
    }

    /// The override 1D axis bindings for `name`, or `None`.
    pub fn get_axis(&self, name: &str) -> Option<&AxisBinding> {
        self.axes.get(name)
    }

    /// The override 2D axis bindings for `name`, or `None`.
    pub fn get_axis_2d(&self, name: &str) -> Option<&Axis2DBinding> {
        self.axes_2d.get(name)
    }

    /// Iterates `(button action name, bindings)` pairs in name-sorted order.
    pub fn iter(&self) -> impl Iterator<Item = (&str, &[Binding])> {
        self.bindings
            .iter()
            .map(|(k, v)| (k.as_str(), v.as_slice()))
    }

    /// Iterates `(1D axis name, bindings)` pairs in name-sorted order.
    pub fn iter_axes(&self) -> impl Iterator<Item = (&str, &AxisBinding)> {
        self.axes.iter().map(|(k, v)| (k.as_str(), v))
    }

    /// Iterates `(2D axis name, bindings)` pairs in name-sorted order.
    pub fn iter_axes_2d(&self) -> impl Iterator<Item = (&str, &Axis2DBinding)> {
        self.axes_2d.iter().map(|(k, v)| (k.as_str(), v))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use winit::event::MouseButton;
    use winit::keyboard::KeyCode;

    fn jump_only() -> ActionMap {
        let mut m = ActionMap::new();
        m.register("Jump", [Binding::Key(KeyCode::Space)]);
        m
    }

    #[test]
    fn default_binding_drives_action() {
        let actions = jump_only();
        let mut input = InputState::new();

        input.press_key(KeyCode::Space);
        assert!(actions.action_pressed("Jump", &input));
        assert!(actions.action_held("Jump", &input));
        assert!(!actions.action_released("Jump", &input));

        input.end_frame();
        assert!(!actions.action_pressed("Jump", &input));
        assert!(actions.action_held("Jump", &input));

        input.release_key(KeyCode::Space);
        assert!(actions.action_released("Jump", &input));
        assert!(!actions.action_held("Jump", &input));
    }

    #[test]
    fn unregistered_action_returns_false() {
        let actions = ActionMap::new();
        let input = InputState::new();
        assert!(!actions.action_pressed("Nope", &input));
        assert!(!actions.action_held("Nope", &input));
        assert!(!actions.action_released("Nope", &input));
        assert_eq!(actions.bindings("Nope"), &[] as &[Binding]);
    }

    #[test]
    fn multi_bind_first_press_fires_edge_second_does_not() {
        let mut actions = ActionMap::new();
        actions.register(
            "Jump",
            [Binding::Key(KeyCode::Space), Binding::Key(KeyCode::KeyJ)],
        );
        let mut input = InputState::new();

        // Press Space — fires pressed edge.
        input.press_key(KeyCode::Space);
        assert!(actions.action_pressed("Jump", &input));
        assert!(actions.action_held("Jump", &input));
        input.end_frame();

        // Now press J while Space still held — must NOT re-fire pressed.
        input.press_key(KeyCode::KeyJ);
        assert!(
            !actions.action_pressed("Jump", &input),
            "already-engaged multi-bind must not re-fire pressed"
        );
        assert!(actions.action_held("Jump", &input));
    }

    #[test]
    fn multi_bind_release_one_keeps_action_held() {
        let mut actions = ActionMap::new();
        actions.register(
            "Jump",
            [Binding::Key(KeyCode::Space), Binding::Key(KeyCode::KeyJ)],
        );
        let mut input = InputState::new();
        input.press_key(KeyCode::Space);
        input.press_key(KeyCode::KeyJ);
        input.end_frame();

        input.release_key(KeyCode::Space);
        assert!(
            !actions.action_released("Jump", &input),
            "another binding is still held — must not fire released"
        );
        assert!(actions.action_held("Jump", &input));

        input.end_frame();
        input.release_key(KeyCode::KeyJ);
        assert!(actions.action_released("Jump", &input));
        assert!(!actions.action_held("Jump", &input));
    }

    #[test]
    fn one_key_drives_multiple_actions_simultaneously() {
        let mut actions = ActionMap::new();
        actions.register("Jump", [Binding::Key(KeyCode::Space)]);
        actions.register("Confirm", [Binding::Key(KeyCode::Space)]);

        let mut input = InputState::new();
        input.press_key(KeyCode::Space);

        assert!(actions.action_pressed("Jump", &input));
        assert!(actions.action_pressed("Confirm", &input));
        assert!(actions.action_held("Jump", &input));
        assert!(actions.action_held("Confirm", &input));
    }

    #[test]
    fn runtime_remap_changes_behavior_without_renaming_action() {
        let mut actions = jump_only();
        let mut input = InputState::new();

        // Initially Space → Jump.
        input.press_key(KeyCode::Space);
        assert!(actions.action_pressed("Jump", &input));

        // Remap Jump to W. Game code still queries "Jump".
        input.end_frame();
        actions.set_bindings("Jump", vec![Binding::Key(KeyCode::KeyW)]);
        assert!(
            !actions.action_held("Jump", &input),
            "Space must no longer drive Jump after remap"
        );

        input.press_key(KeyCode::KeyW);
        assert!(actions.action_pressed("Jump", &input));
    }

    #[test]
    fn add_binding_is_idempotent_and_remove_binding_returns_presence() {
        let mut actions = jump_only();
        actions.add_binding("Jump", Binding::Key(KeyCode::KeyJ));
        actions.add_binding("Jump", Binding::Key(KeyCode::KeyJ)); // dup ignored
        assert_eq!(
            actions.bindings("Jump"),
            &[Binding::Key(KeyCode::Space), Binding::Key(KeyCode::KeyJ)]
        );

        assert!(actions.remove_binding("Jump", Binding::Key(KeyCode::KeyJ)));
        assert!(!actions.remove_binding("Jump", Binding::Key(KeyCode::KeyJ)));
        assert_eq!(actions.bindings("Jump"), &[Binding::Key(KeyCode::Space)]);
    }

    #[test]
    fn restore_defaults_undoes_runtime_remap() {
        let mut actions = jump_only();
        actions.set_bindings("Jump", vec![Binding::Key(KeyCode::KeyW)]);
        assert_eq!(actions.bindings("Jump"), &[Binding::Key(KeyCode::KeyW)]);

        actions.restore_defaults("Jump");
        assert_eq!(actions.bindings("Jump"), &[Binding::Key(KeyCode::Space)]);
    }

    #[test]
    fn re_registering_preserves_remap_but_updates_defaults() {
        let mut actions = jump_only();
        actions.set_bindings("Jump", vec![Binding::Key(KeyCode::KeyW)]);

        // A later code change adds a second default binding.
        actions.register(
            "Jump",
            [
                Binding::Key(KeyCode::Space),
                Binding::Mouse(MouseButton::Other(4)),
            ],
        );

        // Current bindings (the user's remap) are preserved…
        assert_eq!(actions.bindings("Jump"), &[Binding::Key(KeyCode::KeyW)]);
        // …but restoring defaults now picks up the new code-defined list.
        actions.restore_defaults("Jump");
        assert_eq!(
            actions.bindings("Jump"),
            &[
                Binding::Key(KeyCode::Space),
                Binding::Mouse(MouseButton::Other(4)),
            ]
        );
    }

    #[test]
    fn overrides_round_trip_through_ron() {
        let mut actions = ActionMap::new();
        actions.register("Jump", [Binding::Key(KeyCode::Space)]);
        actions.register("Fire", [Binding::Mouse(MouseButton::Left)]);
        actions.set_bindings("Jump", vec![Binding::Key(KeyCode::KeyW)]);

        let overrides = actions.overrides();
        let serialized = ron::to_string(&overrides).unwrap();
        let parsed: ActionOverrides = ron::from_str(&serialized).unwrap();
        assert_eq!(parsed, overrides);

        // Applying the round-tripped overrides on a freshly-registered map
        // recreates the remap — and unknown actions in the file are skipped.
        let mut fresh = ActionMap::new();
        fresh.register("Jump", [Binding::Key(KeyCode::Space)]);
        // Note: "Fire" is intentionally NOT registered in `fresh`.
        fresh.apply_overrides(&parsed);
        assert_eq!(fresh.bindings("Jump"), &[Binding::Key(KeyCode::KeyW)]);
        assert!(!fresh.has("Fire"), "unknown action stayed unregistered");
    }

    #[test]
    fn apply_overrides_skips_unregistered_actions() {
        let mut actions = jump_only();
        let mut overrides = ActionOverrides::new();
        overrides
            .bindings
            .insert("Phantom".into(), vec![Binding::Key(KeyCode::KeyX)]);
        overrides
            .bindings
            .insert("Jump".into(), vec![Binding::Key(KeyCode::KeyW)]);

        actions.apply_overrides(&overrides);
        assert_eq!(actions.bindings("Jump"), &[Binding::Key(KeyCode::KeyW)]);
        assert!(!actions.has("Phantom"));
    }

    #[test]
    fn unregister_drops_the_action() {
        let mut actions = jump_only();
        assert!(actions.unregister("Jump"));
        assert!(!actions.has("Jump"));
        assert!(!actions.unregister("Jump"));
    }

    #[test]
    fn clear_bindings_makes_action_unfireable_until_restored() {
        let mut actions = jump_only();
        actions.clear_bindings("Jump");

        let mut input = InputState::new();
        input.press_key(KeyCode::Space);
        assert!(!actions.action_pressed("Jump", &input));
        assert!(!actions.action_held("Jump", &input));

        actions.restore_defaults("Jump");
        assert!(actions.action_pressed("Jump", &input));
    }

    // --- Piece 3: 1D/2D axes through ActionMap ----------------------------

    fn move_x() -> ActionMap {
        let mut m = ActionMap::new();
        m.register_axis(
            "MoveX",
            AxisBinding::new([Binding::Key(KeyCode::KeyD)], [Binding::Key(KeyCode::KeyA)]),
        );
        m
    }

    #[test]
    fn axis_returns_zero_when_unregistered_or_idle() {
        let map = ActionMap::new();
        let input = InputState::new();
        assert_eq!(map.axis("MoveX", &input), 0.0);
        assert_eq!(map.axis_2d("Move", &input), Vec2::ZERO);

        let map = move_x();
        let input = InputState::new();
        assert_eq!(map.axis("MoveX", &input), 0.0);
    }

    #[test]
    fn axis_resolves_positive_and_negative_directions() {
        let map = move_x();
        let mut input = InputState::new();

        input.press_key(KeyCode::KeyD);
        assert_eq!(map.axis("MoveX", &input), 1.0);

        input.release_key(KeyCode::KeyD);
        input.press_key(KeyCode::KeyA);
        assert_eq!(map.axis("MoveX", &input), -1.0);
    }

    #[test]
    fn axis_remap_changes_binding_without_renaming() {
        let mut map = move_x();
        map.set_axis_bindings(
            "MoveX",
            AxisBinding::new(
                [Binding::Key(KeyCode::ArrowRight)],
                [Binding::Key(KeyCode::ArrowLeft)],
            ),
        );

        let mut input = InputState::new();
        input.press_key(KeyCode::KeyD);
        assert_eq!(
            map.axis("MoveX", &input),
            0.0,
            "old binding no longer drives the axis"
        );

        input.press_key(KeyCode::ArrowRight);
        assert_eq!(map.axis("MoveX", &input), 1.0);

        map.restore_axis_defaults("MoveX");
        let mut input = InputState::new();
        input.press_key(KeyCode::KeyD);
        assert_eq!(map.axis("MoveX", &input), 1.0);
    }

    #[test]
    fn axis_2d_resolves_wasd() {
        let mut map = ActionMap::new();
        map.register_axis_2d(
            "Move",
            Axis2DBinding::new(
                [Binding::Key(KeyCode::KeyD)],
                [Binding::Key(KeyCode::KeyA)],
                [Binding::Key(KeyCode::KeyW)],
                [Binding::Key(KeyCode::KeyS)],
            ),
        );

        let mut input = InputState::new();
        input.press_key(KeyCode::KeyW);
        input.press_key(KeyCode::KeyA);
        assert_eq!(map.axis_2d("Move", &input), Vec2::new(-1.0, 1.0));
    }

    #[test]
    fn restore_all_defaults_covers_buttons_and_axes() {
        let mut map = ActionMap::new();
        map.register("Jump", [Binding::Key(KeyCode::Space)]);
        map.register_axis(
            "MoveX",
            AxisBinding::new([Binding::Key(KeyCode::KeyD)], [Binding::Key(KeyCode::KeyA)]),
        );
        map.register_axis_2d(
            "Move",
            Axis2DBinding::new(
                [Binding::Key(KeyCode::KeyD)],
                [Binding::Key(KeyCode::KeyA)],
                [Binding::Key(KeyCode::KeyW)],
                [Binding::Key(KeyCode::KeyS)],
            ),
        );

        // Remap all three kinds.
        map.set_bindings("Jump", vec![Binding::Key(KeyCode::KeyJ)]);
        map.set_axis_bindings(
            "MoveX",
            AxisBinding::new(
                [Binding::Key(KeyCode::ArrowRight)],
                [Binding::Key(KeyCode::ArrowLeft)],
            ),
        );
        map.set_axis_2d_bindings(
            "Move",
            Axis2DBinding::new(
                [Binding::Key(KeyCode::ArrowRight)],
                [Binding::Key(KeyCode::ArrowLeft)],
                [Binding::Key(KeyCode::ArrowUp)],
                [Binding::Key(KeyCode::ArrowDown)],
            ),
        );

        map.restore_all_defaults();

        assert_eq!(map.bindings("Jump"), &[Binding::Key(KeyCode::Space)]);
        assert_eq!(
            map.axis_bindings("MoveX").unwrap().positive,
            vec![Binding::Key(KeyCode::KeyD)]
        );
        assert_eq!(
            map.axis_2d_bindings("Move").unwrap().y.positive,
            vec![Binding::Key(KeyCode::KeyW)]
        );
    }

    #[test]
    fn three_action_kinds_share_a_name_without_conflict() {
        // Buttons, 1D axes, and 2D axes have disjoint namespaces.
        let mut map = ActionMap::new();
        map.register("Move", [Binding::Key(KeyCode::KeyM)]);
        map.register_axis(
            "Move",
            AxisBinding::new([Binding::Key(KeyCode::KeyD)], [Binding::Key(KeyCode::KeyA)]),
        );
        map.register_axis_2d(
            "Move",
            Axis2DBinding::new(
                [Binding::Key(KeyCode::ArrowRight)],
                [Binding::Key(KeyCode::ArrowLeft)],
                [Binding::Key(KeyCode::ArrowUp)],
                [Binding::Key(KeyCode::ArrowDown)],
            ),
        );

        let mut input = InputState::new();
        input.press_key(KeyCode::KeyM);
        input.press_key(KeyCode::KeyD);
        input.press_key(KeyCode::ArrowUp);

        assert!(map.action_held("Move", &input));
        assert_eq!(map.axis("Move", &input), 1.0);
        assert_eq!(map.axis_2d("Move", &input), Vec2::new(0.0, 1.0));
    }

    #[test]
    fn overrides_round_trip_includes_axes() {
        let mut map = ActionMap::new();
        map.register("Jump", [Binding::Key(KeyCode::Space)]);
        map.register_axis(
            "MoveX",
            AxisBinding::new([Binding::Key(KeyCode::KeyD)], [Binding::Key(KeyCode::KeyA)]),
        );
        map.register_axis_2d(
            "Move",
            Axis2DBinding::new(
                [Binding::Key(KeyCode::KeyD)],
                [Binding::Key(KeyCode::KeyA)],
                [Binding::Key(KeyCode::KeyW)],
                [Binding::Key(KeyCode::KeyS)],
            ),
        );

        // Remap each kind.
        map.set_bindings("Jump", vec![Binding::Key(KeyCode::KeyJ)]);
        map.set_axis_bindings(
            "MoveX",
            AxisBinding::new(
                [Binding::Key(KeyCode::ArrowRight)],
                [Binding::Key(KeyCode::ArrowLeft)],
            ),
        );
        map.set_axis_2d_bindings(
            "Move",
            Axis2DBinding::new(
                [Binding::Key(KeyCode::ArrowRight)],
                [Binding::Key(KeyCode::ArrowLeft)],
                [Binding::Key(KeyCode::ArrowUp)],
                [Binding::Key(KeyCode::ArrowDown)],
            ),
        );

        let overrides = map.overrides();
        let s = ron::to_string(&overrides).unwrap();
        let parsed: ActionOverrides = ron::from_str(&s).unwrap();
        assert_eq!(parsed, overrides);
        assert_eq!(overrides.len(), 3);
        assert!(!overrides.is_empty());

        // Replay on a freshly-registered map.
        let mut fresh = ActionMap::new();
        fresh.register("Jump", [Binding::Key(KeyCode::Space)]);
        fresh.register_axis(
            "MoveX",
            AxisBinding::new([Binding::Key(KeyCode::KeyD)], [Binding::Key(KeyCode::KeyA)]),
        );
        fresh.register_axis_2d(
            "Move",
            Axis2DBinding::new(
                [Binding::Key(KeyCode::KeyD)],
                [Binding::Key(KeyCode::KeyA)],
                [Binding::Key(KeyCode::KeyW)],
                [Binding::Key(KeyCode::KeyS)],
            ),
        );
        fresh.apply_overrides(&parsed);

        assert_eq!(fresh.bindings("Jump"), &[Binding::Key(KeyCode::KeyJ)]);
        assert_eq!(
            fresh.axis_bindings("MoveX").unwrap().positive,
            vec![Binding::Key(KeyCode::ArrowRight)]
        );
        assert_eq!(
            fresh.axis_2d_bindings("Move").unwrap().x.negative,
            vec![Binding::Key(KeyCode::ArrowLeft)]
        );
    }

    #[test]
    fn old_settings_file_without_axes_loads_cleanly() {
        // A legacy file that only stored button overrides — no `axes` or
        // `axes_2d` fields. Must still load, leaving registered axes at
        // their defaults.
        let legacy_ron = r#"(bindings: {"Jump": [Key(KeyW)]})"#;
        let parsed: ActionOverrides = ron::from_str(legacy_ron).unwrap();
        assert_eq!(parsed.len(), 1);
        assert_eq!(parsed.get_axis("MoveX"), None);

        let mut map = ActionMap::new();
        map.register("Jump", [Binding::Key(KeyCode::Space)]);
        map.register_axis(
            "MoveX",
            AxisBinding::new([Binding::Key(KeyCode::KeyD)], [Binding::Key(KeyCode::KeyA)]),
        );
        map.apply_overrides(&parsed);
        assert_eq!(map.bindings("Jump"), &[Binding::Key(KeyCode::KeyW)]);
        // Axis bindings untouched — still defaults.
        assert_eq!(
            map.axis_bindings("MoveX").unwrap().positive,
            vec![Binding::Key(KeyCode::KeyD)]
        );
    }

    #[test]
    fn unregister_axis_drops_each_kind_independently() {
        let mut map = ActionMap::new();
        map.register_axis("MoveX", AxisBinding::new([Binding::Key(KeyCode::KeyD)], []));
        assert!(map.has_axis("MoveX"));
        assert!(map.unregister_axis("MoveX"));
        assert!(!map.has_axis("MoveX"));
        assert!(!map.unregister_axis("MoveX"));
    }
}
