//! Deferred menu command identifiers and dispatch classification.
//!
//! wx menu events are posted through stable private command IDs so handlers can
//! defer substantial work out of native menu callbacks. This module maps those
//! IDs to typed commands; feature-specific command handling lives in `main.rs`
//! dispatch and the relevant action modules.

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_RESOLVE_DUPLICATE_PROMPTS_ID: i32 = 32_030;
const DEFERRED_MENU_GENERATE_AUDIO_ID: i32 = 32_031;
const DEFERRED_MENU_EXPORT_DECK_BUNDLE_ID: i32 = 32_032;
const DEFERRED_MENU_WRITE_DECK_BUNDLE_BACKUP_ID: i32 = 32_033;
const DEFERRED_MENU_EXPORT_DECK_BUNDLE_WITH_STUDY_HISTORY_ID: i32 = 32_034;
const DEFERRED_MENU_RESTORE_DECK_BUNDLE_ID: i32 = 32_035;
const DEFERRED_MENU_IMPORT_ANKI_DECK_ID: i32 = 32_036;
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;
const DEFERRED_MENU_FLAG_ALL_SHOWN_CARDS_ID: i32 = 32_050;
const DEFERRED_MENU_UNFLAG_ALL_SHOWN_CARDS_ID: i32 = 32_051;
const DEFERRED_MENU_SUSPEND_ALL_SHOWN_CARDS_ID: i32 = 32_052;
const DEFERRED_MENU_UNSUSPEND_ALL_SHOWN_CARDS_ID: i32 = 32_053;
const DEFERRED_MENU_CARD_DETAILS_ID: i32 = 32_054;
const DEFERRED_MENU_WIPE_BACKUP_DIRECTORY_ID: i32 = 32_055;
const DEFERRED_MENU_SYNC_UPLOAD_ID: i32 = 32_056;
const DEFERRED_MENU_SYNC_DOWNLOAD_ID: i32 = 32_057;
const DEFERRED_MENU_SYNC_STATUS_ID: i32 = 32_058;
const DEFERRED_MENU_SYNC_REMOTE_DECKS_ID: i32 = 32_059;
const DEFERRED_MENU_SYNC_LOGIN_ID: i32 = 32_060;
const DEFERRED_MENU_SYNC_LOGOUT_ID: i32 = 32_061;
const DEFERRED_MENU_REMOVE_INOPERATIVE_AUDIO_ID: i32 = 32_062;
const DEFERRED_MENU_SYNC_INVALIDATE_TOKEN_ID: i32 = 32_063;
const DEFERRED_MENU_SYNC_INVALIDATE_ALL_TOKENS_ID: i32 = 32_064;
const DEFERRED_MENU_PRUNE_BACKUPS_ID: i32 = 32_065;
const DEFERRED_MENU_SYNC_DELETE_ACCOUNT_ID: i32 = 32_066;
const DEFERRED_MENU_SYNC_REGISTER_ACCOUNT_ID: i32 = 32_067;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct RecentDeckSlot {
    index: usize,
}

impl RecentDeckSlot {
    pub(crate) fn new(index: usize) -> Option<Self> {
        (index < MAX_RECENT_DECKS).then_some(Self { index })
    }

    pub(crate) fn index(self) -> usize {
        self.index
    }

    pub(crate) fn display_number(self) -> usize {
        self.index + 1
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DeferredMenuCommand {
    OpenDeck,
    Options,
    DeckProperties,
    EditDeckProperties,
    SyncStatus,
    SyncRemoteDecks,
    SyncLogin,
    SyncLogout,
    SyncRegisterAccount,
    SyncInvalidateToken,
    SyncInvalidateAllTokens,
    SyncDeleteAccount,
    SyncUpload,
    SyncDownload,
    LearningDashboard,
    GenerateAudio,
    RemoveInoperativeAudio,
    ExportDeckBundle,
    ExportDeckBundleWithStudyHistory,
    WriteDeckBundleBackup,
    RestoreDeckBundle,
    ImportAnkiDeck,
    AddCard,
    AddCardBatch,
    EditCard,
    CardDetails,
    UserGuide,
    About,
    ReportBug,
    ProjectWiki,
    RemoveCard,
    ResolveDuplicatePrompts,
    MakeEligibleCardsReversible,
    UndoBulkReversal,
    FlagAllShownCards,
    UnflagAllShownCards,
    SuspendAllShownCards,
    UnsuspendAllShownCards,
    ScrubDatabase,
    ReclaimDatabaseSpace,
    PruneBackups,
    WipeBackupDirectory,
    RecentDeck(RecentDeckSlot),
    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: &'static [DeferredMenuCommand] = &[
        DeferredMenuCommand::OpenDeck,
        DeferredMenuCommand::Options,
        DeferredMenuCommand::DeckProperties,
        DeferredMenuCommand::EditDeckProperties,
        DeferredMenuCommand::SyncStatus,
        DeferredMenuCommand::SyncRemoteDecks,
        DeferredMenuCommand::SyncLogin,
        DeferredMenuCommand::SyncLogout,
        DeferredMenuCommand::SyncRegisterAccount,
        DeferredMenuCommand::SyncInvalidateToken,
        DeferredMenuCommand::SyncInvalidateAllTokens,
        DeferredMenuCommand::SyncDeleteAccount,
        DeferredMenuCommand::SyncUpload,
        DeferredMenuCommand::SyncDownload,
        DeferredMenuCommand::LearningDashboard,
        DeferredMenuCommand::GenerateAudio,
        DeferredMenuCommand::RemoveInoperativeAudio,
        DeferredMenuCommand::ExportDeckBundle,
        DeferredMenuCommand::ExportDeckBundleWithStudyHistory,
        DeferredMenuCommand::WriteDeckBundleBackup,
        DeferredMenuCommand::RestoreDeckBundle,
        DeferredMenuCommand::ImportAnkiDeck,
        DeferredMenuCommand::AddCard,
        DeferredMenuCommand::AddCardBatch,
        DeferredMenuCommand::EditCard,
        DeferredMenuCommand::CardDetails,
        DeferredMenuCommand::UserGuide,
        DeferredMenuCommand::About,
        DeferredMenuCommand::ReportBug,
        DeferredMenuCommand::ProjectWiki,
        DeferredMenuCommand::RemoveCard,
        DeferredMenuCommand::ResolveDuplicatePrompts,
        DeferredMenuCommand::MakeEligibleCardsReversible,
        DeferredMenuCommand::UndoBulkReversal,
        DeferredMenuCommand::FlagAllShownCards,
        DeferredMenuCommand::UnflagAllShownCards,
        DeferredMenuCommand::SuspendAllShownCards,
        DeferredMenuCommand::UnsuspendAllShownCards,
        DeferredMenuCommand::ScrubDatabase,
        DeferredMenuCommand::ReclaimDatabaseSpace,
        DeferredMenuCommand::PruneBackups,
        DeferredMenuCommand::WipeBackupDirectory,
        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::SyncStatus => DEFERRED_MENU_SYNC_STATUS_ID,
            DeferredMenuCommand::SyncRemoteDecks => DEFERRED_MENU_SYNC_REMOTE_DECKS_ID,
            DeferredMenuCommand::SyncLogin => DEFERRED_MENU_SYNC_LOGIN_ID,
            DeferredMenuCommand::SyncLogout => DEFERRED_MENU_SYNC_LOGOUT_ID,
            DeferredMenuCommand::SyncRegisterAccount => DEFERRED_MENU_SYNC_REGISTER_ACCOUNT_ID,
            DeferredMenuCommand::SyncInvalidateToken => DEFERRED_MENU_SYNC_INVALIDATE_TOKEN_ID,
            DeferredMenuCommand::SyncInvalidateAllTokens => {
                DEFERRED_MENU_SYNC_INVALIDATE_ALL_TOKENS_ID
            }
            DeferredMenuCommand::SyncDeleteAccount => DEFERRED_MENU_SYNC_DELETE_ACCOUNT_ID,
            DeferredMenuCommand::SyncUpload => DEFERRED_MENU_SYNC_UPLOAD_ID,
            DeferredMenuCommand::SyncDownload => DEFERRED_MENU_SYNC_DOWNLOAD_ID,
            DeferredMenuCommand::LearningDashboard => DEFERRED_MENU_LEARNING_DASHBOARD_ID,
            DeferredMenuCommand::GenerateAudio => DEFERRED_MENU_GENERATE_AUDIO_ID,
            DeferredMenuCommand::RemoveInoperativeAudio => {
                DEFERRED_MENU_REMOVE_INOPERATIVE_AUDIO_ID
            }
            DeferredMenuCommand::ExportDeckBundle => DEFERRED_MENU_EXPORT_DECK_BUNDLE_ID,
            DeferredMenuCommand::ExportDeckBundleWithStudyHistory => {
                DEFERRED_MENU_EXPORT_DECK_BUNDLE_WITH_STUDY_HISTORY_ID
            }
            DeferredMenuCommand::WriteDeckBundleBackup => DEFERRED_MENU_WRITE_DECK_BUNDLE_BACKUP_ID,
            DeferredMenuCommand::RestoreDeckBundle => DEFERRED_MENU_RESTORE_DECK_BUNDLE_ID,
            DeferredMenuCommand::ImportAnkiDeck => DEFERRED_MENU_IMPORT_ANKI_DECK_ID,
            DeferredMenuCommand::AddCard => DEFERRED_MENU_ADD_CARD_ID,
            DeferredMenuCommand::AddCardBatch => DEFERRED_MENU_ADD_CARD_BATCH_ID,
            DeferredMenuCommand::EditCard => DEFERRED_MENU_EDIT_CARD_ID,
            DeferredMenuCommand::CardDetails => DEFERRED_MENU_CARD_DETAILS_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::ResolveDuplicatePrompts => {
                DEFERRED_MENU_RESOLVE_DUPLICATE_PROMPTS_ID
            }
            DeferredMenuCommand::MakeEligibleCardsReversible => {
                DEFERRED_MENU_MAKE_ELIGIBLE_REVERSIBLE_ID
            }
            DeferredMenuCommand::UndoBulkReversal => DEFERRED_MENU_UNDO_BULK_REVERSAL_ID,
            DeferredMenuCommand::FlagAllShownCards => DEFERRED_MENU_FLAG_ALL_SHOWN_CARDS_ID,
            DeferredMenuCommand::UnflagAllShownCards => DEFERRED_MENU_UNFLAG_ALL_SHOWN_CARDS_ID,
            DeferredMenuCommand::SuspendAllShownCards => DEFERRED_MENU_SUSPEND_ALL_SHOWN_CARDS_ID,
            DeferredMenuCommand::UnsuspendAllShownCards => {
                DEFERRED_MENU_UNSUSPEND_ALL_SHOWN_CARDS_ID
            }
            DeferredMenuCommand::ScrubDatabase => DEFERRED_MENU_SCRUB_DATABASE_ID,
            DeferredMenuCommand::ReclaimDatabaseSpace => DEFERRED_MENU_RECLAIM_DATABASE_SPACE_ID,
            DeferredMenuCommand::PruneBackups => DEFERRED_MENU_PRUNE_BACKUPS_ID,
            DeferredMenuCommand::WipeBackupDirectory => DEFERRED_MENU_WIPE_BACKUP_DIRECTORY_ID,
            DeferredMenuCommand::RecentDeck(slot) => recent_deck_command_id(slot),
            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_SYNC_STATUS_ID => Some(DeferredMenuCommand::SyncStatus),
            DEFERRED_MENU_SYNC_REMOTE_DECKS_ID => Some(DeferredMenuCommand::SyncRemoteDecks),
            DEFERRED_MENU_SYNC_LOGIN_ID => Some(DeferredMenuCommand::SyncLogin),
            DEFERRED_MENU_SYNC_LOGOUT_ID => Some(DeferredMenuCommand::SyncLogout),
            DEFERRED_MENU_SYNC_REGISTER_ACCOUNT_ID => {
                Some(DeferredMenuCommand::SyncRegisterAccount)
            }
            DEFERRED_MENU_SYNC_INVALIDATE_TOKEN_ID => {
                Some(DeferredMenuCommand::SyncInvalidateToken)
            }
            DEFERRED_MENU_SYNC_INVALIDATE_ALL_TOKENS_ID => {
                Some(DeferredMenuCommand::SyncInvalidateAllTokens)
            }
            DEFERRED_MENU_SYNC_DELETE_ACCOUNT_ID => Some(DeferredMenuCommand::SyncDeleteAccount),
            DEFERRED_MENU_SYNC_UPLOAD_ID => Some(DeferredMenuCommand::SyncUpload),
            DEFERRED_MENU_SYNC_DOWNLOAD_ID => Some(DeferredMenuCommand::SyncDownload),
            DEFERRED_MENU_LEARNING_DASHBOARD_ID => Some(DeferredMenuCommand::LearningDashboard),
            DEFERRED_MENU_GENERATE_AUDIO_ID => Some(DeferredMenuCommand::GenerateAudio),
            DEFERRED_MENU_REMOVE_INOPERATIVE_AUDIO_ID => {
                Some(DeferredMenuCommand::RemoveInoperativeAudio)
            }
            DEFERRED_MENU_EXPORT_DECK_BUNDLE_ID => Some(DeferredMenuCommand::ExportDeckBundle),
            DEFERRED_MENU_EXPORT_DECK_BUNDLE_WITH_STUDY_HISTORY_ID => {
                Some(DeferredMenuCommand::ExportDeckBundleWithStudyHistory)
            }
            DEFERRED_MENU_WRITE_DECK_BUNDLE_BACKUP_ID => {
                Some(DeferredMenuCommand::WriteDeckBundleBackup)
            }
            DEFERRED_MENU_RESTORE_DECK_BUNDLE_ID => Some(DeferredMenuCommand::RestoreDeckBundle),
            DEFERRED_MENU_IMPORT_ANKI_DECK_ID => Some(DeferredMenuCommand::ImportAnkiDeck),
            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_CARD_DETAILS_ID => Some(DeferredMenuCommand::CardDetails),
            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_RESOLVE_DUPLICATE_PROMPTS_ID => {
                Some(DeferredMenuCommand::ResolveDuplicatePrompts)
            }
            DEFERRED_MENU_MAKE_ELIGIBLE_REVERSIBLE_ID => {
                Some(DeferredMenuCommand::MakeEligibleCardsReversible)
            }
            DEFERRED_MENU_UNDO_BULK_REVERSAL_ID => Some(DeferredMenuCommand::UndoBulkReversal),
            DEFERRED_MENU_FLAG_ALL_SHOWN_CARDS_ID => Some(DeferredMenuCommand::FlagAllShownCards),
            DEFERRED_MENU_UNFLAG_ALL_SHOWN_CARDS_ID => {
                Some(DeferredMenuCommand::UnflagAllShownCards)
            }
            DEFERRED_MENU_SUSPEND_ALL_SHOWN_CARDS_ID => {
                Some(DeferredMenuCommand::SuspendAllShownCards)
            }
            DEFERRED_MENU_UNSUSPEND_ALL_SHOWN_CARDS_ID => {
                Some(DeferredMenuCommand::UnsuspendAllShownCards)
            }
            DEFERRED_MENU_SCRUB_DATABASE_ID => Some(DeferredMenuCommand::ScrubDatabase),
            DEFERRED_MENU_RECLAIM_DATABASE_SPACE_ID => {
                Some(DeferredMenuCommand::ReclaimDatabaseSpace)
            }
            DEFERRED_MENU_PRUNE_BACKUPS_ID => Some(DeferredMenuCommand::PruneBackups),
            DEFERRED_MENU_WIPE_BACKUP_DIRECTORY_ID => {
                Some(DeferredMenuCommand::WipeBackupDirectory)
            }
            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 => {
                RecentDeckSlot::new((id - DEFERRED_MENU_RECENT_DECK_BASE_ID) as usize)
                    .map(DeferredMenuCommand::RecentDeck)
            }
            _ => 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(slot: RecentDeckSlot) -> i32 {
    DEFERRED_MENU_RECENT_DECK_BASE_ID + slot.index() as i32
}

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

    #[test]
    fn non_recent_commands_round_trip_through_ids() {
        for command in DeferredMenuCommand::NON_RECENT_COMMANDS.iter().copied() {
            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 slot = RecentDeckSlot::new(index).unwrap();
            let command = DeferredMenuCommand::RecentDeck(slot);
            assert_eq!(DeferredMenuCommand::from_id(command.id()), Some(command));
            assert_eq!(recent_deck_command_id(slot), command.id());
        }
    }

    #[test]
    fn recent_deck_command_ids_reject_out_of_range_slots() {
        assert_eq!(RecentDeckSlot::new(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(RecentDeckSlot::new(3).unwrap()).id()
            ),
            DeferredMenuEventDisposition::Dispatch(DeferredMenuCommand::RecentDeck(
                RecentDeckSlot::new(3).unwrap()
            ))
        );
        assert_eq!(
            deferred_menu_event_disposition(32_999),
            DeferredMenuEventDisposition::Skip
        );
    }
}
