use crate::app_config::QuizAudioPolicy;
use crate::dialog_support::{
    end_modal_once, is_affirmative_dialog_result, is_alt_n_key, is_dialog_accept_key,
};
use crate::keyboard::{is_toggle_review_audio_key, key_to_digit};
use crate::localization::Localizer;
use crate::quiz_mode::{
    QuizAnswer, QuizChoice, QuizChoiceIndex, QuizQuestion, QuizSizeMode, QuizSizeModeSelection,
    QuizSizeRequest, QuizStudyDirection, QuizStudyDirectionSelection,
};
use crate::review_audio::{
    ReviewAudioFile, ReviewAudioPlayer, ReviewAudioSide, audio_file_for_datum_key,
};
use crate::review_report::{quiz_report_file, save_review_report};
use crate::{ExamGradeUi, ExamSummaryUi, QuizQuestionUi, QuizSetupUi};
use std::cell::{Cell, RefCell};
use std::path::Path;
use std::rc::Rc;
use wxdragon::id::{ID_CANCEL, ID_OK};
use wxdragon::prelude::*;

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum QuizQuestionOutcome {
    Submitted(QuizAnswer),
    SubmittedAndEnded(QuizAnswer),
    Ended,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum QuizReviewOutcome {
    Next,
    Summary,
}

pub(crate) struct QuizAnswerReviewDialog<'a> {
    pub(crate) parent: Frame,
    pub(crate) localizer: Localizer,
    pub(crate) answer: &'a QuizAnswer,
    pub(crate) current_answer: usize,
    pub(crate) total_answers: usize,
    pub(crate) deck_path: &'a Path,
    pub(crate) quiz_audio_policy: QuizAudioPolicy,
    pub(crate) guiding_tones: bool,
}

pub(crate) fn show_quiz_setup_dialog(
    parent: Frame,
    localizer: Localizer,
    active_card_count: usize,
    show_study_direction: bool,
) -> Option<QuizSizeRequest> {
    let dialog = QuizSetupUi::new(Some(&parent), false);
    let start_id = QuizSetupUi::xrc_id("button_quiz_setup_start");
    let cancel_id = QuizSetupUi::xrc_id("button_quiz_setup_cancel");

    dialog
        .dialog_quiz_setup
        .set_label(localizer.quiz_setup_dialog_title());
    dialog
        .label_quiz_setup_status
        .set_label(&localizer.quiz_setup_status(active_card_count));
    dialog
        .label_quiz_size_mode
        .set_label(localizer.quiz_size_mode_label());
    dialog
        .label_quiz_study_direction
        .set_label(localizer.quiz_study_direction_label());
    dialog
        .label_quiz_size_value
        .set_label(localizer.quiz_size_value_label());
    dialog
        .button_quiz_setup_cancel
        .set_label(localizer.options_cancel_button());
    dialog
        .button_quiz_setup_start
        .set_label(localizer.quiz_start_button());
    dialog.choice_quiz_size_mode.clear();
    dialog
        .choice_quiz_size_mode
        .append(localizer.quiz_size_question_count_choice());
    dialog
        .choice_quiz_size_mode
        .append(localizer.quiz_size_percentage_choice());
    dialog
        .choice_quiz_size_mode
        .set_selection(QuizSizeMode::QuestionCount.selection_index().index());
    dialog.choice_quiz_study_direction.clear();
    dialog
        .choice_quiz_study_direction
        .append(localizer.quiz_study_direction_standard_choice());
    dialog
        .choice_quiz_study_direction
        .append(localizer.quiz_study_direction_reverse_choice());
    dialog
        .choice_quiz_study_direction
        .append(localizer.quiz_study_direction_both_choice());
    dialog
        .choice_quiz_study_direction
        .set_selection(QuizStudyDirection::Both.selection_index().index());
    dialog.label_quiz_study_direction.show(show_study_direction);
    dialog
        .choice_quiz_study_direction
        .show(show_study_direction);
    dialog
        .text_quiz_size_value
        .set_value(&usize::min(20, active_card_count).max(1).to_string());
    dialog.choice_quiz_size_mode.on_selection_changed({
        let value = dialog.text_quiz_size_value;
        move |_| {
            value.set_value("");
        }
    });

    let modal_dialog = dialog.dialog_quiz_setup;
    dialog
        .button_quiz_setup_cancel
        .on_click(move |_| modal_dialog.end_modal(ID_CANCEL));

    let modal_dialog = dialog.dialog_quiz_setup;
    dialog
        .button_quiz_setup_start
        .on_click(move |_| modal_dialog.end_modal(ID_OK));

    dialog.text_quiz_size_value.on_key_down({
        let modal_dialog = dialog.dialog_quiz_setup;
        move |event| {
            if let WindowEventData::Keyboard(key_event) = event {
                if is_dialog_accept_key(key_event.get_unicode_key(), key_event.get_key_code()) {
                    modal_dialog.end_modal(ID_OK);
                    key_event.event.skip(false);
                } else {
                    key_event.event.skip(true);
                }
            }
        }
    });

    dialog.dialog_quiz_setup.set_affirmative_id(start_id);
    dialog.dialog_quiz_setup.set_escape_id(cancel_id);
    dialog.button_quiz_setup_start.set_default();
    dialog.text_quiz_size_value.set_focus();
    dialog.panel_quiz_setup_root.layout();

    let outcome = loop {
        let result = dialog.dialog_quiz_setup.show_modal();
        if !is_affirmative_dialog_result(result, start_id) {
            break None;
        }

        let mode = QuizSizeMode::from_selection(QuizSizeModeSelection::from_wx_selection(
            dialog.choice_quiz_size_mode.get_selection(),
        ));
        let direction = if show_study_direction {
            QuizStudyDirection::from_selection(QuizStudyDirectionSelection::from_wx_selection(
                dialog.choice_quiz_study_direction.get_selection(),
            ))
        } else {
            QuizStudyDirection::Both
        };
        let value_text = dialog.text_quiz_size_value.get_value();
        let Some(value) = value_text
            .trim()
            .parse::<usize>()
            .ok()
            .filter(|value| *value > 0)
        else {
            dialog
                .label_quiz_setup_status
                .set_label(localizer.quiz_invalid_size_status());
            dialog.text_quiz_size_value.set_focus();
            continue;
        };
        if mode == QuizSizeMode::Percentage && value > 100 {
            dialog
                .label_quiz_setup_status
                .set_label(localizer.quiz_invalid_percentage_status());
            dialog.text_quiz_size_value.set_focus();
            continue;
        }

        break Some(QuizSizeRequest::with_direction(mode, value, direction));
    };
    dialog.dialog_quiz_setup.destroy();
    outcome
}

pub(crate) fn show_quiz_question_dialog(
    parent: Frame,
    localizer: Localizer,
    question: &QuizQuestion,
    current_question: usize,
    total_questions: usize,
    deck_path: &Path,
    quiz_audio_policy: QuizAudioPolicy,
) -> QuizQuestionOutcome {
    let dialog = QuizQuestionUi::new(Some(&parent), false);
    let submit_id = QuizQuestionUi::xrc_id("button_quiz_submit");
    let end_id = QuizQuestionUi::xrc_id("button_quiz_end");

    dialog
        .dialog_quiz_question
        .set_label(localizer.quiz_mode_dialog_title());
    let question_status =
        localizer.quiz_question_status(current_question, total_questions, question.prompt());
    dialog
        .label_quiz_question_status
        .set_label(&question_status);
    dialog
        .label_quiz_prompt_heading
        .set_label(localizer.prompt_heading());
    dialog
        .label_quiz_choices_heading
        .set_label(localizer.quiz_choices_heading());
    dialog
        .button_quiz_end
        .set_label(localizer.end_quiz_button());
    dialog
        .button_quiz_submit
        .set_label(localizer.submit_quiz_answer_button());
    dialog.text_quiz_prompt.set_value(question.prompt());
    dialog.text_quiz_prompt.set_insertion_point(0);
    dialog.list_quiz_choices.clear();
    for (index, choice) in question.choices().iter().enumerate() {
        dialog
            .list_quiz_choices
            .append(&format!("{}. {}", index + 1, choice.response()));
    }
    dialog
        .list_quiz_choices
        .set_name(localizer.quiz_choices_heading());

    let modal_dialog = dialog.dialog_quiz_question;
    dialog
        .button_quiz_end
        .on_click(move |_| modal_dialog.end_modal(ID_CANCEL));

    let modal_dialog = dialog.dialog_quiz_question;
    dialog
        .button_quiz_submit
        .on_click(move |_| modal_dialog.end_modal(ID_OK));

    let audio_controls = configure_quiz_question_audio(
        dialog.list_quiz_choices,
        dialog.label_quiz_question_status,
        localizer,
        deck_path,
        question,
        quiz_audio_policy,
    );
    let choice_key_audio_controls = audio_controls
        .clone()
        .filter(|_| quiz_audio_policy.includes_choices());
    bind_quiz_choice_keys(
        dialog.list_quiz_choices,
        dialog.dialog_quiz_question,
        choice_key_audio_controls,
        dialog.label_quiz_question_status,
        localizer,
    );

    dialog.dialog_quiz_question.set_affirmative_id(submit_id);
    dialog.dialog_quiz_question.set_escape_id(end_id);
    dialog.button_quiz_submit.set_default();
    dialog.list_quiz_choices.set_focus();
    dialog.panel_quiz_question_root.layout();

    let outcome = loop {
        let result = dialog.dialog_quiz_question.show_modal();
        let selection =
            QuizChoiceIndex::from_wx_selection(dialog.list_quiz_choices.get_selection());
        if result == ID_OK || result == submit_id {
            let Some(selection) = selection else {
                dialog
                    .label_quiz_question_status
                    .set_label(localizer.quiz_choose_answer_status());
                dialog.list_quiz_choices.set_focus();
                continue;
            };
            break QuizQuestionOutcome::Submitted(QuizAnswer::new(question.clone(), selection));
        }
        if let Some(selection) = selection {
            break QuizQuestionOutcome::SubmittedAndEnded(QuizAnswer::new(
                question.clone(),
                selection,
            ));
        }
        break QuizQuestionOutcome::Ended;
    };
    stop_quiz_question_audio(&audio_controls);
    dialog.dialog_quiz_question.destroy();
    outcome
}

fn bind_quiz_choice_keys(
    choices: ListBox,
    dialog: Dialog,
    audio_controls: Option<QuizQuestionAudioControls>,
    status: StaticText,
    localizer: Localizer,
) {
    choices.on_key_down(move |event| {
        if let WindowEventData::Keyboard(key_event) = event {
            if is_dialog_accept_key(key_event.get_unicode_key(), key_event.get_key_code()) {
                dialog.end_modal(ID_OK);
                key_event.event.skip(false);
                return;
            }
            if let Some(selection) =
                key_to_digit(key_event.get_unicode_key(), key_event.get_key_code())
                    .and_then(QuizChoiceIndex::from_one_based_shortcut_digit)
            {
                choices.set_selection(selection.index_u32(), true);
                if let Some(controls) = &audio_controls {
                    play_quiz_choice_audio(controls, selection, status, localizer);
                }
                key_event.event.skip(false);
                return;
            }
            key_event.event.skip(true);
        }
    });
}

pub(crate) fn show_quiz_answer_review_dialog(
    request: QuizAnswerReviewDialog<'_>,
) -> QuizReviewOutcome {
    let QuizAnswerReviewDialog {
        parent,
        localizer,
        answer,
        current_answer,
        total_answers,
        deck_path,
        quiz_audio_policy,
        guiding_tones,
    } = request;
    let dialog = ExamGradeUi::new(Some(&parent), false);
    let next_id = ExamGradeUi::xrc_id("button_exam_grade_yes");
    let selected_response = answer
        .selected_response()
        .unwrap_or_else(|| localizer.no_answer_entered());

    dialog
        .dialog_exam_grade
        .set_label(localizer.quiz_review_caption());
    dialog
        .label_exam_grade_status
        .set_label(&localizer.quiz_review_status(
            current_answer,
            total_answers,
            answer.is_correct(),
        ));
    dialog
        .label_exam_grade_prompt_heading
        .set_label(localizer.prompt_heading());
    dialog
        .label_exam_grade_answer_heading
        .set_label(localizer.quiz_selected_answer_heading());
    dialog
        .label_exam_grade_expected_heading
        .set_label(localizer.quiz_correct_answer_heading());
    dialog
        .button_exam_grade_no
        .set_label(localizer.exam_summary_button());
    dialog
        .button_exam_grade_yes
        .set_label(localizer.exam_next_button());
    dialog
        .text_exam_grade_prompt
        .set_value(answer.question().prompt());
    dialog.text_exam_grade_prompt.set_insertion_point(0);
    dialog.text_exam_grade_answer.set_value(selected_response);
    dialog.text_exam_grade_answer.set_insertion_point(0);
    let expected_response = quiz_review_expected_response(answer);
    dialog
        .label_exam_grade_expected_heading
        .show(expected_response.is_some());
    dialog
        .text_exam_grade_expected
        .set_value(expected_response.unwrap_or(""));
    dialog
        .text_exam_grade_expected
        .show(expected_response.is_some());
    dialog.text_exam_grade_expected.set_insertion_point(0);
    let audio_controls = configure_quiz_review_audio(
        dialog.label_exam_grade_status,
        localizer,
        quiz_audio_policy,
        if guiding_tones {
            Some(answer.is_correct())
        } else {
            None
        },
        audio_file_for_datum_key(
            deck_path,
            answer.question().datum_key(),
            ReviewAudioSide::Prompt,
        ),
        audio_file_for_datum_key(
            deck_path,
            answer.question().datum_key(),
            ReviewAudioSide::Response,
        ),
    );

    let modal_active = Rc::new(Cell::new(true));
    let modal_dialog = dialog.dialog_exam_grade;
    let modal_active_copy = Rc::clone(&modal_active);
    dialog.button_exam_grade_no.on_click(move |_| {
        end_modal_once(modal_dialog, &modal_active_copy, ID_CANCEL);
    });

    let modal_dialog = dialog.dialog_exam_grade;
    let modal_active_copy = Rc::clone(&modal_active);
    dialog.button_exam_grade_yes.on_click(move |_| {
        end_modal_once(modal_dialog, &modal_active_copy, ID_OK);
    });
    bind_quiz_review_shortcuts(
        dialog.text_exam_grade_prompt,
        dialog.dialog_exam_grade,
        Rc::clone(&modal_active),
        audio_controls.clone(),
        dialog.label_exam_grade_status,
        localizer,
    );
    bind_quiz_review_shortcuts(
        dialog.text_exam_grade_answer,
        dialog.dialog_exam_grade,
        Rc::clone(&modal_active),
        audio_controls.clone(),
        dialog.label_exam_grade_status,
        localizer,
    );
    bind_quiz_review_shortcuts(
        dialog.text_exam_grade_expected,
        dialog.dialog_exam_grade,
        Rc::clone(&modal_active),
        audio_controls.clone(),
        dialog.label_exam_grade_status,
        localizer,
    );
    bind_quiz_review_shortcuts(
        dialog.button_exam_grade_no,
        dialog.dialog_exam_grade,
        Rc::clone(&modal_active),
        audio_controls.clone(),
        dialog.label_exam_grade_status,
        localizer,
    );
    bind_quiz_review_shortcuts(
        dialog.button_exam_grade_yes,
        dialog.dialog_exam_grade,
        Rc::clone(&modal_active),
        audio_controls.clone(),
        dialog.label_exam_grade_status,
        localizer,
    );

    dialog.button_exam_grade_yes.set_default();
    dialog.text_exam_grade_prompt.set_focus();
    dialog.panel_exam_grade_root.layout();

    let result = dialog.dialog_exam_grade.show_modal();
    modal_active.set(false);
    let outcome = if result == ID_OK || result == next_id {
        QuizReviewOutcome::Next
    } else {
        QuizReviewOutcome::Summary
    };
    stop_quiz_review_audio(&audio_controls);
    dialog.dialog_exam_grade.destroy();
    outcome
}

fn quiz_review_expected_response(answer: &QuizAnswer) -> Option<&str> {
    if answer.is_correct() {
        None
    } else {
        Some(answer.question().correct_response())
    }
}

#[derive(Clone)]
struct QuizQuestionAudioControls {
    player: Rc<RefCell<ReviewAudioPlayer>>,
    choice_audio_files: Rc<Vec<Option<ReviewAudioFile>>>,
}

type QuizReviewAudioControls = (Rc<RefCell<ReviewAudioPlayer>>, Vec<ReviewAudioFile>);

fn configure_quiz_question_audio(
    choices: ListBox,
    status: StaticText,
    localizer: Localizer,
    deck_path: &Path,
    question: &QuizQuestion,
    policy: QuizAudioPolicy,
) -> Option<QuizQuestionAudioControls> {
    let prompt_audio =
        audio_file_for_datum_key(deck_path, question.datum_key(), ReviewAudioSide::Prompt);
    let choice_audio_files: Vec<_> = question
        .choices()
        .iter()
        .map(|choice| quiz_choice_audio_file(deck_path, choice))
        .collect();
    if prompt_audio.is_none() && !choice_audio_files.iter().any(Option::is_some) {
        return None;
    }

    let controls = QuizQuestionAudioControls {
        player: Rc::new(RefCell::new(ReviewAudioPlayer::default())),
        choice_audio_files: Rc::new(choice_audio_files),
    };
    if policy.includes_prompt()
        && let Some(audio_file) = prompt_audio
    {
        play_quiz_audio_file(&controls.player, status, localizer, audio_file);
    }
    if policy.includes_choices() && controls.choice_audio_files.iter().any(Option::is_some) {
        choices.on_selection_changed({
            let controls = controls.clone();
            move |event| {
                if let Some(selection) =
                    QuizChoiceIndex::from_wx_event_selection(event.get_selection())
                {
                    play_quiz_choice_audio(&controls, selection, status, localizer);
                }
            }
        });
        choices.on_kill_focus({
            let player = Rc::clone(&controls.player);
            move |event| {
                player.borrow_mut().stop();
                event.skip(true);
            }
        });
    }

    Some(controls)
}

fn quiz_choice_audio_file(deck_path: &Path, choice: &QuizChoice) -> Option<ReviewAudioFile> {
    audio_file_for_datum_key(deck_path, choice.datum_key(), ReviewAudioSide::Response)
}

fn play_quiz_choice_audio(
    controls: &QuizQuestionAudioControls,
    selection: QuizChoiceIndex,
    status: StaticText,
    localizer: Localizer,
) {
    if let Some(Some(audio_file)) = controls.choice_audio_files.get(selection.index_usize()) {
        play_quiz_audio_file(&controls.player, status, localizer, audio_file.clone());
    } else {
        controls.player.borrow_mut().stop();
    }
}

fn stop_quiz_question_audio(controls: &Option<QuizQuestionAudioControls>) {
    if let Some(controls) = controls {
        controls.player.borrow_mut().stop();
    }
}

fn configure_quiz_review_audio(
    status: StaticText,
    localizer: Localizer,
    policy: QuizAudioPolicy,
    result_cue: Option<bool>,
    prompt_audio: Option<ReviewAudioFile>,
    response_audio: Option<ReviewAudioFile>,
) -> Option<QuizReviewAudioControls> {
    let manual_audio_files = quiz_audio_files(prompt_audio.clone(), response_audio.clone());
    if manual_audio_files.is_empty() && result_cue.is_none() {
        return None;
    }

    let player = Rc::new(RefCell::new(ReviewAudioPlayer::default()));
    let automatic_audio_files =
        automatic_quiz_review_audio_files(policy, prompt_audio, response_audio);
    if result_cue.is_some() || !automatic_audio_files.is_empty() {
        play_quiz_review_audio_files(
            &player,
            status,
            localizer,
            automatic_audio_files,
            result_cue,
        );
    }

    Some((player, manual_audio_files))
}

fn quiz_audio_files(
    prompt_audio: Option<ReviewAudioFile>,
    response_audio: Option<ReviewAudioFile>,
) -> Vec<ReviewAudioFile> {
    [prompt_audio, response_audio]
        .into_iter()
        .flatten()
        .collect()
}

fn automatic_quiz_review_audio_files(
    policy: QuizAudioPolicy,
    prompt_audio: Option<ReviewAudioFile>,
    response_audio: Option<ReviewAudioFile>,
) -> Vec<ReviewAudioFile> {
    match policy {
        QuizAudioPolicy::Off => Vec::new(),
        QuizAudioPolicy::Prompt => prompt_audio.into_iter().collect(),
        QuizAudioPolicy::Choices => response_audio.into_iter().collect(),
        QuizAudioPolicy::Both => quiz_audio_files(prompt_audio, response_audio),
    }
}

fn play_quiz_audio_file(
    player: &Rc<RefCell<ReviewAudioPlayer>>,
    status: StaticText,
    localizer: Localizer,
    audio_file: ReviewAudioFile,
) {
    let play_result = { player.borrow_mut().play_audio_file(audio_file) };
    if let Err(path) = play_result {
        status.set_label(&localizer.could_not_play_review_audio(&path.display().to_string()));
    }
}

fn play_quiz_review_audio_files(
    player: &Rc<RefCell<ReviewAudioPlayer>>,
    status: StaticText,
    localizer: Localizer,
    audio_files: Vec<ReviewAudioFile>,
    result_cue: Option<bool>,
) {
    let play_result = {
        player
            .borrow_mut()
            .play_quiz_review_audio(audio_files, result_cue)
    };
    if let Err(path) = play_result {
        status.set_label(&localizer.could_not_play_review_audio(&path.display().to_string()));
    }
}

fn toggle_quiz_audio_files(
    controls: &QuizReviewAudioControls,
    status: StaticText,
    localizer: Localizer,
) {
    let (player, audio_files) = controls;
    let toggle_result = { player.borrow_mut().toggle_audio_files(audio_files.clone()) };
    if let Err(path) = toggle_result {
        status.set_label(&localizer.could_not_play_review_audio(&path.display().to_string()));
    }
}

fn stop_quiz_review_audio(controls: &Option<QuizReviewAudioControls>) {
    if let Some((player, _)) = controls {
        player.borrow_mut().stop();
    }
}

fn handle_quiz_audio_shortcut(
    unicode_key: Option<i32>,
    key_code: Option<i32>,
    control_down: bool,
    controls: &Option<QuizReviewAudioControls>,
    status: StaticText,
    localizer: Localizer,
) -> bool {
    if !is_toggle_review_audio_key(unicode_key, key_code, control_down) {
        return false;
    }

    if let Some(controls) = controls {
        toggle_quiz_audio_files(controls, status, localizer);
    }
    true
}

fn bind_quiz_review_shortcuts<W>(
    widget: W,
    modal_dialog: Dialog,
    modal_active: Rc<Cell<bool>>,
    controls: Option<QuizReviewAudioControls>,
    status: StaticText,
    localizer: Localizer,
) where
    W: WxWidget + WindowEvents + Copy + 'static,
{
    widget.on_key_down(move |event| {
        if let WindowEventData::Keyboard(key_event) = event {
            if handle_quiz_audio_shortcut(
                key_event.get_unicode_key(),
                key_event.get_key_code(),
                key_event.control_down(),
                &controls,
                status,
                localizer,
            ) {
                key_event.event.skip(false);
            } else if is_alt_n_key(
                key_event.get_unicode_key(),
                key_event.get_key_code(),
                key_event.alt_down(),
            ) {
                end_modal_once(modal_dialog, &modal_active, ID_OK);
                key_event.event.skip(false);
            } else {
                key_event.event.skip(true);
            }
        }
    });
}

pub(crate) fn show_quiz_summary_dialog(
    parent: Frame,
    localizer: Localizer,
    summary_text: &str,
    deck_path: &Path,
    review_db_path: &Path,
    started_at_unix_seconds: i64,
    finished_at_unix_seconds: i64,
) {
    let dialog = ExamSummaryUi::new(Some(&parent), false);
    let ok_id = ExamSummaryUi::xrc_id("button_exam_summary_ok");

    dialog
        .dialog_exam_summary
        .set_label(localizer.quiz_summary_caption());
    dialog
        .label_exam_summary_status
        .set_label(localizer.quiz_summary_caption());
    dialog.text_exam_summary.set_value(summary_text);
    dialog.text_exam_summary.set_insertion_point(0);
    dialog
        .button_exam_summary_save_report
        .set_label(localizer.save_report_button());
    dialog.button_exam_summary_save_report.on_click({
        let status = dialog.label_exam_summary_status;
        let deck_path = deck_path.to_path_buf();
        let review_db_path = review_db_path.to_path_buf();
        let summary_text = summary_text.to_string();
        move |_| {
            let report = quiz_report_file(
                localizer,
                &deck_path,
                &review_db_path,
                started_at_unix_seconds,
                finished_at_unix_seconds,
                &summary_text,
            );
            match save_review_report(&report) {
                Ok(path) => {
                    status.set_label(
                        &localizer.saved_quiz_report_status(&path.display().to_string()),
                    );
                }
                Err(error) => {
                    status.set_label(&localizer.could_not_save_quiz_report_status(
                        &report.path().display().to_string(),
                        &error.to_string(),
                    ));
                }
            }
        }
    });
    dialog
        .button_exam_summary_ok
        .set_label(localizer.options_ok_button());

    let modal_dialog = dialog.dialog_exam_summary;
    dialog
        .button_exam_summary_ok
        .on_click(move |_| modal_dialog.end_modal(ID_OK));
    dialog.text_exam_summary.on_key_down({
        let modal_dialog = dialog.dialog_exam_summary;
        move |event| {
            if let WindowEventData::Keyboard(key_event) = event {
                if is_dialog_accept_key(key_event.get_unicode_key(), key_event.get_key_code()) {
                    modal_dialog.end_modal(ID_OK);
                    key_event.event.skip(false);
                } else {
                    key_event.event.skip(true);
                }
            }
        }
    });

    dialog.dialog_exam_summary.set_affirmative_id(ok_id);
    dialog.button_exam_summary_ok.set_default();
    dialog.text_exam_summary.set_focus();
    apply_quiz_summary_visual_style(
        dialog.label_exam_summary_status,
        dialog.text_exam_summary,
        dialog.button_exam_summary_save_report,
        dialog.button_exam_summary_ok,
    );
    dialog.panel_exam_summary_root.layout();

    let _ = dialog.dialog_exam_summary.show_modal();
    dialog.dialog_exam_summary.destroy();
}

fn apply_quiz_summary_visual_style(
    status: StaticText,
    summary: TextCtrl,
    save_button: Button,
    ok_button: Button,
) {
    increase_font_to_at_least(status, 11);
    increase_font_to_at_least(summary, 12);
    for button in [save_button, ok_button] {
        increase_font_to_at_least(button, 11);
        button.set_min_size(Size::new(120, 40));
    }
}

fn increase_font_to_at_least<W>(widget: W, minimum_point_size: i32)
where
    W: WxWidget,
{
    let Some(mut font) = widget.get_font() else {
        return;
    };
    let point_size = font.get_point_size();
    if point_size > 0 && point_size < minimum_point_size {
        font.set_point_size(minimum_point_size);
        widget.set_font(&font);
    }
}

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

    fn quiz_answer(selected_choice_index: usize) -> QuizAnswer {
        QuizAnswer::new(
            QuizQuestion::new(
                DatumKey::for_card_id(1),
                DatumKey::for_card_id(1),
                "one",
                "uno",
                vec![
                    QuizChoice::new(DatumKey::for_card_id(2), "dos"),
                    QuizChoice::new(DatumKey::for_card_id(1), "uno"),
                    QuizChoice::new(DatumKey::for_card_id(3), "tres"),
                    QuizChoice::new(DatumKey::for_card_id(4), "cuatro"),
                ],
                QuizChoiceIndex::new(1),
            ),
            QuizChoiceIndex::new(selected_choice_index),
        )
    }

    #[test]
    fn quiz_review_omits_duplicate_correct_response_for_correct_answers() {
        let correct = quiz_answer(1);
        let incorrect = quiz_answer(0);

        assert_eq!(quiz_review_expected_response(&correct), None);
        assert_eq!(quiz_review_expected_response(&incorrect), Some("uno"));
    }

    #[test]
    fn quiz_review_source_binds_alt_n_to_next_on_review_controls() {
        let source = include_str!("quiz_dialog.rs")
            .split("#[cfg(test)]")
            .next()
            .expect("runtime source before tests");
        let review_start = source
            .find("pub(crate) fn show_quiz_answer_review_dialog")
            .expect("review dialog function");
        let summary_start = source[review_start..]
            .find("pub(crate) fn show_quiz_summary_dialog")
            .expect("next function after review dialog");
        let review_source = &source[review_start..review_start + summary_start];

        assert!(review_source.contains("bind_quiz_review_shortcuts"));
        assert!(review_source.contains("dialog.text_exam_grade_prompt"));
        assert!(review_source.contains("dialog.text_exam_grade_answer"));
        assert!(review_source.contains("dialog.text_exam_grade_expected"));
        assert!(source.contains("is_alt_n_key("));
        assert!(source.contains("is_toggle_review_audio_key("));
        assert!(review_source.contains("let modal_active = Rc::new(Cell::new(true));"));
        assert!(review_source.contains("end_modal_once(modal_dialog, &modal_active_copy, ID_OK);"));
        assert!(review_source.contains("modal_active.set(false);"));
        assert!(!review_source.contains("set_affirmative_id"));
        assert!(!review_source.contains("set_escape_id"));
    }

    #[test]
    fn quiz_review_source_plays_result_cues_only_when_guiding_tones_are_enabled() {
        let source = include_str!("quiz_dialog.rs")
            .split("#[cfg(test)]")
            .next()
            .expect("runtime source before tests");
        let review_start = source
            .find("pub(crate) fn show_quiz_answer_review_dialog")
            .expect("review dialog function");
        let summary_start = source[review_start..]
            .find("pub(crate) fn show_quiz_summary_dialog")
            .expect("next function after review dialog");
        let review_source = &source[review_start..review_start + summary_start];

        assert!(source.contains("pub(crate) guiding_tones: bool"));
        assert!(review_source.contains("if guiding_tones"));
        assert!(review_source.contains("Some(answer.is_correct())"));
        assert!(source.contains("play_quiz_review_audio("));
    }

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

        assert!(source.contains("configure_quiz_question_audio"));
        assert!(source.contains("choices.on_selection_changed"));
        assert!(source.contains("play_quiz_choice_audio"));
        assert!(source.contains("choices.on_kill_focus"));
        assert!(source.contains("quiz_choice_audio_file"));
        assert!(source.contains("ReviewAudioSide::Response"));
    }

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

        assert!(source.contains("show_study_direction"));
        assert!(source.contains("label_quiz_study_direction"));
        assert!(source.contains("choice_quiz_study_direction"));
        assert!(source.contains("QuizStudyDirection::Both"));
    }

    #[test]
    fn quiz_summary_source_exposes_save_report_button() {
        let source = include_str!("quiz_dialog.rs")
            .split("#[cfg(test)]")
            .next()
            .expect("runtime source before tests");
        let summary_start = source
            .find("pub(crate) fn show_quiz_summary_dialog")
            .expect("summary dialog function");
        let summary_source = &source[summary_start..];

        assert!(summary_source.contains("button_exam_summary_save_report"));
        assert!(summary_source.contains("quiz_report_file("));
        assert!(summary_source.contains("save_review_report(&report)"));
        assert!(summary_source.contains("saved_quiz_report_status"));
        assert!(summary_source.contains("could_not_save_quiz_report_status"));
        assert!(!summary_source.contains("button_exam_summary_save_report.show(false)"));
    }

    #[test]
    fn quiz_summary_enter_key_is_handled_as_ok() {
        let source = include_str!("quiz_dialog.rs")
            .split("#[cfg(test)]")
            .next()
            .expect("runtime source before tests");
        let summary_start = source
            .find("pub(crate) fn show_quiz_summary_dialog")
            .expect("summary dialog function");
        let style_start = source[summary_start..]
            .find("fn apply_quiz_summary_visual_style")
            .expect("next function after summary dialog");
        let summary_source = &source[summary_start..summary_start + style_start];

        assert!(summary_source.contains("dialog.text_exam_summary.on_key_down("));
        assert!(summary_source.contains("is_dialog_accept_key("));
        assert!(summary_source.contains("modal_dialog.end_modal(ID_OK);"));
    }
}
