use muda::accelerator::{Accelerator, CMD_OR_CTRL, Code, Modifiers};
use muda::{CheckMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu};

use crate::app::DetailTab;
use crate::native_edit::EditCommand;

const PREFERENCES: &str = "preferences";
const MODEL_MANAGER: &str = "model-manager";
const NEW_CHAT: &str = "new-chat";
const EXPORT_CHAT: &str = "export-chat";
const TOGGLE_SIDEBAR: &str = "toggle-sidebar";
const SHOW_CHAT: &str = "show-chat";
const SHOW_A2UI: &str = "show-a2ui";
const SHOW_GIT: &str = "show-git";
const SHOW_STATS: &str = "show-stats";
const HELP: &str = "help";
const QUIT: &str = "quit";
const UNDO: &str = "undo";
const REDO: &str = "redo";
const CUT: &str = "cut";
const COPY: &str = "copy";
const PASTE: &str = "paste";
const SELECT_ALL: &str = "select-all";

pub(crate) struct NativeMenu {
    _menu: Menu,
    new_chat: MenuItem,
    export_chat: MenuItem,
    sidebar: CheckMenuItem,
    chat: CheckMenuItem,
    a2ui: CheckMenuItem,
    git: CheckMenuItem,
    stats: CheckMenuItem,
    undo: MenuItem,
    redo: MenuItem,
    cut: MenuItem,
    copy: MenuItem,
    paste: MenuItem,
    select_all: MenuItem,
}

#[derive(Clone, Copy, Debug)]
pub(crate) enum NativeMenuEvent {
    Preferences,
    ModelManager,
    NewChat,
    ExportChat,
    ToggleSidebar,
    ShowChat,
    ShowA2ui,
    ShowGit,
    ShowStats,
    Help,
    Quit,
    Edit(EditCommand),
}

pub(crate) fn install() -> Result<NativeMenu, String> {
    let menu = Menu::new();
    let application = Submenu::new("DS4Server", true);
    let file = Submenu::new("File", true);
    let edit = Submenu::new("Edit", true);
    let view = Submenu::new("View", true);
    let window = Submenu::new("Window", true);
    let help = Submenu::new("Help", true);
    let preferences = MenuItem::with_id(
        PREFERENCES,
        "Preferences…",
        true,
        Some(Accelerator::new(Some(Modifiers::SUPER), Code::Comma)),
    );
    let model_manager = MenuItem::with_id(
        MODEL_MANAGER,
        "Model Manager…",
        true,
        Some(Accelerator::new(
            Some(Modifiers::SUPER | Modifiers::SHIFT),
            Code::KeyM,
        )),
    );
    let new_chat = MenuItem::with_id(
        NEW_CHAT,
        "New Chat",
        false,
        Some(Accelerator::new(Some(Modifiers::SUPER), Code::KeyN)),
    );
    let export_chat = MenuItem::with_id(
        EXPORT_CHAT,
        "Export Chat as Markdown…",
        false,
        Some(Accelerator::new(
            Some(Modifiers::SUPER | Modifiers::SHIFT),
            Code::KeyS,
        )),
    );
    let sidebar = CheckMenuItem::with_id(
        TOGGLE_SIDEBAR,
        "Show Sidebar",
        true,
        true,
        Some(Accelerator::new(Some(Modifiers::SUPER), Code::KeyB)),
    );
    let chat = view_item(SHOW_CHAT, "Chat", Code::Digit1, true);
    let a2ui = view_item(SHOW_A2UI, "A2UI", Code::Digit2, false);
    let git = view_item(SHOW_GIT, "Git", Code::Digit3, false);
    let stats = view_item(SHOW_STATS, "Stats", Code::Digit4, false);
    let open_help = MenuItem::with_id(
        HELP,
        "DS4Server Help",
        true,
        Some(Accelerator::new(
            Some(Modifiers::SUPER | Modifiers::SHIFT),
            Code::Slash,
        )),
    );
    let undo = edit_item(UNDO, "Undo", Code::KeyZ, None);
    let redo = edit_item(REDO, "Redo", Code::KeyZ, Some(Modifiers::SHIFT));
    let cut = edit_item(CUT, "Cut", Code::KeyX, None);
    let copy = edit_item(COPY, "Copy", Code::KeyC, None);
    let paste = edit_item(PASTE, "Paste", Code::KeyV, None);
    let select_all = edit_item(SELECT_ALL, "Select All", Code::KeyA, None);
    let quit = MenuItem::with_id(
        QUIT,
        "Quit DS4Server",
        true,
        Some(Accelerator::new(Some(Modifiers::SUPER), Code::KeyQ)),
    );

    application
        .append_items(&[
            &PredefinedMenuItem::about(None, None),
            &PredefinedMenuItem::separator(),
            &preferences,
            &PredefinedMenuItem::separator(),
            &PredefinedMenuItem::services(None),
            &PredefinedMenuItem::separator(),
            &PredefinedMenuItem::hide(None),
            &PredefinedMenuItem::hide_others(None),
            &PredefinedMenuItem::show_all(None),
            &PredefinedMenuItem::separator(),
            &quit,
        ])
        .map_err(|error| error.to_string())?;
    file.append_items(&[
        &new_chat,
        &export_chat,
        &PredefinedMenuItem::separator(),
        &PredefinedMenuItem::close_window(None),
    ])
    .map_err(|error| error.to_string())?;
    edit.append_items(&[
        &undo,
        &redo,
        &PredefinedMenuItem::separator(),
        &cut,
        &copy,
        &paste,
        &select_all,
    ])
    .map_err(|error| error.to_string())?;
    view.append_items(&[
        &sidebar,
        &PredefinedMenuItem::separator(),
        &chat,
        &a2ui,
        &git,
        &stats,
        &PredefinedMenuItem::separator(),
        &model_manager,
    ])
    .map_err(|error| error.to_string())?;
    window
        .append_items(&[
            &PredefinedMenuItem::minimize(None),
            &PredefinedMenuItem::maximize(None),
            &PredefinedMenuItem::close_window(None),
            &PredefinedMenuItem::bring_all_to_front(None),
        ])
        .map_err(|error| error.to_string())?;
    help.append(&open_help).map_err(|error| error.to_string())?;
    menu.append_items(&[&application, &file, &edit, &view, &window, &help])
        .map_err(|error| error.to_string())?;
    menu.init_for_nsapp();
    window.set_as_windows_menu_for_nsapp();
    Ok(NativeMenu {
        _menu: menu,
        new_chat,
        export_chat,
        sidebar,
        chat,
        a2ui,
        git,
        stats,
        undo,
        redo,
        cut,
        copy,
        paste,
        select_all,
    })
}

impl NativeMenu {
    pub(crate) fn update(
        &self,
        project_active: bool,
        chat_active: bool,
        sidebar_visible: bool,
        detail_tab: DetailTab,
        edit: crate::native_edit::EditAvailability,
    ) {
        self.new_chat.set_enabled(project_active);
        self.export_chat.set_enabled(chat_active);
        self.sidebar.set_checked(sidebar_visible);
        self.chat.set_checked(detail_tab == DetailTab::Chat);
        self.a2ui.set_checked(detail_tab == DetailTab::A2ui);
        self.git.set_checked(detail_tab == DetailTab::Git);
        self.stats.set_checked(detail_tab == DetailTab::Stats);
        self.undo.set_enabled(edit.undo);
        self.redo.set_enabled(edit.redo);
        self.cut.set_enabled(edit.cut);
        self.copy.set_enabled(edit.copy);
        self.paste.set_enabled(edit.paste);
        self.select_all.set_enabled(edit.select_all);
    }
}

pub(crate) fn next_event() -> Option<NativeMenuEvent> {
    while let Ok(event) = MenuEvent::receiver().try_recv() {
        let event = match event.id.0.as_str() {
            PREFERENCES => NativeMenuEvent::Preferences,
            MODEL_MANAGER => NativeMenuEvent::ModelManager,
            NEW_CHAT => NativeMenuEvent::NewChat,
            EXPORT_CHAT => NativeMenuEvent::ExportChat,
            TOGGLE_SIDEBAR => NativeMenuEvent::ToggleSidebar,
            SHOW_CHAT => NativeMenuEvent::ShowChat,
            SHOW_A2UI => NativeMenuEvent::ShowA2ui,
            SHOW_GIT => NativeMenuEvent::ShowGit,
            SHOW_STATS => NativeMenuEvent::ShowStats,
            HELP => NativeMenuEvent::Help,
            QUIT => NativeMenuEvent::Quit,
            UNDO => NativeMenuEvent::Edit(EditCommand::Undo),
            REDO => NativeMenuEvent::Edit(EditCommand::Redo),
            CUT => NativeMenuEvent::Edit(EditCommand::Cut),
            COPY => NativeMenuEvent::Edit(EditCommand::Copy),
            PASTE => NativeMenuEvent::Edit(EditCommand::Paste),
            SELECT_ALL => NativeMenuEvent::Edit(EditCommand::SelectAll),
            _ => continue,
        };
        return Some(event);
    }
    None
}

fn view_item(id: &'static str, label: &'static str, code: Code, checked: bool) -> CheckMenuItem {
    CheckMenuItem::with_id(
        id,
        label,
        true,
        checked,
        Some(Accelerator::new(Some(Modifiers::SUPER), code)),
    )
}

fn edit_item(
    id: &'static str,
    label: &'static str,
    code: Code,
    extra_modifier: Option<Modifiers>,
) -> MenuItem {
    MenuItem::with_id(
        id,
        label,
        true,
        Some(Accelerator::new(
            Some(CMD_OR_CTRL | extra_modifier.unwrap_or_else(Modifiers::empty)),
            code,
        )),
    )
}

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

    #[test]
    fn edit_items_are_custom_menu_events() {
        let paste = edit_item(PASTE, "Paste", Code::KeyV, None);
        assert_eq!(paste.id().0, PASTE);
        assert!(paste.is_enabled());
    }

    #[test]
    fn view_items_keep_the_selected_pane_checked() {
        let chat = view_item(SHOW_CHAT, "Chat", Code::Digit1, true);
        let stats = view_item(SHOW_STATS, "Stats", Code::Digit4, false);
        assert_eq!(chat.id().0, SHOW_CHAT);
        assert!(chat.is_checked());
        assert_eq!(stats.id().0, SHOW_STATS);
        assert!(!stats.is_checked());
    }
}
