use crate::app_config::MAX_RECENT_DECKS;
use crate::resident_tray::{TRAY_MENU_QUIT_ID, TRAY_MENU_RESTORE_ID};
use wxdragon::prelude::*;

const DEFERRED_MENU_REMOVE_CARD_ID: i32 = 32_011;
const DEFERRED_MENU_MAKE_ELIGIBLE_REVERSIBLE_ID: i32 = 32_012;
const DEFERRED_MENU_UNDO_BULK_REVERSAL_ID: i32 = 32_013;
const DEFERRED_MENU_SCRUB_DATABASE_ID: i32 = 32_014;
const DEFERRED_MENU_RECLAIM_DATABASE_SPACE_ID: i32 = 32_015;
const DEFERRED_MENU_DECK_PROPERTIES_ID: i32 = 32_016;
const DEFERRED_MENU_EDIT_DECK_PROPERTIES_ID: i32 = 32_017;
const DEFERRED_MENU_OPEN_DECK_ID: i32 = 32_020;
const DEFERRED_MENU_OPTIONS_ID: i32 = 32_021;
const DEFERRED_MENU_LEARNING_DASHBOARD_ID: i32 = 32_022;
const DEFERRED_MENU_ADD_CARD_ID: i32 = 32_023;
const DEFERRED_MENU_ADD_CARD_BATCH_ID: i32 = 32_024;
const DEFERRED_MENU_EDIT_CARD_ID: i32 = 32_025;
const DEFERRED_MENU_USER_GUIDE_ID: i32 = 32_026;
const DEFERRED_MENU_ABOUT_ID: i32 = 32_027;
const DEFERRED_MENU_REPORT_BUG_ID: i32 = 32_028;
const DEFERRED_MENU_PROJECT_WIKI_ID: i32 = 32_029;
const DEFERRED_MENU_RECENT_DECK_BASE_ID: i32 = 32_040;
const DEFERRED_MENU_RECENT_DECK_LAST_ID: i32 =
    DEFERRED_MENU_RECENT_DECK_BASE_ID + MAX_RECENT_DECKS as i32 - 1;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DeferredMenuCommand {
    OpenDeck,
    Options,
    DeckProperties,
    EditDeckProperties,
    LearningDashboard,
    AddCard,
    AddCardBatch,
    EditCard,
    UserGuide,
    About,
    ReportBug,
    ProjectWiki,
    RemoveCard,
    MakeEligibleCardsReversible,
    UndoBulkReversal,
    ScrubDatabase,
    ReclaimDatabaseSpace,
    RecentDeck(usize),
    TrayRestore,
    TrayQuit,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DeferredMenuEventDisposition {
    Dispatch(DeferredMenuCommand),
    Skip,
}

impl DeferredMenuCommand {
    #[cfg(test)]
    pub(crate) const NON_RECENT_COMMANDS: [DeferredMenuCommand; 19] = [
        DeferredMenuCommand::OpenDeck,
        DeferredMenuCommand::Options,
        DeferredMenuCommand::DeckProperties,
        DeferredMenuCommand::EditDeckProperties,
        DeferredMenuCommand::LearningDashboard,
        DeferredMenuCommand::AddCard,
        DeferredMenuCommand::AddCardBatch,
        DeferredMenuCommand::EditCard,
        DeferredMenuCommand::UserGuide,
        DeferredMenuCommand::About,
        DeferredMenuCommand::ReportBug,
        DeferredMenuCommand::ProjectWiki,
        DeferredMenuCommand::RemoveCard,
        DeferredMenuCommand::MakeEligibleCardsReversible,
        DeferredMenuCommand::UndoBulkReversal,
        DeferredMenuCommand::ScrubDatabase,
        DeferredMenuCommand::ReclaimDatabaseSpace,
        DeferredMenuCommand::TrayRestore,
        DeferredMenuCommand::TrayQuit,
    ];

    pub(crate) fn id(self) -> i32 {
        match self {
            DeferredMenuCommand::OpenDeck => DEFERRED_MENU_OPEN_DECK_ID,
            DeferredMenuCommand::Options => DEFERRED_MENU_OPTIONS_ID,
            DeferredMenuCommand::DeckProperties => DEFERRED_MENU_DECK_PROPERTIES_ID,
            DeferredMenuCommand::EditDeckProperties => DEFERRED_MENU_EDIT_DECK_PROPERTIES_ID,
            DeferredMenuCommand::LearningDashboard => DEFERRED_MENU_LEARNING_DASHBOARD_ID,
            DeferredMenuCommand::AddCard => DEFERRED_MENU_ADD_CARD_ID,
            DeferredMenuCommand::AddCardBatch => DEFERRED_MENU_ADD_CARD_BATCH_ID,
            DeferredMenuCommand::EditCard => DEFERRED_MENU_EDIT_CARD_ID,
            DeferredMenuCommand::UserGuide => DEFERRED_MENU_USER_GUIDE_ID,
            DeferredMenuCommand::About => DEFERRED_MENU_ABOUT_ID,
            DeferredMenuCommand::ReportBug => DEFERRED_MENU_REPORT_BUG_ID,
            DeferredMenuCommand::ProjectWiki => DEFERRED_MENU_PROJECT_WIKI_ID,
            DeferredMenuCommand::RemoveCard => DEFERRED_MENU_REMOVE_CARD_ID,
            DeferredMenuCommand::MakeEligibleCardsReversible => {
                DEFERRED_MENU_MAKE_ELIGIBLE_REVERSIBLE_ID
            }
            DeferredMenuCommand::UndoBulkReversal => DEFERRED_MENU_UNDO_BULK_REVERSAL_ID,
            DeferredMenuCommand::ScrubDatabase => DEFERRED_MENU_SCRUB_DATABASE_ID,
            DeferredMenuCommand::ReclaimDatabaseSpace => DEFERRED_MENU_RECLAIM_DATABASE_SPACE_ID,
            DeferredMenuCommand::RecentDeck(index) => recent_deck_command_id(index)
                .expect("recent deck command index must be within the menu slot range"),
            DeferredMenuCommand::TrayRestore => TRAY_MENU_RESTORE_ID,
            DeferredMenuCommand::TrayQuit => TRAY_MENU_QUIT_ID,
        }
    }

    pub(crate) fn from_id(id: i32) -> Option<Self> {
        match id {
            DEFERRED_MENU_OPEN_DECK_ID => Some(DeferredMenuCommand::OpenDeck),
            DEFERRED_MENU_OPTIONS_ID => Some(DeferredMenuCommand::Options),
            DEFERRED_MENU_DECK_PROPERTIES_ID => Some(DeferredMenuCommand::DeckProperties),
            DEFERRED_MENU_EDIT_DECK_PROPERTIES_ID => Some(DeferredMenuCommand::EditDeckProperties),
            DEFERRED_MENU_LEARNING_DASHBOARD_ID => Some(DeferredMenuCommand::LearningDashboard),
            DEFERRED_MENU_ADD_CARD_ID => Some(DeferredMenuCommand::AddCard),
            DEFERRED_MENU_ADD_CARD_BATCH_ID => Some(DeferredMenuCommand::AddCardBatch),
            DEFERRED_MENU_EDIT_CARD_ID => Some(DeferredMenuCommand::EditCard),
            DEFERRED_MENU_USER_GUIDE_ID => Some(DeferredMenuCommand::UserGuide),
            DEFERRED_MENU_ABOUT_ID => Some(DeferredMenuCommand::About),
            DEFERRED_MENU_REPORT_BUG_ID => Some(DeferredMenuCommand::ReportBug),
            DEFERRED_MENU_PROJECT_WIKI_ID => Some(DeferredMenuCommand::ProjectWiki),
            DEFERRED_MENU_REMOVE_CARD_ID => Some(DeferredMenuCommand::RemoveCard),
            DEFERRED_MENU_MAKE_ELIGIBLE_REVERSIBLE_ID => {
                Some(DeferredMenuCommand::MakeEligibleCardsReversible)
            }
            DEFERRED_MENU_UNDO_BULK_REVERSAL_ID => Some(DeferredMenuCommand::UndoBulkReversal),
            DEFERRED_MENU_SCRUB_DATABASE_ID => Some(DeferredMenuCommand::ScrubDatabase),
            DEFERRED_MENU_RECLAIM_DATABASE_SPACE_ID => {
                Some(DeferredMenuCommand::ReclaimDatabaseSpace)
            }
            TRAY_MENU_RESTORE_ID => Some(DeferredMenuCommand::TrayRestore),
            TRAY_MENU_QUIT_ID => Some(DeferredMenuCommand::TrayQuit),
            DEFERRED_MENU_RECENT_DECK_BASE_ID..=DEFERRED_MENU_RECENT_DECK_LAST_ID => Some(
                DeferredMenuCommand::RecentDeck((id - DEFERRED_MENU_RECENT_DECK_BASE_ID) as usize),
            ),
            _ => None,
        }
    }
}

pub(crate) fn deferred_menu_event_disposition(event_id: i32) -> DeferredMenuEventDisposition {
    DeferredMenuCommand::from_id(event_id)
        .map(DeferredMenuEventDisposition::Dispatch)
        .unwrap_or(DeferredMenuEventDisposition::Skip)
}

pub(crate) fn install_deferred_menu_handlers<F>(frame: Frame, dispatch: F)
where
    F: Fn(DeferredMenuCommand) + 'static,
{
    frame.on_menu(
        move |event| match deferred_menu_event_disposition(event.get_id()) {
            DeferredMenuEventDisposition::Dispatch(command) => {
                event.skip(false);
                dispatch(command);
            }
            DeferredMenuEventDisposition::Skip => event.skip(true),
        },
    );
}

pub(crate) fn recent_deck_command_id(index: usize) -> Option<i32> {
    if index < MAX_RECENT_DECKS {
        Some(DEFERRED_MENU_RECENT_DECK_BASE_ID + index as i32)
    } else {
        None
    }
}

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

    #[test]
    fn non_recent_commands_round_trip_through_ids() {
        for command in DeferredMenuCommand::NON_RECENT_COMMANDS {
            assert_eq!(DeferredMenuCommand::from_id(command.id()), Some(command));
        }
    }

    #[test]
    fn recent_deck_commands_round_trip_through_slot_ids() {
        for index in 0..MAX_RECENT_DECKS {
            let command = DeferredMenuCommand::RecentDeck(index);
            assert_eq!(DeferredMenuCommand::from_id(command.id()), Some(command));
            assert_eq!(recent_deck_command_id(index), Some(command.id()));
        }
    }

    #[test]
    fn recent_deck_command_ids_reject_out_of_range_slots() {
        assert_eq!(recent_deck_command_id(MAX_RECENT_DECKS), None);
        assert_eq!(
            DeferredMenuCommand::from_id(DEFERRED_MENU_RECENT_DECK_BASE_ID - 1),
            None
        );
        assert_eq!(
            DeferredMenuCommand::from_id(DEFERRED_MENU_RECENT_DECK_LAST_ID + 1),
            None
        );
    }

    #[test]
    fn menu_event_disposition_dispatches_known_ids_and_skips_unknown_ids() {
        assert_eq!(
            deferred_menu_event_disposition(DeferredMenuCommand::OpenDeck.id()),
            DeferredMenuEventDisposition::Dispatch(DeferredMenuCommand::OpenDeck)
        );
        assert_eq!(
            deferred_menu_event_disposition(DeferredMenuCommand::RecentDeck(3).id()),
            DeferredMenuEventDisposition::Dispatch(DeferredMenuCommand::RecentDeck(3))
        );
        assert_eq!(
            deferred_menu_event_disposition(32_999),
            DeferredMenuEventDisposition::Skip
        );
    }
}
