//! Binding set: maps key sequences to action names.

use crate::key::{Key, ParseError};

/// A named binding: a key sequence that triggers an action.
#[derive(Clone, Debug)]
pub struct Binding {
    /// The ordered sequence of keys that must be pressed.
    pub keys: Vec<Key>,
    /// The action name emitted when the sequence completes.
    pub action: String,
}

/// Result of looking up a (partial) key sequence in a [`BindingSet`].
#[derive(Debug, PartialEq, Eq)]
pub enum LookupResult {
    /// The sequence is an exact match for a binding.
    Exact(String),
    /// The sequence is a valid prefix of one or more bindings; keep waiting.
    Prefix,
    /// The sequence matches nothing.
    NoMatch,
}

/// A collection of key→action bindings.
///
/// Internally stored as a flat `Vec`; acceptable because the number of
/// bindings is small (typically < 100) and sequences are short (≤ 4 keys).
#[derive(Default, Clone, Debug)]
pub struct BindingSet {
    bindings: Vec<Binding>,
}

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

    /// Add a binding from pre-parsed keys.
    pub fn add(&mut self, keys: Vec<Key>, action: impl Into<String>) {
        self.bindings.push(Binding {
            keys,
            action: action.into(),
        });
    }

    /// Add a binding by parsing the key sequence string.
    ///
    /// Returns the `ParseError` if the string is not valid.
    pub fn add_parsed(
        &mut self,
        key_sequence: &str,
        action: impl Into<String>,
    ) -> Result<(), ParseError> {
        let keys = crate::key::parse_sequence(key_sequence)?;
        self.add(keys, action);
        Ok(())
    }

    /// Look up a (possibly partial) key sequence.
    ///
    /// If a sequence is both an exact match *and* a prefix of longer bindings,
    /// the exact match takes priority (no ambiguity).
    pub fn lookup(&self, keys: &[Key]) -> LookupResult {
        let mut found_prefix = false;

        for binding in &self.bindings {
            if binding.keys == keys {
                return LookupResult::Exact(binding.action.clone());
            }
            if binding.keys.starts_with(keys) && binding.keys.len() > keys.len() {
                found_prefix = true;
            }
        }

        if found_prefix {
            LookupResult::Prefix
        } else {
            LookupResult::NoMatch
        }
    }

    /// Apply user-defined overrides on top of this binding set.
    ///
    /// For each `(action, key_sequence)` pair in `overrides`:
    /// - All existing bindings for that action are removed.
    /// - A new binding from the parsed sequence is added.
    ///
    /// Unknown action names are added as new bindings; parse errors are skipped.
    pub fn apply_overrides<'a>(&mut self, overrides: impl IntoIterator<Item = (&'a str, &'a str)>) {
        for (action, key_seq) in overrides {
            // Remove existing bindings for this action.
            self.bindings.retain(|b| b.action != action);
            // Add the new binding (skip on parse error).
            if let Ok(keys) = crate::key::parse_sequence(key_seq) {
                self.add(keys, action);
            }
        }
    }

    /// Return all bindings for inspection.
    pub fn bindings(&self) -> &[Binding] {
        &self.bindings
    }
}

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

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

    fn set_with_defaults() -> BindingSet {
        let mut s = BindingSet::new();
        s.add_parsed("j", "nav.down").unwrap();
        s.add_parsed("k", "nav.up").unwrap();
        s.add_parsed("<Down>", "nav.down").unwrap();
        s.add_parsed("gg", "nav.top").unwrap();
        s.add_parsed("G", "nav.bottom").unwrap();
        s.add_parsed("zo", "tree.expand").unwrap();
        s
    }

    #[test]
    fn exact_single_key() {
        let s = set_with_defaults();
        let keys = vec![Key::char('j')];
        assert_eq!(s.lookup(&keys), LookupResult::Exact("nav.down".into()));
    }

    #[test]
    fn exact_multi_key() {
        let s = set_with_defaults();
        let keys = vec![Key::char('g'), Key::char('g')];
        assert_eq!(s.lookup(&keys), LookupResult::Exact("nav.top".into()));
    }

    #[test]
    fn prefix_of_multi_key_binding() {
        let s = set_with_defaults();
        let keys = vec![Key::char('g')];
        assert_eq!(s.lookup(&keys), LookupResult::Prefix);
    }

    #[test]
    fn no_match() {
        let s = set_with_defaults();
        let keys = vec![Key::char('x')];
        assert_eq!(s.lookup(&keys), LookupResult::NoMatch);
    }

    #[test]
    fn no_match_wrong_continuation() {
        let s = set_with_defaults();
        // "gx" should not match anything.
        let keys = vec![Key::char('g'), Key::char('x')];
        assert_eq!(s.lookup(&keys), LookupResult::NoMatch);
    }

    #[test]
    fn apply_overrides_replaces_action_binding() {
        let mut s = set_with_defaults();
        // Replace nav.down from "j" to "n".
        s.apply_overrides([("nav.down", "n")]);

        // Old "j" binding is gone.
        assert_eq!(s.lookup(&[Key::char('j')]), LookupResult::NoMatch);
        // New "n" binding is present.
        assert_eq!(
            s.lookup(&[Key::char('n')]),
            LookupResult::Exact("nav.down".into())
        );
    }

    #[test]
    fn apply_overrides_removes_all_bindings_for_action() {
        let mut s = set_with_defaults();
        // nav.down is bound to both "j" and "<Down>".
        s.apply_overrides([("nav.down", "n")]);

        // Both old bindings should be gone.
        use crate::key::{Key as K, KeyCode};
        assert_eq!(
            s.lookup(&[K::plain(KeyCode::ArrowDown)]),
            LookupResult::NoMatch
        );
    }

    #[test]
    fn apply_overrides_bad_sequence_is_skipped() {
        let mut s = set_with_defaults();
        // "<Bogus>" is not a valid key sequence — the override should be silently skipped.
        s.apply_overrides([("nav.down", "<Bogus>")]);
        // Original binding is still gone (we removed it before failing to parse).
        // This is an acceptable edge case — the user has a bad config.
    }
}
