//! The keymap state machine.
//!
//! `KeymapState` processes a stream of [`Key`] presses and emits [`Outcome`]s.
//! It supports:
//!
//! - **Count prefixes**: digits build a numeric multiplier before the binding
//!   fires (e.g. `5j` → `nav.down` with count 5).  `0` alone is treated as a
//!   regular key (not a count) so it can be bound; `10`, `20`, … work normally.
//!
//! - **Multi-key sequences**: bindings like `gg`, `zo`, `zc` are resolved by
//!   accumulating pressed keys until an exact match is found.  If the
//!   accumulated sequence stops being a prefix of any binding, the machine
//!   discards the prefix and retries with just the latest key.
//!
//! - **Configurable bindings**: the `BindingSet` is injected at construction;
//!   user overrides are applied before constructing the state.

use crate::{
    binding::{BindingSet, LookupResult},
    key::Key,
};

/// What the state machine decided after processing one key press.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Outcome {
    /// An action was fully resolved.
    Action {
        /// The action name (see [`crate::actions`]).
        name: String,
        /// The count prefix (always ≥ 1; `1` if no prefix was typed).
        count: u32,
    },
    /// The key was consumed but we are waiting for more keys.
    Pending,
    /// The key (or the accumulated sequence) did not match any binding.
    Unbound,
}

/// The keymap state machine.
#[derive(Debug)]
pub struct KeymapState {
    bindings: BindingSet,
    /// Keys accumulated so far in the current multi-key sequence.
    pending: Vec<Key>,
    /// The count prefix typed before the current sequence (0 = none).
    count: u32,
}

impl KeymapState {
    pub fn new(bindings: BindingSet) -> Self {
        Self {
            bindings,
            pending: Vec::new(),
            count: 0,
        }
    }

    /// Process one key press and return the outcome.
    pub fn process(&mut self, key: Key) -> Outcome {
        // Count-building: only when no sequence is in progress.
        if self.pending.is_empty() {
            if let Some(digit) = digit_of(&key) {
                // '0' alone is not a count-start — it's treated as a key.
                // Once a count > 0 has started, '0' extends it (e.g., "10j").
                if self.count > 0 || digit != 0 {
                    self.count = self.count.saturating_mul(10).saturating_add(digit);
                    return Outcome::Pending;
                }
            }
        }

        // Append to the in-progress sequence and look it up.
        self.pending.push(key.clone());

        match self.bindings.lookup(&self.pending) {
            LookupResult::Exact(action) => {
                let count = if self.count == 0 { 1 } else { self.count };
                let outcome = Outcome::Action {
                    name: action,
                    count,
                };
                self.reset();
                outcome
            }
            LookupResult::Prefix => Outcome::Pending,
            LookupResult::NoMatch => {
                // The accumulated sequence is a dead end.
                // Discard the prefix and retry with only the last key,
                // unless this is already a single-key sequence.
                if self.pending.len() > 1 {
                    let last = key; // the key we just pushed
                    self.pending.clear();
                    self.count = 0;
                    return self.process(last);
                }
                // Single key and still no match → unbound.
                self.reset();
                Outcome::Unbound
            }
        }
    }

    /// Reset the state machine to idle (clears pending keys and count).
    pub fn reset(&mut self) {
        self.pending.clear();
        self.count = 0;
    }

    /// Keys accumulated so far (useful for displaying a "pending sequence" hint).
    pub fn pending_keys(&self) -> &[Key] {
        &self.pending
    }

    /// Count prefix typed so far (0 = none).
    pub fn current_count(&self) -> u32 {
        self.count
    }
}

/// If `key` is an unmodified digit, return its numeric value; otherwise `None`.
fn digit_of(key: &Key) -> Option<u32> {
    if key.ctrl || key.shift || key.alt || key.meta {
        return None;
    }
    if let crate::key::KeyCode::Char(c) = key.code {
        c.to_digit(10)
    } else {
        None
    }
}

// ── Tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use crate::key::{Key, KeyCode};

    fn make_state() -> KeymapState {
        let mut bindings = BindingSet::new();
        bindings.add_parsed("j", "nav.down").unwrap();
        bindings.add_parsed("k", "nav.up").unwrap();
        bindings.add_parsed("<Down>", "nav.down").unwrap();
        bindings.add_parsed("gg", "nav.top").unwrap();
        bindings.add_parsed("G", "nav.bottom").unwrap();
        bindings.add_parsed("zo", "tree.expand").unwrap();
        bindings.add_parsed("zc", "tree.collapse").unwrap();
        bindings.add_parsed("za", "tree.toggle").unwrap();
        bindings.add_parsed("<Esc>", "mode.normal").unwrap();
        bindings.add_parsed(":", "mode.command").unwrap();
        KeymapState::new(bindings)
    }

    fn key(c: char) -> Key {
        Key::char(c)
    }

    fn special(code: KeyCode) -> Key {
        Key::plain(code)
    }

    // ── Single-key bindings ───────────────────────────────────────────────────

    #[test]
    fn single_key_fires_action() {
        let mut s = make_state();
        assert_eq!(
            s.process(key('j')),
            Outcome::Action {
                name: "nav.down".into(),
                count: 1
            }
        );
    }

    #[test]
    fn unbound_key_returns_unbound() {
        let mut s = make_state();
        assert_eq!(s.process(key('x')), Outcome::Unbound);
    }

    #[test]
    fn special_key_fires_action() {
        let mut s = make_state();
        assert_eq!(
            s.process(special(KeyCode::Escape)),
            Outcome::Action {
                name: "mode.normal".into(),
                count: 1
            }
        );
    }

    // ── Count prefix ──────────────────────────────────────────────────────────

    #[test]
    fn count_prefix_single_digit() {
        let mut s = make_state();
        assert_eq!(s.process(key('5')), Outcome::Pending);
        assert_eq!(s.current_count(), 5);
        assert_eq!(
            s.process(key('j')),
            Outcome::Action {
                name: "nav.down".into(),
                count: 5
            }
        );
    }

    #[test]
    fn count_prefix_multi_digit() {
        let mut s = make_state();
        assert_eq!(s.process(key('1')), Outcome::Pending);
        assert_eq!(s.process(key('0')), Outcome::Pending);
        assert_eq!(s.current_count(), 10);
        assert_eq!(
            s.process(key('j')),
            Outcome::Action {
                name: "nav.down".into(),
                count: 10
            }
        );
    }

    #[test]
    fn zero_alone_is_not_a_count() {
        // '0' alone with no prior count should be treated as a key, not a count-start.
        let mut bindings = BindingSet::new();
        bindings.add_parsed("0", "nav.top").unwrap();
        let mut s = KeymapState::new(bindings);
        assert_eq!(
            s.process(key('0')),
            Outcome::Action {
                name: "nav.top".into(),
                count: 1
            }
        );
    }

    #[test]
    fn count_resets_after_action() {
        let mut s = make_state();
        s.process(key('3'));
        s.process(key('j'));
        // Next key should have count=1 again.
        assert_eq!(
            s.process(key('j')),
            Outcome::Action {
                name: "nav.down".into(),
                count: 1
            }
        );
    }

    // ── Multi-key sequences ───────────────────────────────────────────────────

    #[test]
    fn multi_key_first_key_is_pending() {
        let mut s = make_state();
        assert_eq!(s.process(key('g')), Outcome::Pending);
        assert_eq!(s.pending_keys(), &[Key::char('g')]);
    }

    #[test]
    fn multi_key_sequence_fires_on_completion() {
        let mut s = make_state();
        s.process(key('g'));
        assert_eq!(
            s.process(key('g')),
            Outcome::Action {
                name: "nav.top".into(),
                count: 1
            }
        );
        // State is cleared after action.
        assert!(s.pending_keys().is_empty());
    }

    #[test]
    fn multi_key_wrong_continuation_retries_last_key() {
        let mut s = make_state();
        // 'g' starts a sequence; 'j' does not continue it → retry 'j' alone.
        s.process(key('g'));
        assert_eq!(
            s.process(key('j')),
            Outcome::Action {
                name: "nav.down".into(),
                count: 1
            }
        );
    }

    #[test]
    fn multi_key_wrong_continuation_unbound_if_retry_also_fails() {
        let mut s = make_state();
        // 'g' starts a sequence; 'x' does not continue it and is also unbound alone.
        s.process(key('g'));
        assert_eq!(s.process(key('x')), Outcome::Unbound);
    }

    #[test]
    fn zo_sequence() {
        let mut s = make_state();
        assert_eq!(s.process(key('z')), Outcome::Pending);
        assert_eq!(
            s.process(key('o')),
            Outcome::Action {
                name: "tree.expand".into(),
                count: 1
            }
        );
    }

    #[test]
    fn count_with_multi_key_sequence() {
        let mut s = make_state();
        s.process(key('3')); // count = 3
        s.process(key('z')); // pending = [z]
        assert_eq!(
            s.process(key('o')),
            Outcome::Action {
                name: "tree.expand".into(),
                count: 3
            }
        );
    }

    // ── Reset ─────────────────────────────────────────────────────────────────

    #[test]
    fn reset_clears_pending_and_count() {
        let mut s = make_state();
        s.process(key('5'));
        s.process(key('g'));
        s.reset();
        assert_eq!(s.current_count(), 0);
        assert!(s.pending_keys().is_empty());
    }

    #[test]
    fn after_reset_processes_normally() {
        let mut s = make_state();
        s.process(key('5'));
        s.process(key('g'));
        s.reset();
        assert_eq!(
            s.process(key('j')),
            Outcome::Action {
                name: "nav.down".into(),
                count: 1
            }
        );
    }
}
