use crate::OptionsUi;
use crate::app_config::{
    AppSettings, ReviewAudioPolicy, ReviewDbBackupPolicy, SoundNotificationPolicy,
};
use crate::dialog_support::{is_affirmative_dialog_result, show_yes_no_dialog_with_parent};
use crate::file_ops::{DeckBackupPolicy, default_backup_dir, prune_obsolete_deck_backups};
use crate::learning_dashboard_model::CalibrationRecallRule;
use crate::localization::{Language, Localizer};
use wxdragon::id::{ID_CANCEL, ID_OK};
use wxdragon::prelude::*;

pub(crate) fn available_languages() -> &'static [Language] {
    &[Language::English, Language::Spanish, Language::Galician]
}

fn available_deck_backup_policies() -> &'static [DeckBackupPolicy] {
    &[
        DeckBackupPolicy::Full,
        DeckBackupPolicy::LastOnly,
        DeckBackupPolicy::None,
    ]
}

fn available_review_db_backup_policies() -> &'static [ReviewDbBackupPolicy] {
    &[
        ReviewDbBackupPolicy::OnSessionStart,
        ReviewDbBackupPolicy::Off,
    ]
}

fn available_sound_notification_policies() -> &'static [SoundNotificationPolicy] {
    &[
        SoundNotificationPolicy::Never,
        SoundNotificationPolicy::Always,
        SoundNotificationPolicy::WhenVisible,
        SoundNotificationPolicy::WhenInTray,
    ]
}

fn available_review_audio_policies() -> &'static [ReviewAudioPolicy] {
    &[
        ReviewAudioPolicy::Off,
        ReviewAudioPolicy::Manual,
        ReviewAudioPolicy::Automatic,
    ]
}

fn available_calibration_recall_rules() -> &'static [CalibrationRecallRule] {
    CalibrationRecallRule::available()
}

pub(crate) fn language_selection_index(language: Language) -> Option<u32> {
    available_languages()
        .iter()
        .position(|candidate| *candidate == language)
        .map(|index| index as u32)
}

pub(crate) fn deck_backup_policy_selection_index(policy: DeckBackupPolicy) -> Option<u32> {
    available_deck_backup_policies()
        .iter()
        .position(|candidate| *candidate == policy)
        .map(|index| index as u32)
}

pub(crate) fn review_db_backup_policy_selection_index(policy: ReviewDbBackupPolicy) -> Option<u32> {
    available_review_db_backup_policies()
        .iter()
        .position(|candidate| *candidate == policy)
        .map(|index| index as u32)
}

pub(crate) fn sound_notification_policy_selection_index(
    policy: SoundNotificationPolicy,
) -> Option<u32> {
    available_sound_notification_policies()
        .iter()
        .position(|candidate| *candidate == policy)
        .map(|index| index as u32)
}

pub(crate) fn review_audio_policy_selection_index(policy: ReviewAudioPolicy) -> Option<u32> {
    available_review_audio_policies()
        .iter()
        .position(|candidate| *candidate == policy)
        .map(|index| index as u32)
}

pub(crate) fn calibration_recall_rule_selection_index(rule: CalibrationRecallRule) -> Option<u32> {
    available_calibration_recall_rules()
        .iter()
        .position(|candidate| *candidate == rule)
        .map(|index| index as u32)
}

fn deck_backup_policy_display_name(localizer: Localizer, policy: DeckBackupPolicy) -> &'static str {
    match policy {
        DeckBackupPolicy::Full => localizer.deck_backup_full_choice(),
        DeckBackupPolicy::LastOnly => localizer.deck_backup_last_only_choice(),
        DeckBackupPolicy::None => localizer.deck_backup_none_choice(),
    }
}

fn review_db_backup_policy_display_name(
    localizer: Localizer,
    policy: ReviewDbBackupPolicy,
) -> &'static str {
    match policy {
        ReviewDbBackupPolicy::OnSessionStart => {
            localizer.review_db_backup_on_session_start_choice()
        }
        ReviewDbBackupPolicy::Off => localizer.review_db_backup_off_choice(),
    }
}

fn sound_notification_policy_display_name(
    localizer: Localizer,
    policy: SoundNotificationPolicy,
) -> &'static str {
    match policy {
        SoundNotificationPolicy::Never => localizer.sound_notification_never_choice(),
        SoundNotificationPolicy::Always => localizer.sound_notification_always_choice(),
        SoundNotificationPolicy::WhenVisible => localizer.sound_notification_when_visible_choice(),
        SoundNotificationPolicy::WhenInTray => localizer.sound_notification_when_in_tray_choice(),
    }
}

fn review_audio_policy_display_name(
    localizer: Localizer,
    policy: ReviewAudioPolicy,
) -> &'static str {
    match policy {
        ReviewAudioPolicy::Off => localizer.review_audio_off_choice(),
        ReviewAudioPolicy::Manual => localizer.review_audio_manual_choice(),
        ReviewAudioPolicy::Automatic => localizer.review_audio_automatic_choice(),
    }
}

fn calibration_recall_rule_display_name(
    localizer: Localizer,
    rule: CalibrationRecallRule,
) -> &'static str {
    match rule {
        CalibrationRecallRule::Lenient => localizer.learning_dashboard_lenient_recall_rule(),
        CalibrationRecallRule::Strict => localizer.learning_dashboard_strict_recall_rule(),
    }
}

pub(crate) fn show_options_dialog(
    parent: Frame,
    localizer: Localizer,
    current_settings: AppSettings,
) -> Option<AppSettings> {
    let dialog = OptionsUi::new(Some(&parent), false);
    let save_id = OptionsUi::xrc_id("button_options_ok");

    dialog
        .dialog_options
        .set_label(localizer.options_dialog_title());
    dialog
        .label_options_status
        .set_label(localizer.options_dialog_status());
    dialog
        .label_options_language
        .set_label(localizer.options_language_label());
    dialog
        .label_options_minimum_cards
        .set_label(localizer.options_minimum_cards_label());
    dialog
        .label_options_deck_backups
        .set_label(localizer.options_deck_backups_label());
    dialog
        .label_options_review_db_backups
        .set_label(localizer.options_review_db_backups_label());
    dialog
        .label_options_sound_notifications
        .set_label(localizer.options_sound_notifications_label());
    dialog
        .label_options_review_audio
        .set_label(localizer.options_review_audio_label());
    dialog
        .label_options_calibration_recall_rule
        .set_label(localizer.options_calibration_recall_rule_label());
    dialog
        .checkbox_options_minimize_to_tray
        .set_label(localizer.options_minimize_to_tray_checkbox());
    dialog
        .button_options_cancel
        .set_label(localizer.options_cancel_button());
    dialog
        .button_options_ok
        .set_label(localizer.options_ok_button());
    dialog
        .button_options_prune_backups
        .set_label(localizer.options_prune_backups_button());

    dialog.choice_options_language.clear();
    for language in available_languages() {
        dialog
            .choice_options_language
            .append(localizer.language_display_name(*language));
    }
    if let Some(index) = language_selection_index(current_settings.language) {
        dialog.choice_options_language.set_selection(index);
    }
    dialog
        .spin_options_minimum_cards
        .set_value(current_settings.minimum_cards_per_session as i32);
    dialog.choice_options_deck_backups.clear();
    for policy in available_deck_backup_policies() {
        dialog
            .choice_options_deck_backups
            .append(deck_backup_policy_display_name(localizer, *policy));
    }
    if let Some(index) = deck_backup_policy_selection_index(current_settings.deck_backup_policy) {
        dialog.choice_options_deck_backups.set_selection(index);
    }
    dialog.choice_options_review_db_backups.clear();
    for policy in available_review_db_backup_policies() {
        dialog
            .choice_options_review_db_backups
            .append(review_db_backup_policy_display_name(localizer, *policy));
    }
    if let Some(index) =
        review_db_backup_policy_selection_index(current_settings.review_db_backup_policy)
    {
        dialog.choice_options_review_db_backups.set_selection(index);
    }
    dialog.choice_options_sound_notifications.clear();
    for policy in available_sound_notification_policies() {
        dialog
            .choice_options_sound_notifications
            .append(sound_notification_policy_display_name(localizer, *policy));
    }
    if let Some(index) =
        sound_notification_policy_selection_index(current_settings.sound_notification_policy)
    {
        dialog
            .choice_options_sound_notifications
            .set_selection(index);
    }
    dialog.choice_options_review_audio.clear();
    for policy in available_review_audio_policies() {
        dialog
            .choice_options_review_audio
            .append(review_audio_policy_display_name(localizer, *policy));
    }
    if let Some(index) = review_audio_policy_selection_index(current_settings.review_audio_policy) {
        dialog.choice_options_review_audio.set_selection(index);
    }
    dialog.choice_options_calibration_recall_rule.clear();
    for rule in available_calibration_recall_rules() {
        dialog
            .choice_options_calibration_recall_rule
            .append(calibration_recall_rule_display_name(localizer, *rule));
    }
    if let Some(index) =
        calibration_recall_rule_selection_index(current_settings.calibration_recall_rule)
    {
        dialog
            .choice_options_calibration_recall_rule
            .set_selection(index);
    }
    dialog
        .checkbox_options_minimize_to_tray
        .set_value(current_settings.minimize_to_tray);

    let modal_dialog = dialog.dialog_options;
    dialog
        .button_options_ok
        .on_click(move |_| modal_dialog.end_modal(ID_OK));

    let modal_dialog = dialog.dialog_options;
    dialog
        .button_options_cancel
        .on_click(move |_| modal_dialog.end_modal(ID_CANCEL));

    let options_dialog = dialog.dialog_options;
    let status = dialog.label_options_status;
    let prune_button = dialog.button_options_prune_backups;
    dialog.button_options_prune_backups.on_click(move |_| {
        if !show_yes_no_dialog_with_parent(
            &options_dialog,
            localizer.prune_backups_caption(),
            localizer.prune_backups_message(),
        ) {
            prune_button.set_focus();
            return;
        }

        match prune_obsolete_deck_backups(&default_backup_dir()) {
            Ok(summary) => status.set_label(&format!(
                "{}: {}",
                localizer.prune_backups_completed_caption(),
                localizer
                    .prune_backups_completed_message(summary.removed_count, summary.kept_count)
            )),
            Err(error) => status.set_label(&format!(
                "{}: {}",
                localizer.could_not_prune_backups_caption(),
                error
            )),
        }
        prune_button.set_focus();
    });

    dialog.dialog_options.set_affirmative_id(save_id);
    dialog
        .dialog_options
        .set_escape_id(OptionsUi::xrc_id("button_options_cancel"));
    dialog.button_options_ok.set_default();
    dialog.choice_options_language.set_focus();

    let result = dialog.dialog_options.show_modal();
    let settings = if is_affirmative_dialog_result(result, save_id) {
        let selected_language = dialog
            .choice_options_language
            .get_selection()
            .and_then(|index| available_languages().get(index as usize).copied())
            .unwrap_or(current_settings.language);
        Some(AppSettings {
            language: selected_language,
            minimum_cards_per_session: dialog.spin_options_minimum_cards.value() as usize,
            deck_backup_policy: dialog
                .choice_options_deck_backups
                .get_selection()
                .and_then(|index| {
                    available_deck_backup_policies()
                        .get(index as usize)
                        .copied()
                })
                .unwrap_or(current_settings.deck_backup_policy),
            review_db_backup_policy: dialog
                .choice_options_review_db_backups
                .get_selection()
                .and_then(|index| {
                    available_review_db_backup_policies()
                        .get(index as usize)
                        .copied()
                })
                .unwrap_or(current_settings.review_db_backup_policy),
            sound_notification_policy: dialog
                .choice_options_sound_notifications
                .get_selection()
                .and_then(|index| {
                    available_sound_notification_policies()
                        .get(index as usize)
                        .copied()
                })
                .unwrap_or(current_settings.sound_notification_policy),
            review_audio_policy: dialog
                .choice_options_review_audio
                .get_selection()
                .and_then(|index| {
                    available_review_audio_policies()
                        .get(index as usize)
                        .copied()
                })
                .unwrap_or(current_settings.review_audio_policy),
            calibration_recall_rule: dialog
                .choice_options_calibration_recall_rule
                .get_selection()
                .and_then(|index| {
                    available_calibration_recall_rules()
                        .get(index as usize)
                        .copied()
                })
                .unwrap_or(current_settings.calibration_recall_rule),
            minimize_to_tray: dialog.checkbox_options_minimize_to_tray.is_checked(),
        })
    } else {
        None
    };
    dialog.dialog_options.destroy();
    settings
}

#[cfg(test)]
mod tests {
    #[test]
    fn backup_prune_completion_stays_in_options_dialog() {
        let source = include_str!("options_dialog.rs")
            .split("#[cfg(test)]")
            .next()
            .expect("runtime source before tests");

        assert!(source.contains("show_yes_no_dialog_with_parent(\n            &options_dialog,"));
        assert!(source.contains("status.set_label("));
        assert!(source.contains("prune_button.set_focus();"));
        assert!(!source.contains("show_info_dialog_with_parent("));
        assert!(!source.contains("show_error_dialog_with_parent("));
    }

    #[test]
    fn options_dialog_does_not_announce_redundant_minimise_label_on_open() {
        let source = include_str!("options_dialog.rs")
            .split("#[cfg(test)]")
            .next()
            .expect("runtime source before tests");
        let xrc = include_str!("../ui/options_dialog.xrc");

        assert!(!source.contains("label_options_minimize_to_tray\n        .set_label"));
        assert!(xrc.contains("<object class=\"wxStaticText\" name=\"label_options_minimize_to_tray\">\n                <label></label>"));
        assert!(xrc.contains("<object class=\"wxCheckBox\" name=\"checkbox_options_minimize_to_tray\">\n                <label>Minimise to system tray</label>"));
    }
}
