//! Key representation and string parsing.
//!
//! Key sequences are written in a vim-inspired notation:
//!
//! | Notation         | Meaning                        |
//! |------------------|-------------------------------|
//! | `j`              | the letter j                   |
//! | `G`              | capital G (no modifier needed) |
//! | `<Enter>` / `<CR>` | Enter / Return               |
//! | `<Esc>`          | Escape                         |
//! | `<Tab>`          | Tab                            |
//! | `<BS>`           | Backspace                      |
//! | `<Del>`          | Delete                         |
//! | `<Space>`        | Space bar                      |
//! | `<Up/Down/Left/Right>` | Arrow keys              |
//! | `<Home>` / `<End>` | Home / End                   |
//! | `<PageUp>` / `<PageDown>` | Page Up / Down        |
//! | `<C-x>`          | Ctrl+x                         |
//! | `<S-Tab>`        | Shift+Tab                      |
//! | `<M-x>` / `<A-x>` | Alt+x                        |
//!
//! Sequences are formed by concatenating specs: `gg`, `zo`, `<C-d>`.

use std::fmt;

/// A single key press, including its modifiers.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Key {
    pub code: KeyCode,
    pub ctrl: bool,
    pub shift: bool,
    pub alt: bool,
    pub meta: bool,
}

impl Key {
    /// Create an unmodified character key.
    pub fn char(c: char) -> Self {
        Key {
            code: KeyCode::Char(c),
            ctrl: false,
            shift: false,
            alt: false,
            meta: false,
        }
    }

    /// Create a key with the given code and no modifiers.
    pub fn plain(code: KeyCode) -> Self {
        Key {
            code,
            ctrl: false,
            shift: false,
            alt: false,
            meta: false,
        }
    }
}

impl fmt::Display for Key {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let needs_brackets = self.ctrl
            || self.shift
            || self.alt
            || self.meta
            || !matches!(self.code, KeyCode::Char(_));

        if needs_brackets {
            write!(f, "<")?;
            if self.ctrl {
                write!(f, "C-")?;
            }
            if self.shift {
                write!(f, "S-")?;
            }
            if self.alt {
                write!(f, "M-")?;
            }
            if self.meta {
                write!(f, "D-")?;
            }
            write!(f, "{}>", self.code)
        } else {
            write!(f, "{}", self.code)
        }
    }
}

/// The logical key code, independent of platform.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum KeyCode {
    /// A Unicode character key (letters, digits, punctuation).
    Char(char),
    Enter,
    Escape,
    Tab,
    Backspace,
    Delete,
    Space,
    ArrowUp,
    ArrowDown,
    ArrowLeft,
    ArrowRight,
    Home,
    End,
    PageUp,
    PageDown,
    F(u8),
}

impl fmt::Display for KeyCode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            KeyCode::Char(c) => write!(f, "{}", c),
            KeyCode::Enter => write!(f, "Enter"),
            KeyCode::Escape => write!(f, "Esc"),
            KeyCode::Tab => write!(f, "Tab"),
            KeyCode::Backspace => write!(f, "BS"),
            KeyCode::Delete => write!(f, "Del"),
            KeyCode::Space => write!(f, "Space"),
            KeyCode::ArrowUp => write!(f, "Up"),
            KeyCode::ArrowDown => write!(f, "Down"),
            KeyCode::ArrowLeft => write!(f, "Left"),
            KeyCode::ArrowRight => write!(f, "Right"),
            KeyCode::Home => write!(f, "Home"),
            KeyCode::End => write!(f, "End"),
            KeyCode::PageUp => write!(f, "PageUp"),
            KeyCode::PageDown => write!(f, "PageDown"),
            KeyCode::F(n) => write!(f, "F{}", n),
        }
    }
}

// ── Parsing ───────────────────────────────────────────────────────────────────

/// Error type for key sequence parsing.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ParseError {
    /// A `<` was not closed with a `>`.
    UnclosedBracket,
    /// A `<...>` block contained an unrecognized key name.
    UnknownKey(String),
    /// An empty `<>` was encountered.
    EmptyBracket,
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ParseError::UnclosedBracket => write!(f, "unclosed '<' in key sequence"),
            ParseError::UnknownKey(k) => write!(f, "unknown key name: '{}'", k),
            ParseError::EmptyBracket => write!(f, "empty '<>' in key sequence"),
        }
    }
}

/// Parse a key sequence string (e.g. `"gg"`, `"<C-d>"`, `"zo"`) into a list of [`Key`]s.
pub fn parse_sequence(s: &str) -> Result<Vec<Key>, ParseError> {
    let mut keys = Vec::new();
    let mut chars = s.chars().peekable();

    while let Some(c) = chars.next() {
        if c == '<' {
            // Consume until the matching '>'.
            let mut spec = String::new();
            loop {
                match chars.next() {
                    Some('>') => break,
                    Some(c) => spec.push(c),
                    None => return Err(ParseError::UnclosedBracket),
                }
            }
            if spec.is_empty() {
                return Err(ParseError::EmptyBracket);
            }
            keys.push(parse_bracket_spec(&spec)?);
        } else {
            keys.push(Key::char(c));
        }
    }

    Ok(keys)
}

/// Parse the interior of a `<...>` bracket, e.g. `"C-d"`, `"S-Tab"`, `"Enter"`.
fn parse_bracket_spec(spec: &str) -> Result<Key, ParseError> {
    let mut ctrl = false;
    let mut shift = false;
    let mut alt = false;
    let mut meta = false;
    let mut rest = spec;

    // Strip modifier prefixes in any order.
    loop {
        if let Some(s) = rest.strip_prefix("C-") {
            ctrl = true;
            rest = s;
        } else if let Some(s) = rest.strip_prefix("S-") {
            shift = true;
            rest = s;
        } else if let Some(s) = rest.strip_prefix("M-").or_else(|| rest.strip_prefix("A-")) {
            alt = true;
            rest = s;
        } else if let Some(s) = rest.strip_prefix("D-") {
            meta = true;
            rest = s;
        } else {
            break;
        }
    }

    let code = match rest {
        "Enter" | "CR" | "Return" => KeyCode::Enter,
        "Esc" | "Escape" => KeyCode::Escape,
        "Tab" => KeyCode::Tab,
        "BS" | "Backspace" => KeyCode::Backspace,
        "Del" | "Delete" => KeyCode::Delete,
        "Space" => KeyCode::Space,
        "Up" => KeyCode::ArrowUp,
        "Down" => KeyCode::ArrowDown,
        "Left" => KeyCode::ArrowLeft,
        "Right" => KeyCode::ArrowRight,
        "Home" => KeyCode::Home,
        "End" => KeyCode::End,
        "PageUp" => KeyCode::PageUp,
        "PageDown" => KeyCode::PageDown,
        s if s.starts_with('F') && s.len() > 1 => {
            let n: u8 = s[1..]
                .parse()
                .map_err(|_| ParseError::UnknownKey(spec.to_owned()))?;
            KeyCode::F(n)
        }
        s if s.chars().count() == 1 => KeyCode::Char(s.chars().next().unwrap()),
        _ => return Err(ParseError::UnknownKey(spec.to_owned())),
    };

    Ok(Key {
        code,
        ctrl,
        shift,
        alt,
        meta,
    })
}

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

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

    fn seq(s: &str) -> Vec<Key> {
        parse_sequence(s).unwrap()
    }

    #[test]
    fn single_char() {
        assert_eq!(seq("j"), vec![Key::char('j')]);
        assert_eq!(seq("G"), vec![Key::char('G')]);
    }

    #[test]
    fn multi_char_sequence() {
        assert_eq!(seq("gg"), vec![Key::char('g'), Key::char('g')]);
        assert_eq!(seq("zo"), vec![Key::char('z'), Key::char('o')]);
    }

    #[test]
    fn special_keys() {
        assert_eq!(seq("<Enter>"), vec![Key::plain(KeyCode::Enter)]);
        assert_eq!(seq("<CR>"), vec![Key::plain(KeyCode::Enter)]);
        assert_eq!(seq("<Esc>"), vec![Key::plain(KeyCode::Escape)]);
        assert_eq!(seq("<Tab>"), vec![Key::plain(KeyCode::Tab)]);
        assert_eq!(seq("<BS>"), vec![Key::plain(KeyCode::Backspace)]);
        assert_eq!(seq("<Del>"), vec![Key::plain(KeyCode::Delete)]);
        assert_eq!(seq("<Space>"), vec![Key::plain(KeyCode::Space)]);
        assert_eq!(seq("<Up>"), vec![Key::plain(KeyCode::ArrowUp)]);
        assert_eq!(seq("<Down>"), vec![Key::plain(KeyCode::ArrowDown)]);
        assert_eq!(seq("<Home>"), vec![Key::plain(KeyCode::Home)]);
        assert_eq!(seq("<End>"), vec![Key::plain(KeyCode::End)]);
        assert_eq!(seq("<PageUp>"), vec![Key::plain(KeyCode::PageUp)]);
        assert_eq!(seq("<PageDown>"), vec![Key::plain(KeyCode::PageDown)]);
    }

    #[test]
    fn ctrl_modifier() {
        let key = &seq("<C-d>")[0];
        assert_eq!(key.code, KeyCode::Char('d'));
        assert!(key.ctrl);
        assert!(!key.shift);
    }

    #[test]
    fn shift_modifier() {
        let key = &seq("<S-Tab>")[0];
        assert_eq!(key.code, KeyCode::Tab);
        assert!(key.shift);
    }

    #[test]
    fn alt_modifier() {
        let key = &seq("<M-j>")[0];
        assert_eq!(key.code, KeyCode::Char('j'));
        assert!(key.alt);
    }

    #[test]
    fn combined_modifiers() {
        let key = &seq("<C-S-Tab>")[0];
        assert_eq!(key.code, KeyCode::Tab);
        assert!(key.ctrl);
        assert!(key.shift);
    }

    #[test]
    fn function_key() {
        assert_eq!(seq("<F1>"), vec![Key::plain(KeyCode::F(1))]);
        assert_eq!(seq("<F12>"), vec![Key::plain(KeyCode::F(12))]);
    }

    #[test]
    fn mixed_sequence() {
        let keys = seq("<C-d>j<Enter>");
        assert_eq!(keys.len(), 3);
        assert_eq!(keys[0].code, KeyCode::Char('d'));
        assert!(keys[0].ctrl);
        assert_eq!(keys[1], Key::char('j'));
        assert_eq!(keys[2].code, KeyCode::Enter);
    }

    #[test]
    fn unclosed_bracket_error() {
        assert_eq!(parse_sequence("<Enter"), Err(ParseError::UnclosedBracket));
    }

    #[test]
    fn unknown_key_error() {
        assert!(matches!(
            parse_sequence("<Foobar>"),
            Err(ParseError::UnknownKey(_))
        ));
    }

    #[test]
    fn empty_bracket_error() {
        assert_eq!(parse_sequence("<>"), Err(ParseError::EmptyBracket));
    }

    #[test]
    fn display_char_key() {
        assert_eq!(Key::char('j').to_string(), "j");
    }

    #[test]
    fn display_ctrl_key() {
        let k = seq("<C-d>")[0].clone();
        assert_eq!(k.to_string(), "<C-d>");
    }

    #[test]
    fn display_shift_tab() {
        let k = seq("<S-Tab>")[0].clone();
        assert_eq!(k.to_string(), "<S-Tab>");
    }

    #[test]
    fn display_special_key() {
        let k = Key::plain(KeyCode::Enter);
        assert_eq!(k.to_string(), "<Enter>");
    }
}
