use core::fmt;

use serde::{Deserialize, Serialize};

#[derive(
    Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct ModifierMask(u8);

impl ModifierMask {
    pub const NONE: Self = Self(0);
    pub const CTRL: Self = Self(1 << 0);
    pub const SHIFT: Self = Self(1 << 1);
    pub const ALT: Self = Self(1 << 2);
    pub const META: Self = Self(1 << 3);

    #[must_use]
    pub const fn contains(self, other: Self) -> bool {
        (self.0 & other.0) == other.0
    }

    #[must_use]
    pub const fn union(self, other: Self) -> Self {
        Self(self.0 | other.0)
    }
}

impl core::ops::BitOr for ModifierMask {
    type Output = Self;
    fn bitor(self, rhs: Self) -> Self {
        self.union(rhs)
    }
}

impl fmt::Display for ModifierMask {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (flag, name) in [
            (Self::CTRL, "Ctrl"),
            (Self::SHIFT, "Shift"),
            (Self::ALT, "Alt"),
            (Self::META, "Meta"),
        ] {
            if self.contains(flag) {
                f.write_str(name)?;
                f.write_str("+")?;
            }
        }
        Ok(())
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum NamedKey {
    Tab,
    Enter,
    Escape,
    Backspace,
    Delete,
    Space,
    ArrowUp,
    ArrowDown,
    ArrowLeft,
    ArrowRight,
    Home,
    End,
    PageUp,
    PageDown,
}

impl NamedKey {
    #[must_use]
    pub const fn label(self) -> &'static str {
        match self {
            Self::Tab => "Tab",
            Self::Enter => "Enter",
            Self::Escape => "Esc",
            Self::Backspace => "Backspace",
            Self::Delete => "Delete",
            Self::Space => "Space",
            Self::ArrowUp => "Up",
            Self::ArrowDown => "Down",
            Self::ArrowLeft => "Left",
            Self::ArrowRight => "Right",
            Self::Home => "Home",
            Self::End => "End",
            Self::PageUp => "PageUp",
            Self::PageDown => "PageDown",
        }
    }
}

impl fmt::Display for NamedKey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.label())
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct KeyChar(char);

impl KeyChar {
    #[must_use]
    pub fn from_char(c: char) -> Self {
        Self(c.to_lowercase().next().unwrap_or(c))
    }

    #[must_use]
    pub const fn get(self) -> char {
        self.0
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum KeyCode {
    Named(NamedKey),
    Char(KeyChar),
}

impl fmt::Display for KeyCode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Named(named) => fmt::Display::fmt(named, f),
            Self::Char(c) => f.write_str(&c.get().to_uppercase().collect::<String>()),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct KeyEvent {
    pub code: KeyCode,
    pub modifiers: ModifierMask,
}

impl KeyEvent {
    #[must_use]
    pub const fn new(code: KeyCode, modifiers: ModifierMask) -> Self {
        Self { code, modifiers }
    }
}

#[cfg(test)]
mod tests {
    use super::{KeyChar, ModifierMask};

    #[test]
    fn modifier_contains_self() {
        let m = ModifierMask::CTRL | ModifierMask::SHIFT;
        assert!(m.contains(ModifierMask::CTRL));
        assert!(m.contains(ModifierMask::SHIFT));
        assert!(!m.contains(ModifierMask::ALT));
    }

    #[test]
    fn key_char_normalizes_to_lower() {
        assert_eq!(KeyChar::from_char('A').get(), 'a');
        assert_eq!(KeyChar::from_char('z').get(), 'z');
        assert_eq!(KeyChar::from_char('É').get(), 'é');
    }
}
