use crate::dialog_support::{
    is_affirmative_dialog_result, is_dialog_accept_key, show_info_dialog_with_parent,
    show_yes_no_dialog_with_parent,
};
use crate::localization::{DeckPropertiesLabel as PropertyLabel, Localizer};
use crate::review_report::{
    learning_dashboard_report_file, review_report_file, save_review_report,
};
use crate::review_session::ReviewCycleSummary;
use crate::{CardDetailsUi, CardEditorUi, CycleStatsUi, DeckPropertiesEditorUi};
use cosimo::{Datum, DatumValidationError, DeckMetadata};
use std::path::Path;
use wxdragon::id::{ID_CANCEL, ID_OK};
use wxdragon::prelude::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RemoveCardConfirmation {
    Normal,
    ReversibleParent,
    Reverse,
}

pub(crate) fn show_card_details_dialog(parent: Frame, localizer: Localizer, details: &str) {
    show_text_report_dialog(
        parent,
        localizer.card_details_dialog_title(),
        localizer.card_details_dialog_status(),
        localizer.card_details_close_button(),
        details,
        None,
    );
}

pub(crate) fn show_learning_dashboard_dialog(
    parent: Frame,
    localizer: Localizer,
    dashboard: &str,
    deck_path: &Path,
    review_db_path: &Path,
    generated_at_unix_seconds: i64,
) {
    let report = learning_dashboard_report_file(
        localizer,
        deck_path,
        review_db_path,
        dashboard,
        generated_at_unix_seconds,
    );
    show_text_report_dialog(
        parent,
        localizer.learning_dashboard_dialog_title(),
        localizer.learning_dashboard_dialog_status(),
        localizer.card_details_close_button(),
        dashboard,
        Some(TextReportSave::LearningDashboard { localizer, report }),
    );
}

pub(crate) fn show_deck_properties_dialog(parent: Frame, localizer: Localizer, details: &str) {
    show_text_report_dialog(
        parent,
        localizer.deck_properties_dialog_title(),
        localizer.deck_properties_dialog_status(),
        localizer.card_details_close_button(),
        details,
        None,
    );
}

pub(crate) fn show_deck_properties_editor_dialog(
    parent: Frame,
    localizer: Localizer,
    metadata: &DeckMetadata,
) -> Option<DeckMetadata> {
    let dialog = DeckPropertiesEditorUi::new(Some(&parent), false);
    let save_id = DeckPropertiesEditorUi::xrc_id("button_deck_properties_save");
    let cancel_id = DeckPropertiesEditorUi::xrc_id("button_deck_properties_cancel");

    dialog
        .dialog_deck_properties_editor
        .set_label(localizer.deck_properties_editor_title());
    dialog
        .label_deck_properties_editor_status
        .set_label(localizer.deck_properties_editor_status());
    dialog
        .label_deck_properties_title
        .set_label(localizer.deck_properties_label(PropertyLabel::Title));
    dialog
        .label_deck_properties_author
        .set_label(localizer.deck_properties_label(PropertyLabel::Author));
    dialog
        .label_deck_properties_description
        .set_label(localizer.deck_properties_label(PropertyLabel::Description));
    dialog
        .label_deck_properties_url
        .set_label(localizer.deck_properties_label(PropertyLabel::Url));
    dialog
        .button_deck_properties_cancel
        .set_label(localizer.card_editor_cancel_button());
    dialog
        .button_deck_properties_save
        .set_label(localizer.card_editor_save_button());

    dialog
        .text_deck_properties_title
        .set_value(metadata.get("title").unwrap_or(""));
    dialog
        .text_deck_properties_author
        .set_value(metadata.get("author").unwrap_or(""));
    dialog
        .text_deck_properties_description
        .set_value(metadata.get("description").unwrap_or(""));
    dialog
        .text_deck_properties_url
        .set_value(metadata.get("url").unwrap_or(""));
    dialog.text_deck_properties_title.set_insertion_point(0);
    dialog.text_deck_properties_author.set_insertion_point(0);
    dialog
        .text_deck_properties_description
        .set_insertion_point(0);
    dialog.text_deck_properties_url.set_insertion_point(0);

    let modal_dialog = dialog.dialog_deck_properties_editor;
    let status = dialog.label_deck_properties_editor_status;
    let title = dialog.text_deck_properties_title;
    let author = dialog.text_deck_properties_author;
    let description = dialog.text_deck_properties_description;
    let url = dialog.text_deck_properties_url;
    dialog.button_deck_properties_save.on_click(move |_| {
        for text_control in [title, author, description, url] {
            if !deck_metadata_field_is_single_line(&text_control.get_value()) {
                status.set_label(localizer.deck_properties_editor_single_line_status());
                text_control.set_focus();
                return;
            }
        }
        modal_dialog.end_modal(ID_OK);
    });

    let modal_dialog = dialog.dialog_deck_properties_editor;
    dialog
        .button_deck_properties_cancel
        .on_click(move |_| modal_dialog.end_modal(ID_CANCEL));

    dialog
        .dialog_deck_properties_editor
        .set_affirmative_id(save_id);
    dialog
        .dialog_deck_properties_editor
        .set_escape_id(cancel_id);
    dialog.text_deck_properties_title.set_focus();

    let result = dialog.dialog_deck_properties_editor.show_modal();
    let saved_metadata = if is_affirmative_dialog_result(result, save_id) {
        let mut updated_metadata = metadata.clone();
        apply_deck_metadata_field(
            &mut updated_metadata,
            "title",
            &dialog.text_deck_properties_title.get_value(),
        );
        apply_deck_metadata_field(
            &mut updated_metadata,
            "author",
            &dialog.text_deck_properties_author.get_value(),
        );
        apply_deck_metadata_field(
            &mut updated_metadata,
            "description",
            &dialog.text_deck_properties_description.get_value(),
        );
        apply_deck_metadata_field(
            &mut updated_metadata,
            "url",
            &dialog.text_deck_properties_url.get_value(),
        );
        updated_metadata.ensure_v2_header();
        Some(updated_metadata)
    } else {
        None
    };
    dialog.dialog_deck_properties_editor.destroy();
    saved_metadata
}

fn deck_metadata_field_is_single_line(value: &str) -> bool {
    !value.contains('\n') && !value.contains('\r')
}

fn apply_deck_metadata_field(metadata: &mut DeckMetadata, key: &str, value: &str) {
    let value = value.trim();
    if value.is_empty() {
        metadata.remove(key);
    } else {
        metadata.set(key, value);
    }
}

enum TextReportSave {
    LearningDashboard {
        localizer: Localizer,
        report: crate::review_report::ReviewReportFile,
    },
}

impl TextReportSave {
    fn button_label(&self) -> &'static str {
        match self {
            TextReportSave::LearningDashboard { localizer, .. } => localizer.save_report_button(),
        }
    }
}

fn show_text_report_dialog(
    parent: Frame,
    title: &str,
    status: &str,
    close: &str,
    text: &str,
    save: Option<TextReportSave>,
) {
    let dialog = CardDetailsUi::new(Some(&parent), false);
    let close_id = CardDetailsUi::xrc_id("button_card_details_close");

    dialog.dialog_card_details.set_label(title);
    dialog.label_card_details_status.set_label(status);
    dialog.button_card_details_close.set_label(close);
    dialog.text_card_details.set_value(text);
    dialog.text_card_details.set_insertion_point(0);
    apply_text_report_visual_style(
        dialog.label_card_details_status,
        dialog.text_card_details,
        dialog.button_card_details_save_report,
        dialog.button_card_details_close,
    );
    if let Some(save) = save {
        dialog.button_card_details_save_report.show(true);
        dialog
            .button_card_details_save_report
            .set_label(save.button_label());
        match save {
            TextReportSave::LearningDashboard { localizer, report } => {
                let status = dialog.label_card_details_status;
                dialog
                    .button_card_details_save_report
                    .on_click(move |_| match save_review_report(&report) {
                        Ok(path) => {
                            status.set_label(&localizer.saved_learning_dashboard_report_status(
                                &path.display().to_string(),
                            ));
                        }
                        Err(error) => {
                            status.set_label(
                                &localizer.could_not_save_learning_dashboard_report_status(
                                    &report.path().display().to_string(),
                                    &error.to_string(),
                                ),
                            );
                        }
                    });
            }
        }
    } else {
        dialog.button_card_details_save_report.show(false);
    }
    dialog.button_card_details_close.on_click({
        let dialog = dialog.dialog_card_details;
        move |_| dialog.end_modal(ID_OK)
    });
    dialog.text_card_details.on_key_down({
        let dialog = dialog.dialog_card_details;
        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);
                } else {
                    key_event.event.skip(true);
                }
            }
        }
    });
    dialog.dialog_card_details.set_affirmative_id(close_id);
    dialog.dialog_card_details.set_escape_id(close_id);
    dialog.button_card_details_close.set_default();
    dialog.text_card_details.set_focus();
    dialog.panel_card_details_root.layout();

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

pub(crate) fn show_about_dialog(parent: Frame, localizer: Localizer) {
    show_info_dialog_with_parent(
        &parent,
        localizer.about_caption(),
        &localizer.about_message(),
    );
}

pub(crate) fn show_cycle_stats_dialog(
    parent: Frame,
    localizer: Localizer,
    cycle_summary: ReviewCycleSummary,
    deck_path: &Path,
    review_db_path: &Path,
) -> bool {
    let dialog = CycleStatsUi::new(Some(&parent), false);
    let summary = cycle_summary.stats.summary_text(
        localizer,
        cycle_summary.total_cards,
        cycle_summary.elapsed_seconds,
        cycle_summary.ended_early,
        cycle_summary.was_scheduled,
    );
    let continue_id = CycleStatsUi::xrc_id("button_continue");

    dialog
        .dialog_stats
        .set_label(localizer.stats_dialog_title());
    dialog
        .label_stats_status
        .set_label(localizer.stats_dialog_status());
    dialog
        .button_continue
        .set_label(localizer.continue_button());
    dialog
        .button_save_report
        .set_label(localizer.save_report_button());
    dialog.text_stats_summary.set_value(&summary);
    dialog.text_stats_summary.set_insertion_point(0);
    dialog.button_save_report.on_click({
        let status = dialog.label_stats_status;
        let cycle_summary = cycle_summary.clone();
        let deck_path = deck_path.to_path_buf();
        let review_db_path = review_db_path.to_path_buf();
        let summary = summary.clone();
        move |_| {
            let report = review_report_file(
                localizer,
                &cycle_summary,
                &deck_path,
                &review_db_path,
                &summary,
            );
            match save_review_report(&report) {
                Ok(path) => {
                    status.set_label(
                        &localizer.saved_review_report_status(&path.display().to_string()),
                    );
                }
                Err(error) => {
                    status.set_label(&localizer.could_not_save_review_report_status(
                        &report.path().display().to_string(),
                        &error.to_string(),
                    ));
                }
            }
        }
    });
    dialog.button_continue.on_click({
        let dialog = dialog.dialog_stats;
        move |_| dialog.end_modal(ID_OK)
    });
    dialog.text_stats_summary.on_key_down({
        let dialog = dialog.dialog_stats;
        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);
                } else {
                    key_event.event.skip(true);
                }
            }
        }
    });
    dialog.dialog_stats.set_affirmative_id(continue_id);
    dialog.dialog_stats.set_escape_id(continue_id);
    dialog.button_continue.set_default();
    dialog.text_stats_summary.set_focus();
    apply_text_report_visual_style(
        dialog.label_stats_status,
        dialog.text_stats_summary,
        dialog.button_save_report,
        dialog.button_continue,
    );
    dialog.panel_stats_root.layout();

    let result = dialog.dialog_stats.show_modal();
    dialog.dialog_stats.destroy();
    is_affirmative_dialog_result(result, continue_id)
}

fn apply_text_report_visual_style(
    status: StaticText,
    text: TextCtrl,
    save_button: Button,
    close_button: Button,
) {
    increase_font_to_at_least(status, 11);
    increase_font_to_at_least(text, 12);
    for button in [save_button, close_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);
    }
}

pub(crate) fn show_open_deck_dialog(
    parent: Frame,
    localizer: Localizer,
    current_deck_path: &str,
) -> Option<String> {
    let current_path = Path::new(current_deck_path);
    let default_dir = current_path
        .parent()
        .and_then(|path| path.to_str())
        .unwrap_or("");
    let default_file = current_path
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or("");
    let dialog = FileDialog::builder(&parent)
        .with_message(localizer.open_deck_dialog_message())
        .with_default_dir(default_dir)
        .with_default_file(default_file)
        .with_wildcard(localizer.open_deck_wildcard())
        // Do not add FileMustExist. Opening a missing path is how the user
        // creates a new deck through this flow.
        .with_style(FileDialogStyle::Open)
        .build();

    let selected_path = if dialog.show_modal() == ID_OK {
        dialog.get_path()
    } else {
        None
    };
    drop(dialog);
    selected_path
}

pub(crate) fn show_card_editor_dialog(
    parent: Frame,
    localizer: Localizer,
    existing_datum: Option<&Datum>,
) -> Option<Datum> {
    let dialog = CardEditorUi::new(Some(&parent), false);
    let save_id = CardEditorUi::xrc_id("button_card_save");
    let status_label = localizer.card_editor_status(existing_datum.is_some());

    dialog
        .dialog_card_editor
        .set_label(localizer.card_editor_title());
    dialog.label_card_status.set_label(status_label);
    dialog
        .label_card_prompt_heading
        .set_label(localizer.card_editor_prompt_heading());
    dialog
        .label_card_response_heading
        .set_label(localizer.card_editor_response_heading());
    dialog
        .button_card_cancel
        .set_label(localizer.card_editor_cancel_button());
    dialog
        .button_card_save
        .set_label(localizer.card_editor_save_button());
    if let Some(existing_datum) = existing_datum {
        dialog.text_card_prompt.set_value(existing_datum.prompt());
        dialog
            .text_card_response
            .set_value(existing_datum.response());
    }
    dialog.text_card_prompt.set_insertion_point(0);
    dialog.text_card_response.set_insertion_point(0);

    let modal_dialog = dialog.dialog_card_editor;
    let status = dialog.label_card_status;
    let prompt = dialog.text_card_prompt;
    let response = dialog.text_card_response;
    dialog.button_card_save.on_click(move |_| {
        let prompt_value = prompt.get_value();
        let response_value = response.get_value();
        match Datum::new(prompt_value, response_value) {
            Ok(_) => modal_dialog.end_modal(ID_OK),
            Err(DatumValidationError::EmptyPrompt) => {
                status.set_label(localizer.card_editor_empty_prompt_status());
                prompt.set_focus();
            }
            Err(DatumValidationError::EmptyResponse) => {
                status.set_label(localizer.card_editor_empty_response_status());
                response.set_focus();
            }
        }
    });

    let modal_dialog = dialog.dialog_card_editor;
    dialog
        .button_card_cancel
        .on_click(move |_| modal_dialog.end_modal(ID_CANCEL));

    dialog.dialog_card_editor.set_affirmative_id(save_id);
    dialog
        .dialog_card_editor
        .set_escape_id(CardEditorUi::xrc_id("button_card_cancel"));
    dialog.text_card_prompt.set_focus();

    let result = dialog.dialog_card_editor.show_modal();
    let saved_datum = if is_affirmative_dialog_result(result, save_id) {
        Datum::new(
            dialog.text_card_prompt.get_value(),
            dialog.text_card_response.get_value(),
        )
        .ok()
    } else {
        None
    };
    dialog.dialog_card_editor.destroy();
    saved_datum
}

pub(crate) fn confirm_remove_selected_card_dialog(
    parent: Frame,
    localizer: Localizer,
    prompt_preview: &str,
    confirmation: RemoveCardConfirmation,
) -> bool {
    let message = match confirmation {
        RemoveCardConfirmation::Normal => localizer.remove_selected_card_message(prompt_preview),
        RemoveCardConfirmation::ReversibleParent => {
            localizer.remove_reversible_parent_card_message(prompt_preview)
        }
        RemoveCardConfirmation::Reverse => localizer.remove_reverse_card_message(prompt_preview),
    };
    show_yes_no_dialog_with_parent(&parent, localizer.remove_card_caption(), &message)
}

pub(crate) fn confirm_make_eligible_cards_reversible_dialog(
    parent: Frame,
    localizer: Localizer,
    card_count: usize,
    overwrites_review_db_backup: bool,
) -> bool {
    show_yes_no_dialog_with_parent(
        &parent,
        localizer.make_eligible_cards_reversible_caption(),
        &localizer.make_eligible_cards_reversible_message(card_count, overwrites_review_db_backup),
    )
}

pub(crate) fn confirm_undo_latest_bulk_reversal_dialog(
    parent: Frame,
    localizer: Localizer,
    card_count: usize,
    overwrites_review_db_backup: bool,
) -> bool {
    show_yes_no_dialog_with_parent(
        &parent,
        localizer.undo_last_bulk_reversal_caption(),
        &localizer.undo_last_bulk_reversal_message(card_count, overwrites_review_db_backup),
    )
}

pub(crate) fn confirm_scrub_review_database_dialog(
    parent: Frame,
    localizer: Localizer,
    backups_disabled: bool,
    overwrites_review_db_backup: bool,
) -> bool {
    show_yes_no_dialog_with_parent(
        &parent,
        localizer.scrub_review_database_caption(),
        &localizer.scrub_review_database_message(backups_disabled, overwrites_review_db_backup),
    )
}

pub(crate) fn confirm_reclaim_database_space_dialog(
    parent: Frame,
    localizer: Localizer,
    backups_disabled: bool,
) -> bool {
    show_yes_no_dialog_with_parent(
        &parent,
        localizer.reclaim_database_space_caption(),
        &localizer.reclaim_database_space_message(backups_disabled),
    )
}

pub(crate) fn confirm_create_deck_dialog(
    parent: Frame,
    localizer: Localizer,
    deck_path: &str,
) -> bool {
    show_yes_no_dialog_with_parent(
        &parent,
        localizer.create_deck_caption(),
        &localizer.create_deck_message(deck_path),
    )
}

#[cfg(test)]
mod tests {
    #[test]
    fn stats_continue_button_is_configured_as_default() {
        let source = include_str!("review_dialogs.rs");

        assert!(source.contains("dialog.button_continue.set_default();"));
    }

    #[test]
    fn stats_summary_enter_key_is_handled_as_continue() {
        let source = include_str!("review_dialogs.rs");

        assert!(source.contains("dialog.text_stats_summary.on_key_down("));
        assert!(source.contains("is_dialog_accept_key("));
    }
}
