use crate::prelude::*;

use input::{Key, Modifiers};

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct KeyEvent {
    pub modifiers: Modifiers,
    pub key: Key,
}

impl TryFrom<crossterm::event::KeyEvent> for KeyEvent {
    type Error = ();

    fn try_from(event: crossterm::event::KeyEvent) -> Result<Self, Self::Error> {
        Ok(KeyEvent {
            modifiers: event.modifiers.into(),
            key: Key::try_from(event.code)?,
        })
    }
}

impl std::fmt::Display for KeyEvent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.modifiers.is_empty() {
            write!(f, "{}", self.key)
        } else {
            write!(f, "{}+{}", self.modifiers, self.key)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crossterm::event::{KeyCode, KeyModifiers};

    fn ct_key(
        code: KeyCode,
        mods: KeyModifiers,
    ) -> crossterm::event::KeyEvent {
        crossterm::event::KeyEvent::new(code, mods)
    }

    // --- TryFrom ---
    #[test]
    fn try_from_char_no_mods() {
        let ev = KeyEvent::try_from(ct_key(KeyCode::Char('a'), KeyModifiers::empty()))
            .unwrap();
        assert_eq!(ev.key, Key::Char('a'));
        assert!(ev.modifiers.is_empty());
    }

    #[test]
    fn try_from_char_with_ctrl() {
        let ev =
            KeyEvent::try_from(ct_key(KeyCode::Char('s'), KeyModifiers::CONTROL)).unwrap();
        assert_eq!(ev.key, Key::Char('s'));
        assert!(ev.modifiers.contains(Modifiers::CTRL));
    }

    #[test]
    fn try_from_enter_with_shift() {
        let ev = KeyEvent::try_from(ct_key(KeyCode::Enter, KeyModifiers::SHIFT)).unwrap();
        assert_eq!(ev.key, Key::Enter);
        assert!(ev.modifiers.contains(Modifiers::SHIFT));
    }

    #[test]
    fn try_from_unknown_key_is_err() {
        assert!(
            KeyEvent::try_from(ct_key(KeyCode::Null, KeyModifiers::empty())).is_err()
        );
    }

    // --- Display ---
    #[test]
    fn display_no_modifiers() {
        let ev = KeyEvent {
            modifiers: Modifiers::empty(),
            key: Key::Enter,
        };
        assert_eq!(ev.to_string(), "Enter");
    }

    #[test]
    fn display_ctrl_modifier() {
        let ev = KeyEvent {
            modifiers: Modifiers::CTRL,
            key: Key::Char('s'),
        };
        assert_eq!(ev.to_string(), "Ctrl+s");
    }

    #[test]
    fn display_ctrl_shift_modifier() {
        let ev = KeyEvent {
            modifiers: Modifiers::CTRL | Modifiers::SHIFT,
            key: Key::ArrowUp,
        };
        assert_eq!(ev.to_string(), "Ctrl+Shift+ArrowUp");
    }

    #[test]
    fn display_alt_modifier() {
        let ev = KeyEvent {
            modifiers: Modifiers::ALT,
            key: Key::F(4),
        };
        assert_eq!(ev.to_string(), "Alt+F4");
    }

    // --- Derived trait smoke tests ---
    #[test]
    fn key_event_clone_eq() {
        let ev = KeyEvent {
            modifiers: Modifiers::CTRL,
            key: Key::Char('c'),
        };
        assert_eq!(ev.clone(), ev);
    }

    #[test]
    fn key_event_hash() {
        use std::collections::HashSet;
        let ev = KeyEvent {
            modifiers: Modifiers::CTRL,
            key: Key::Char('c'),
        };
        let mut s = HashSet::new();
        s.insert(ev);
        assert!(s.contains(&ev));
    }
}
