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

use crate::native_edit::EditCommand;

const PREFERENCES: &str = "preferences";
const MODEL_MANAGER: &str = "model-manager";
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,
}

#[derive(Clone, Copy, Debug)]
pub(crate) enum NativeMenuEvent {
    Preferences,
    ModelManager,
    Edit(EditCommand),
}

pub(crate) fn install() -> Result<NativeMenu, String> {
    let menu = Menu::new();
    let application = Submenu::new("DS4Server", true);
    let edit = Submenu::new("Edit", true);
    let window = Submenu::new("Window", 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 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);

    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(),
            &PredefinedMenuItem::quit(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())?;
    window
        .append_items(&[
            &model_manager,
            &PredefinedMenuItem::separator(),
            &PredefinedMenuItem::minimize(None),
            &PredefinedMenuItem::maximize(None),
            &PredefinedMenuItem::close_window(None),
            &PredefinedMenuItem::bring_all_to_front(None),
        ])
        .map_err(|error| error.to_string())?;
    menu.append_items(&[&application, &edit, &window])
        .map_err(|error| error.to_string())?;
    menu.init_for_nsapp();
    window.set_as_windows_menu_for_nsapp();
    Ok(NativeMenu { _menu: menu })
}

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,
            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 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());
    }
}
