use gpui::{App, KeyBinding};
use serde::{Deserialize, Serialize};

mod profile;

pub(crate) use profile::{
    ELYKEYS_FILE_EXTENSION, ShortcutProfile, parse_shortcut_profile_json,
    shortcut_rebinding_key_bindings,
};

use crate::{
    CloseCurrentTab, FocusAddressBar, FocusCommandMode, OpenDownloads, OpenHistory, OpenNewTab,
    OpenPrivateWindow, OpenSettings, OpenTaskManager, Quit, ResetZoom, RestoreClosedTab,
    SelectNextSpace, SelectNextTab, SelectPreviousSpace, SelectPreviousTab, SplitRight,
    ToggleFavoriteTab, ToggleSidebar, ZoomIn, ZoomOut,
};

#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum ShortcutPlatform {
    Macos,
    WindowsLinux,
}

impl ShortcutPlatform {
    pub(crate) fn label(self) -> &'static str {
        match self {
            Self::Macos => "macOS",
            Self::WindowsLinux => "Windows/Linux",
        }
    }
}

#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum ShortcutAction {
    FocusAddressBar,
    FocusCommandMode,
    OpenNewTab,
    OpenPrivateWindow,
    CloseCurrentTab,
    RestoreClosedTab,
    SelectNextSpace,
    SelectPreviousSpace,
    SelectNextTab,
    SelectPreviousTab,
    SplitRight,
    ToggleSidebar,
    ToggleFavoriteTab,
    ZoomIn,
    ZoomOut,
    ResetZoom,
    OpenDownloads,
    OpenHistory,
    OpenSettings,
    OpenTaskManager,
    Quit,
}

impl ShortcutAction {
    pub(crate) fn label(self) -> &'static str {
        match self {
            Self::FocusAddressBar => "Command Bar",
            Self::FocusCommandMode => "Command Mode",
            Self::OpenNewTab => "New Tab",
            Self::OpenPrivateWindow => "New Private Window",
            Self::CloseCurrentTab => "Close Tab",
            Self::RestoreClosedTab => "Restore Closed Tab",
            Self::SelectNextSpace => "Next Space",
            Self::SelectPreviousSpace => "Previous Space",
            Self::SelectNextTab => "Next Tab",
            Self::SelectPreviousTab => "Previous Tab",
            Self::SplitRight => "Split Right",
            Self::ToggleSidebar => "Toggle Sidebar",
            Self::ToggleFavoriteTab => "Toggle Favorite",
            Self::ZoomIn => "Zoom In",
            Self::ZoomOut => "Zoom Out",
            Self::ResetZoom => "Reset Zoom",
            Self::OpenDownloads => "Open Downloads",
            Self::OpenHistory => "Open History",
            Self::OpenSettings => "Open Settings",
            Self::OpenTaskManager => "Task Manager",
            Self::Quit => "Quit ELY Browser",
        }
    }

    pub(crate) fn category(self) -> &'static str {
        match self {
            Self::FocusAddressBar | Self::FocusCommandMode => "Command",
            Self::OpenNewTab
            | Self::OpenPrivateWindow
            | Self::CloseCurrentTab
            | Self::RestoreClosedTab
            | Self::SelectNextSpace
            | Self::SelectPreviousSpace
            | Self::SelectNextTab
            | Self::SelectPreviousTab
            | Self::SplitRight
            | Self::ToggleSidebar
            | Self::ToggleFavoriteTab
            | Self::ZoomIn
            | Self::ZoomOut
            | Self::ResetZoom => "Tabs",
            Self::OpenDownloads | Self::OpenHistory => "Library",
            Self::OpenSettings | Self::OpenTaskManager => "System",
            Self::Quit => "Application",
        }
    }

    pub(crate) fn command(self) -> Option<&'static str> {
        match self {
            Self::FocusAddressBar => None,
            Self::FocusCommandMode => None,
            Self::OpenNewTab => Some(">new-tab"),
            Self::OpenPrivateWindow => None,
            Self::CloseCurrentTab => Some(">close-tab"),
            Self::RestoreClosedTab => Some(">restore-tab"),
            Self::SelectNextSpace => None,
            Self::SelectPreviousSpace => None,
            Self::SelectNextTab => None,
            Self::SelectPreviousTab => None,
            Self::SplitRight => Some(">split-right"),
            Self::ToggleSidebar => None,
            Self::ToggleFavoriteTab => Some(">favorite"),
            Self::ZoomIn => Some(">zoom-in"),
            Self::ZoomOut => Some(">zoom-out"),
            Self::ResetZoom => Some(">reset-zoom"),
            Self::OpenDownloads => Some(">open-downloads"),
            Self::OpenHistory => Some(">open-history"),
            Self::OpenSettings => Some(">open-settings"),
            Self::OpenTaskManager => Some(">open-task-manager"),
            Self::Quit => None,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct ShortcutBinding {
    action: ShortcutAction,
    platform: ShortcutPlatform,
    keystroke: &'static str,
}

impl ShortcutBinding {
    pub(crate) fn action(self) -> ShortcutAction {
        self.action
    }

    pub(crate) fn platform(self) -> ShortcutPlatform {
        self.platform
    }

    pub(crate) fn keystroke(self) -> &'static str {
        self.keystroke
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ShortcutConflict {
    pub(crate) platform: ShortcutPlatform,
    pub(crate) keystroke: String,
    pub(crate) actions: Vec<ShortcutAction>,
}

pub(crate) const SHORTCUT_ACTIONS: &[ShortcutAction] = &[
    ShortcutAction::FocusAddressBar,
    ShortcutAction::FocusCommandMode,
    ShortcutAction::OpenNewTab,
    ShortcutAction::OpenPrivateWindow,
    ShortcutAction::CloseCurrentTab,
    ShortcutAction::RestoreClosedTab,
    ShortcutAction::SelectNextSpace,
    ShortcutAction::SelectPreviousSpace,
    ShortcutAction::SelectNextTab,
    ShortcutAction::SelectPreviousTab,
    ShortcutAction::SplitRight,
    ShortcutAction::ToggleSidebar,
    ShortcutAction::ToggleFavoriteTab,
    ShortcutAction::ZoomIn,
    ShortcutAction::ZoomOut,
    ShortcutAction::ResetZoom,
    ShortcutAction::OpenDownloads,
    ShortcutAction::OpenHistory,
    ShortcutAction::OpenSettings,
    ShortcutAction::OpenTaskManager,
    ShortcutAction::Quit,
];

pub(crate) const SHORTCUT_BINDINGS: &[ShortcutBinding] = &[
    shortcut(ShortcutAction::OpenNewTab, ShortcutPlatform::Macos, "cmd-t"),
    shortcut(ShortcutAction::OpenNewTab, ShortcutPlatform::WindowsLinux, "ctrl-t"),
    shortcut(ShortcutAction::OpenPrivateWindow, ShortcutPlatform::Macos, "cmd-shift-n"),
    shortcut(ShortcutAction::OpenPrivateWindow, ShortcutPlatform::WindowsLinux, "ctrl-shift-n"),
    shortcut(ShortcutAction::SplitRight, ShortcutPlatform::Macos, "cmd-\\"),
    shortcut(ShortcutAction::SplitRight, ShortcutPlatform::WindowsLinux, "ctrl-\\"),
    shortcut(ShortcutAction::ToggleSidebar, ShortcutPlatform::Macos, "cmd-b"),
    shortcut(ShortcutAction::ToggleSidebar, ShortcutPlatform::WindowsLinux, "ctrl-b"),
    shortcut(ShortcutAction::OpenDownloads, ShortcutPlatform::Macos, "cmd-shift-j"),
    shortcut(ShortcutAction::OpenDownloads, ShortcutPlatform::WindowsLinux, "ctrl-shift-j"),
    shortcut(ShortcutAction::OpenHistory, ShortcutPlatform::Macos, "cmd-y"),
    shortcut(ShortcutAction::OpenHistory, ShortcutPlatform::WindowsLinux, "ctrl-h"),
    shortcut(ShortcutAction::OpenTaskManager, ShortcutPlatform::Macos, "cmd-escape"),
    shortcut(ShortcutAction::OpenTaskManager, ShortcutPlatform::WindowsLinux, "shift-escape"),
    shortcut(ShortcutAction::OpenSettings, ShortcutPlatform::Macos, "cmd-,"),
    shortcut(ShortcutAction::OpenSettings, ShortcutPlatform::WindowsLinux, "ctrl-,"),
    shortcut(ShortcutAction::FocusAddressBar, ShortcutPlatform::Macos, "cmd-l"),
    shortcut(ShortcutAction::FocusAddressBar, ShortcutPlatform::Macos, "cmd-k"),
    shortcut(ShortcutAction::FocusAddressBar, ShortcutPlatform::WindowsLinux, "ctrl-l"),
    shortcut(ShortcutAction::FocusAddressBar, ShortcutPlatform::WindowsLinux, "ctrl-k"),
    shortcut(ShortcutAction::CloseCurrentTab, ShortcutPlatform::Macos, "cmd-w"),
    shortcut(ShortcutAction::CloseCurrentTab, ShortcutPlatform::WindowsLinux, "ctrl-w"),
    shortcut(ShortcutAction::RestoreClosedTab, ShortcutPlatform::Macos, "cmd-shift-t"),
    shortcut(ShortcutAction::RestoreClosedTab, ShortcutPlatform::WindowsLinux, "ctrl-shift-t"),
    shortcut(ShortcutAction::ToggleFavoriteTab, ShortcutPlatform::Macos, "cmd-shift-f"),
    shortcut(ShortcutAction::ToggleFavoriteTab, ShortcutPlatform::WindowsLinux, "ctrl-shift-f"),
    shortcut(ShortcutAction::ZoomIn, ShortcutPlatform::Macos, "cmd-="),
    shortcut(ShortcutAction::ZoomIn, ShortcutPlatform::WindowsLinux, "ctrl-="),
    shortcut(ShortcutAction::ZoomOut, ShortcutPlatform::Macos, "cmd--"),
    shortcut(ShortcutAction::ZoomOut, ShortcutPlatform::WindowsLinux, "ctrl--"),
    shortcut(ShortcutAction::ResetZoom, ShortcutPlatform::Macos, "cmd-0"),
    shortcut(ShortcutAction::ResetZoom, ShortcutPlatform::WindowsLinux, "ctrl-0"),
    shortcut(ShortcutAction::FocusCommandMode, ShortcutPlatform::Macos, "cmd-shift-p"),
    shortcut(ShortcutAction::FocusCommandMode, ShortcutPlatform::WindowsLinux, "ctrl-shift-p"),
    shortcut(ShortcutAction::SelectNextSpace, ShortcutPlatform::Macos, "cmd-alt-right"),
    shortcut(ShortcutAction::SelectNextSpace, ShortcutPlatform::WindowsLinux, "ctrl-alt-right"),
    shortcut(ShortcutAction::SelectPreviousSpace, ShortcutPlatform::Macos, "cmd-alt-left"),
    shortcut(ShortcutAction::SelectPreviousSpace, ShortcutPlatform::WindowsLinux, "ctrl-alt-left"),
    shortcut(ShortcutAction::SelectNextTab, ShortcutPlatform::Macos, "cmd-shift-]"),
    shortcut(ShortcutAction::SelectNextTab, ShortcutPlatform::Macos, "cmd-alt-down"),
    shortcut(ShortcutAction::SelectNextTab, ShortcutPlatform::WindowsLinux, "ctrl-tab"),
    shortcut(ShortcutAction::SelectNextTab, ShortcutPlatform::WindowsLinux, "ctrl-alt-down"),
    shortcut(ShortcutAction::SelectPreviousTab, ShortcutPlatform::Macos, "cmd-shift-["),
    shortcut(ShortcutAction::SelectPreviousTab, ShortcutPlatform::Macos, "cmd-alt-up"),
    shortcut(ShortcutAction::SelectPreviousTab, ShortcutPlatform::WindowsLinux, "ctrl-shift-tab"),
    shortcut(ShortcutAction::SelectPreviousTab, ShortcutPlatform::WindowsLinux, "ctrl-alt-up"),
    shortcut(ShortcutAction::Quit, ShortcutPlatform::Macos, "cmd-q"),
];

const fn shortcut(
    action: ShortcutAction,
    platform: ShortcutPlatform,
    keystroke: &'static str,
) -> ShortcutBinding {
    ShortcutBinding { action, platform, keystroke }
}

pub(crate) fn bind_shortcuts(cx: &mut App) {
    cx.bind_keys(SHORTCUT_BINDINGS.iter().map(|binding| binding.key_binding()));
}

impl ShortcutBinding {
    fn key_binding(self) -> KeyBinding {
        key_binding_for_action(self.action, self.keystroke)
    }
}

pub(crate) fn key_binding_for_action(action: ShortcutAction, keystroke: &str) -> KeyBinding {
    match action {
        ShortcutAction::CloseCurrentTab => KeyBinding::new(keystroke, CloseCurrentTab, None),
        ShortcutAction::FocusAddressBar => KeyBinding::new(keystroke, FocusAddressBar, None),
        ShortcutAction::FocusCommandMode => KeyBinding::new(keystroke, FocusCommandMode, None),
        ShortcutAction::OpenDownloads => KeyBinding::new(keystroke, OpenDownloads, None),
        ShortcutAction::OpenHistory => KeyBinding::new(keystroke, OpenHistory, None),
        ShortcutAction::OpenNewTab => KeyBinding::new(keystroke, OpenNewTab, None),
        ShortcutAction::OpenPrivateWindow => KeyBinding::new(keystroke, OpenPrivateWindow, None),
        ShortcutAction::OpenSettings => KeyBinding::new(keystroke, OpenSettings, None),
        ShortcutAction::OpenTaskManager => KeyBinding::new(keystroke, OpenTaskManager, None),
        ShortcutAction::Quit => KeyBinding::new(keystroke, Quit, None),
        ShortcutAction::ResetZoom => KeyBinding::new(keystroke, ResetZoom, None),
        ShortcutAction::RestoreClosedTab => KeyBinding::new(keystroke, RestoreClosedTab, None),
        ShortcutAction::SelectNextSpace => KeyBinding::new(keystroke, SelectNextSpace, None),
        ShortcutAction::SelectNextTab => KeyBinding::new(keystroke, SelectNextTab, None),
        ShortcutAction::SelectPreviousSpace => {
            KeyBinding::new(keystroke, SelectPreviousSpace, None)
        }
        ShortcutAction::SelectPreviousTab => KeyBinding::new(keystroke, SelectPreviousTab, None),
        ShortcutAction::SplitRight => KeyBinding::new(keystroke, SplitRight, None),
        ShortcutAction::ToggleFavoriteTab => KeyBinding::new(keystroke, ToggleFavoriteTab, None),
        ShortcutAction::ToggleSidebar => KeyBinding::new(keystroke, ToggleSidebar, None),
        ShortcutAction::ZoomIn => KeyBinding::new(keystroke, ZoomIn, None),
        ShortcutAction::ZoomOut => KeyBinding::new(keystroke, ZoomOut, None),
    }
}

pub(crate) fn display_keystroke(keystroke: &str, platform: ShortcutPlatform) -> String {
    keystroke
        .split('-')
        .map(|part| display_key_part(part, platform))
        .collect::<Vec<_>>()
        .join(" + ")
}

fn display_key_part(part: &str, platform: ShortcutPlatform) -> String {
    match part {
        "cmd" => "Cmd".to_string(),
        "ctrl" => "Ctrl".to_string(),
        "alt" if platform == ShortcutPlatform::Macos => "Option".to_string(),
        "alt" => "Alt".to_string(),
        "shift" => "Shift".to_string(),
        "escape" => "Esc".to_string(),
        value => value.to_ascii_uppercase(),
    }
}

#[cfg(test)]
mod tests {
    use super::{
        SHORTCUT_ACTIONS, SHORTCUT_BINDINGS, ShortcutAction, ShortcutPlatform, ShortcutProfile,
    };

    #[test]
    fn registered_shortcuts_have_no_conflicts() {
        assert_eq!(ShortcutProfile::default_profile().conflicts(), Vec::new());
    }

    #[test]
    fn open_settings_shortcut_has_platform_bindings() {
        let bindings = platform_labels(ShortcutAction::OpenSettings);

        assert_eq!(bindings, vec!["Cmd + ,".to_string(), "Ctrl + ,".to_string()]);
    }

    #[test]
    fn private_window_shortcut_has_platform_bindings() {
        let bindings = platform_labels(ShortcutAction::OpenPrivateWindow);

        assert_eq!(bindings, vec!["Cmd + Shift + N".to_string(), "Ctrl + Shift + N".to_string()]);
    }

    #[test]
    fn toggle_sidebar_shortcut_has_platform_bindings() {
        let bindings = platform_labels(ShortcutAction::ToggleSidebar);

        assert_eq!(bindings, vec!["Cmd + B".to_string(), "Ctrl + B".to_string()]);
    }

    #[test]
    fn space_switch_shortcuts_have_platform_bindings() {
        let next_bindings = platform_labels(ShortcutAction::SelectNextSpace);
        let previous_bindings = platform_labels(ShortcutAction::SelectPreviousSpace);

        assert_eq!(
            next_bindings,
            vec!["Cmd + Option + RIGHT".to_string(), "Ctrl + Alt + RIGHT".to_string()]
        );
        assert_eq!(
            previous_bindings,
            vec!["Cmd + Option + LEFT".to_string(), "Ctrl + Alt + LEFT".to_string()]
        );
    }

    #[test]
    fn every_declared_action_has_a_binding() {
        for action in SHORTCUT_ACTIONS {
            assert!(SHORTCUT_BINDINGS.iter().any(|binding| binding.action() == *action));
        }
    }

    fn platform_labels(action: ShortcutAction) -> Vec<String> {
        let profile = ShortcutProfile::default_profile();
        [ShortcutPlatform::Macos, ShortcutPlatform::WindowsLinux]
            .into_iter()
            .map(|platform| profile.display_bindings_for_action(action, platform))
            .collect()
    }
}
