use crate::OptionsUi;
use crate::app_config::{
    AppSettings, AudioGenerationFormat, AutomaticFullDeckBackupPolicy, ExamPostAnswerAudioPolicy,
    QuizAudioPolicy, ReviewAudioPolicy, ReviewDbBackupPolicy, SoundNotificationPolicy,
    normalize_sync_server_url, sync_server_url_text_disables_sync,
};
use crate::dialog_support::is_affirmative_dialog_result;
use crate::file_ops::DeckBackupPolicy;
use crate::learning_dashboard_model::CalibrationRecallRule;
use crate::localization::{Language, Localizer};
use cosimo::normalize_device_name;
use wxdragon::id::{ID_CANCEL, ID_OK};
use wxdragon::prelude::*;

pub(crate) fn available_languages() -> &'static [Language] {
    Language::ALL
}

fn available_deck_backup_policies() -> &'static [DeckBackupPolicy] {
    DeckBackupPolicy::ALL
}

fn available_review_db_backup_policies() -> &'static [ReviewDbBackupPolicy] {
    ReviewDbBackupPolicy::ALL
}

fn available_automatic_full_deck_backup_policies() -> &'static [AutomaticFullDeckBackupPolicy] {
    AutomaticFullDeckBackupPolicy::ALL
}

fn available_sound_notification_policies() -> &'static [SoundNotificationPolicy] {
    SoundNotificationPolicy::ALL
}

fn available_review_audio_policies() -> &'static [ReviewAudioPolicy] {
    ReviewAudioPolicy::ALL
}

fn available_audio_generation_formats() -> &'static [AudioGenerationFormat] {
    AudioGenerationFormat::ALL
}

fn available_exam_post_answer_audio_policies() -> &'static [ExamPostAnswerAudioPolicy] {
    ExamPostAnswerAudioPolicy::ALL
}

fn available_quiz_audio_policies() -> &'static [QuizAudioPolicy] {
    QuizAudioPolicy::ALL
}

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

pub(crate) fn language_selection_index(language: Language) -> Option<u32> {
    choice_selection_index(available_languages(), language)
}

pub(crate) fn deck_backup_policy_selection_index(policy: DeckBackupPolicy) -> Option<u32> {
    choice_selection_index(available_deck_backup_policies(), policy)
}

pub(crate) fn review_db_backup_policy_selection_index(policy: ReviewDbBackupPolicy) -> Option<u32> {
    choice_selection_index(available_review_db_backup_policies(), policy)
}

pub(crate) fn automatic_full_deck_backup_policy_selection_index(
    policy: AutomaticFullDeckBackupPolicy,
) -> Option<u32> {
    choice_selection_index(available_automatic_full_deck_backup_policies(), policy)
}

pub(crate) fn sound_notification_policy_selection_index(
    policy: SoundNotificationPolicy,
) -> Option<u32> {
    choice_selection_index(available_sound_notification_policies(), policy)
}

pub(crate) fn review_audio_policy_selection_index(policy: ReviewAudioPolicy) -> Option<u32> {
    choice_selection_index(available_review_audio_policies(), policy)
}

pub(crate) fn audio_generation_format_selection_index(
    format: AudioGenerationFormat,
) -> Option<u32> {
    choice_selection_index(available_audio_generation_formats(), format)
}

pub(crate) fn exam_post_answer_audio_policy_selection_index(
    policy: ExamPostAnswerAudioPolicy,
) -> Option<u32> {
    choice_selection_index(available_exam_post_answer_audio_policies(), policy)
}

pub(crate) fn quiz_audio_policy_selection_index(policy: QuizAudioPolicy) -> Option<u32> {
    choice_selection_index(available_quiz_audio_policies(), policy)
}

pub(crate) fn calibration_recall_rule_selection_index(rule: CalibrationRecallRule) -> Option<u32> {
    choice_selection_index(available_calibration_recall_rules(), rule)
}

fn choice_selection_index<T: Copy + Eq>(choices: &[T], choice: T) -> Option<u32> {
    choices
        .iter()
        .position(|candidate| *candidate == choice)
        .map(|index| index as u32)
}

fn selected_choice_or<T: Copy>(selection: Option<u32>, choices: &[T], fallback: T) -> T {
    selection
        .and_then(|index| choices.get(index as usize).copied())
        .unwrap_or(fallback)
}

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 automatic_full_deck_backup_policy_display_name(
    localizer: Localizer,
    policy: AutomaticFullDeckBackupPolicy,
) -> &'static str {
    match policy {
        AutomaticFullDeckBackupPolicy::Daily => localizer.automatic_full_deck_backup_daily_choice(),
        AutomaticFullDeckBackupPolicy::Off => localizer.automatic_full_deck_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 audio_generation_format_display_name(
    localizer: Localizer,
    format: AudioGenerationFormat,
) -> &'static str {
    match format {
        AudioGenerationFormat::Opus => localizer.audio_generation_format_opus_choice(),
        AudioGenerationFormat::Wav => localizer.audio_generation_format_wav_choice(),
    }
}

fn exam_post_answer_audio_policy_display_name(
    localizer: Localizer,
    policy: ExamPostAnswerAudioPolicy,
) -> &'static str {
    match policy {
        ExamPostAnswerAudioPolicy::Off => localizer.exam_post_answer_audio_off_choice(),
        ExamPostAnswerAudioPolicy::Question => localizer.exam_post_answer_audio_question_choice(),
        ExamPostAnswerAudioPolicy::Answer => localizer.exam_post_answer_audio_answer_choice(),
        ExamPostAnswerAudioPolicy::Both => localizer.exam_post_answer_audio_both_choice(),
    }
}

fn quiz_audio_policy_display_name(localizer: Localizer, policy: QuizAudioPolicy) -> &'static str {
    match policy {
        QuizAudioPolicy::Off => localizer.quiz_audio_off_choice(),
        QuizAudioPolicy::Prompt => localizer.quiz_audio_prompt_choice(),
        QuizAudioPolicy::Choices => localizer.quiz_audio_choices_choice(),
        QuizAudioPolicy::Both => localizer.quiz_audio_both_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(),
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct OptionsDialogResult {
    pub(crate) settings: AppSettings,
    pub(crate) sync_server_url: Option<String>,
    pub(crate) sync_server_url_disabled: bool,
    pub(crate) local_device_name: Option<String>,
}

pub(crate) fn show_options_dialog(
    parent: Frame,
    localizer: Localizer,
    current_settings: AppSettings,
    current_sync_server_url: Option<&str>,
    current_local_device_name: Option<&str>,
) -> Option<OptionsDialogResult> {
    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_new_cards_per_day
        .set_label(localizer.options_new_cards_per_day_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_automatic_full_deck_backups
        .set_label(localizer.options_automatic_full_deck_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());
    let review_audio_cues_label = localizer.options_review_audio_cues_checkbox();
    dialog
        .checkbox_options_review_audio_cues
        .set_label(review_audio_cues_label);
    dialog
        .checkbox_options_review_audio_cues
        .set_name(review_audio_cues_label);
    dialog
        .label_options_audio_generation_format
        .set_label(localizer.options_audio_generation_format_label());
    dialog
        .label_options_exam_post_answer_audio
        .set_label(localizer.options_exam_post_answer_audio_label());
    dialog
        .label_options_quiz_audio
        .set_label(localizer.options_quiz_audio_label());
    dialog
        .label_options_calibration_recall_rule
        .set_label(localizer.options_calibration_recall_rule_label());
    dialog
        .label_options_sync_server_url
        .set_label(localizer.options_sync_server_url_label());
    dialog
        .label_options_local_device_name
        .set_label(localizer.options_local_device_name_label());
    let minimize_to_tray_label = localizer.options_minimize_to_tray_checkbox();
    dialog
        .checkbox_options_minimize_to_tray
        .set_label(minimize_to_tray_label);
    dialog
        .checkbox_options_minimize_to_tray
        .set_name(minimize_to_tray_label);
    dialog
        .button_options_cancel
        .set_label(localizer.options_cancel_button());
    dialog
        .button_options_ok
        .set_label(localizer.options_ok_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
        .spin_options_new_cards_per_day
        .set_value(current_settings.new_cards_per_day 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_automatic_full_deck_backups.clear();
    for policy in available_automatic_full_deck_backup_policies() {
        dialog.choice_options_automatic_full_deck_backups.append(
            automatic_full_deck_backup_policy_display_name(localizer, *policy),
        );
    }
    if let Some(index) = automatic_full_deck_backup_policy_selection_index(
        current_settings.automatic_full_deck_backup_policy,
    ) {
        dialog
            .choice_options_automatic_full_deck_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
        .checkbox_options_review_audio_cues
        .set_value(current_settings.review_audio_cues);
    dialog.choice_options_audio_generation_format.clear();
    for format in available_audio_generation_formats() {
        dialog
            .choice_options_audio_generation_format
            .append(audio_generation_format_display_name(localizer, *format));
    }
    if let Some(index) =
        audio_generation_format_selection_index(current_settings.audio_generation_format)
    {
        dialog
            .choice_options_audio_generation_format
            .set_selection(index);
    }
    dialog.choice_options_exam_post_answer_audio.clear();
    for policy in available_exam_post_answer_audio_policies() {
        dialog.choice_options_exam_post_answer_audio.append(
            exam_post_answer_audio_policy_display_name(localizer, *policy),
        );
    }
    if let Some(index) = exam_post_answer_audio_policy_selection_index(
        current_settings.exam_post_answer_audio_policy,
    ) {
        dialog
            .choice_options_exam_post_answer_audio
            .set_selection(index);
    }
    dialog.choice_options_quiz_audio.clear();
    for policy in available_quiz_audio_policies() {
        dialog
            .choice_options_quiz_audio
            .append(quiz_audio_policy_display_name(localizer, *policy));
    }
    if let Some(index) = quiz_audio_policy_selection_index(current_settings.quiz_audio_policy) {
        dialog.choice_options_quiz_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);
    dialog
        .text_options_sync_server_url
        .set_value(current_sync_server_url.unwrap_or(""));
    dialog.text_options_sync_server_url.set_insertion_point(0);
    dialog
        .text_options_local_device_name
        .set_value(current_local_device_name.unwrap_or(""));
    dialog.text_options_local_device_name.set_insertion_point(0);

    let modal_dialog = dialog.dialog_options;
    let status = dialog.label_options_status;
    let sync_server_url = dialog.text_options_sync_server_url;
    let local_device_name = dialog.text_options_local_device_name;
    dialog.button_options_ok.on_click(move |_| {
        if sync_server_url.get_value().contains(['\n', '\r']) {
            status.set_label(localizer.options_sync_server_url_single_line_status());
            sync_server_url.set_focus();
            return;
        }
        if local_device_name.get_value().contains(['\n', '\r']) {
            status.set_label(localizer.options_local_device_name_single_line_status());
            local_device_name.set_focus();
            return;
        }
        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));

    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 sync_server_url_text = dialog.text_options_sync_server_url.get_value();
        let sync_server_url_disabled = sync_server_url_text_disables_sync(&sync_server_url_text);
        let selected_language = selected_choice_or(
            dialog.choice_options_language.get_selection(),
            available_languages(),
            current_settings.language,
        );
        Some(OptionsDialogResult {
            settings: AppSettings {
                language: selected_language,
                minimum_cards_per_session: dialog.spin_options_minimum_cards.value() as usize,
                new_cards_per_day: dialog.spin_options_new_cards_per_day.value() as usize,
                deck_backup_policy: selected_choice_or(
                    dialog.choice_options_deck_backups.get_selection(),
                    available_deck_backup_policies(),
                    current_settings.deck_backup_policy,
                ),
                review_db_backup_policy: selected_choice_or(
                    dialog.choice_options_review_db_backups.get_selection(),
                    available_review_db_backup_policies(),
                    current_settings.review_db_backup_policy,
                ),
                automatic_full_deck_backup_policy: selected_choice_or(
                    dialog
                        .choice_options_automatic_full_deck_backups
                        .get_selection(),
                    available_automatic_full_deck_backup_policies(),
                    current_settings.automatic_full_deck_backup_policy,
                ),
                sound_notification_policy: selected_choice_or(
                    dialog.choice_options_sound_notifications.get_selection(),
                    available_sound_notification_policies(),
                    current_settings.sound_notification_policy,
                ),
                review_audio_policy: selected_choice_or(
                    dialog.choice_options_review_audio.get_selection(),
                    available_review_audio_policies(),
                    current_settings.review_audio_policy,
                ),
                review_audio_cues: dialog.checkbox_options_review_audio_cues.is_checked(),
                audio_generation_format: selected_choice_or(
                    dialog
                        .choice_options_audio_generation_format
                        .get_selection(),
                    available_audio_generation_formats(),
                    current_settings.audio_generation_format,
                ),
                exam_post_answer_audio_policy: selected_choice_or(
                    dialog.choice_options_exam_post_answer_audio.get_selection(),
                    available_exam_post_answer_audio_policies(),
                    current_settings.exam_post_answer_audio_policy,
                ),
                quiz_audio_policy: selected_choice_or(
                    dialog.choice_options_quiz_audio.get_selection(),
                    available_quiz_audio_policies(),
                    current_settings.quiz_audio_policy,
                ),
                calibration_recall_rule: selected_choice_or(
                    dialog
                        .choice_options_calibration_recall_rule
                        .get_selection(),
                    available_calibration_recall_rules(),
                    current_settings.calibration_recall_rule,
                ),
                minimize_to_tray: dialog.checkbox_options_minimize_to_tray.is_checked(),
            },
            sync_server_url: if sync_server_url_disabled {
                None
            } else {
                normalize_sync_server_url(Some(&sync_server_url_text))
            },
            sync_server_url_disabled,
            local_device_name: normalize_device_name(Some(
                &dialog.text_options_local_device_name.get_value(),
            )),
        })
    } else {
        None
    };
    dialog.dialog_options.destroy();
    settings
}

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

    #[test]
    fn audio_generation_format_default_is_first_option() {
        assert_eq!(
            audio_generation_format_selection_index(AudioGenerationFormat::Opus),
            Some(0)
        );
        assert_eq!(
            audio_generation_format_selection_index(AudioGenerationFormat::Wav),
            Some(1)
        );
    }

    #[test]
    fn selected_choice_or_uses_selection_when_valid_otherwise_fallback() {
        let choices = ["first", "second"];

        assert_eq!(selected_choice_or(Some(1), &choices, "fallback"), "second");
        assert_eq!(
            selected_choice_or(Some(2), &choices, "fallback"),
            "fallback"
        );
        assert_eq!(selected_choice_or(None, &choices, "fallback"), "fallback");
    }

    #[test]
    fn backup_prune_is_not_an_options_dialog_action() {
        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("prune_obsolete_deck_backups"));
        assert!(!source.contains("button_options_prune_backups"));
        assert!(!xrc.contains("button_options_prune_backups"));
    }

    #[test]
    fn options_dialog_uses_empty_static_text_cells_instead_of_redundant_checkbox_labels() {
        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!(!source.contains("label_options_review_audio_cues\n        .set_label"));
        assert!(!xrc.contains("label_options_minimize_to_tray"));
        assert!(!xrc.contains("label_options_review_audio_cues"));
        assert!(!xrc.contains("<object class=\"spacer\">"));
        assert!(xrc.contains("<object class=\"wxStaticText\">\n                <label></label>"));
        assert!(xrc.contains("<object class=\"wxCheckBox\" name=\"checkbox_options_minimize_to_tray\">\n                <label>Minimise to system tray</label>"));
        assert!(xrc.contains("<object class=\"wxCheckBox\" name=\"checkbox_options_review_audio_cues\">\n                <label>Guiding tones for automatic audio</label>"));
    }

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

        assert!(source.contains(
            "checkbox_options_minimize_to_tray\n        .set_name(minimize_to_tray_label);"
        ));
        assert!(source.contains(
            "checkbox_options_review_audio_cues\n        .set_name(review_audio_cues_label);"
        ));
    }

    #[test]
    fn options_dialog_contains_sync_settings() {
        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!(xrc.contains("name=\"label_options_sync_server_url\""));
        assert!(xrc.contains("name=\"text_options_sync_server_url\""));
        assert!(xrc.contains("name=\"label_options_local_device_name\""));
        assert!(xrc.contains("name=\"text_options_local_device_name\""));
        assert!(source.contains("options_sync_server_url_label()"));
        assert!(source.contains("text_options_sync_server_url"));
        assert!(source.contains("normalize_sync_server_url(Some("));
        assert!(source.contains("sync_server_url_text_disables_sync("));
        assert!(source.contains("options_local_device_name_label()"));
        assert!(source.contains("text_options_local_device_name"));
        assert!(source.contains("normalize_device_name(Some("));
    }
}
