use std::cell::RefCell;
use std::rc::Rc;

use wxdragon::prelude::*;

use crate::app_state::AppState;
use crate::keyboard::{
    is_edit_revealed_card_key, is_reveal_response_key, is_toggle_deferral_key, is_toggle_flag_key,
    is_toggle_review_audio_key, key_to_digit,
};
use crate::review_session::ReviewPhase;
use crate::review_ui::ReviewUi;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ReviewKeyCommand {
    ToggleFlag,
    ToggleDeferral,
    ToggleReviewAudio,
    EditRevealedCard,
    RevealResponse,
    EndCurrentPass,
    ActivateRating(usize),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ReviewKeyDisposition {
    Dispatch(ReviewKeyCommand),
    Skip,
}

pub(crate) fn install_review_key_handlers<F>(
    ui: &ReviewUi,
    state: Rc<RefCell<AppState>>,
    dispatch: F,
) where
    F: Fn(ReviewKeyCommand) -> bool + 'static,
{
    let dispatch = Rc::new(dispatch);

    bind_review_key_handler(ui.frame, Rc::clone(&state), Rc::clone(&dispatch));
    bind_review_key_handler(ui.root_panel, Rc::clone(&state), Rc::clone(&dispatch));
    bind_review_key_handler(ui.start_panel, Rc::clone(&state), Rc::clone(&dispatch));
    bind_review_key_handler(ui.cycle_summary, Rc::clone(&state), Rc::clone(&dispatch));
    bind_review_key_handler(ui.ready_cards, Rc::clone(&state), Rc::clone(&dispatch));
    bind_review_key_handler(
        ui.ready_card_text_filter,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(
        ui.ready_card_filter_choice,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(
        ui.ready_add_card_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(
        ui.ready_edit_card_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(
        ui.ready_remove_card_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(
        ui.start_cycle_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(
        ui.force_start_cycle_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(
        ui.review_flagged_cards_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(
        ui.start_exam_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(
        ui.start_quit_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(ui.prompt, Rc::clone(&state), Rc::clone(&dispatch));
    bind_review_key_handler(
        ui.reveal_response_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(
        ui.guess_flag_card_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(
        ui.guess_defer_card_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(ui.response, Rc::clone(&state), Rc::clone(&dispatch));
    bind_review_key_handler(
        ui.review_audio_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(ui.guess_panel, Rc::clone(&state), Rc::clone(&dispatch));
    bind_review_key_handler(ui.feedback_panel, Rc::clone(&state), Rc::clone(&dispatch));
    bind_review_key_handler(
        ui.feedback_edit_card_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_key_handler(
        ui.feedback_flag_card_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );

    for button in ui.guess_buttons {
        bind_review_key_handler(button, Rc::clone(&state), Rc::clone(&dispatch));
    }
    bind_review_key_handler(
        ui.guess_end_pass_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );

    for button in ui.feedback_buttons {
        bind_review_key_handler(button, Rc::clone(&state), Rc::clone(&dispatch));
    }
    bind_review_key_handler(ui.feedback_end_pass_button, state, dispatch);
}

fn bind_review_key_handler<W, F>(widget: W, state: Rc<RefCell<AppState>>, dispatch: Rc<F>)
where
    W: WxWidget + WindowEvents + Copy + 'static,
    F: Fn(ReviewKeyCommand) -> bool + 'static,
{
    widget.on_key_down(move |event| {
        if let WindowEventData::Keyboard(key_event) = event {
            let phase = state.borrow().session.phase();
            match review_key_disposition(
                phase,
                key_event.get_unicode_key(),
                key_event.get_key_code(),
                key_event.control_down(),
            ) {
                ReviewKeyDisposition::Dispatch(command) => {
                    key_event.event.skip(!dispatch(command));
                }
                ReviewKeyDisposition::Skip => key_event.event.skip(true),
            }
        }
    });
}

pub(crate) fn review_key_disposition(
    phase: ReviewPhase,
    unicode_key: Option<i32>,
    key_code: Option<i32>,
    control_down: bool,
) -> ReviewKeyDisposition {
    if matches!(phase, ReviewPhase::ReadyToStart) {
        return ReviewKeyDisposition::Skip;
    }

    if is_toggle_flag_key(unicode_key, key_code, control_down) {
        return ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ToggleFlag);
    }

    if is_toggle_deferral_key(unicode_key, key_code, control_down)
        && matches!(phase, ReviewPhase::Prompt)
    {
        return ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ToggleDeferral);
    }

    if is_toggle_review_audio_key(unicode_key, key_code, control_down) {
        return ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ToggleReviewAudio);
    }

    if is_edit_revealed_card_key(unicode_key, key_code)
        && matches!(phase, ReviewPhase::Response { .. })
    {
        return ReviewKeyDisposition::Dispatch(ReviewKeyCommand::EditRevealedCard);
    }

    if is_reveal_response_key(unicode_key, key_code) && matches!(phase, ReviewPhase::Prompt) {
        return ReviewKeyDisposition::Dispatch(ReviewKeyCommand::RevealResponse);
    }

    match key_to_digit(unicode_key, key_code) {
        Some(0) => ReviewKeyDisposition::Dispatch(ReviewKeyCommand::EndCurrentPass),
        Some(1) => ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ActivateRating(0)),
        Some(2) => ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ActivateRating(1)),
        Some(3) => ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ActivateRating(2)),
        Some(4) => ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ActivateRating(3)),
        Some(_) | None => ReviewKeyDisposition::Skip,
    }
}

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

    #[test]
    fn review_keys_skip_when_no_review_is_running() {
        assert_eq!(
            review_key_disposition(ReviewPhase::ReadyToStart, Some('1' as i32), None, false),
            ReviewKeyDisposition::Skip
        );
        assert_eq!(
            review_key_disposition(ReviewPhase::ReadyToStart, Some('f' as i32), None, true),
            ReviewKeyDisposition::Skip
        );
    }

    #[test]
    fn prompt_phase_review_keys_dispatch_prompt_actions() {
        assert_eq!(
            review_key_disposition(ReviewPhase::Prompt, Some('f' as i32), None, true),
            ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ToggleFlag)
        );
        assert_eq!(
            review_key_disposition(ReviewPhase::Prompt, Some('d' as i32), None, true),
            ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ToggleDeferral)
        );
        assert_eq!(
            review_key_disposition(ReviewPhase::Prompt, Some('p' as i32), None, true),
            ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ToggleReviewAudio)
        );
        assert_eq!(
            review_key_disposition(ReviewPhase::Prompt, Some(' ' as i32), None, false),
            ReviewKeyDisposition::Dispatch(ReviewKeyCommand::RevealResponse)
        );
        assert_eq!(
            review_key_disposition(ReviewPhase::Prompt, Some('0' as i32), None, false),
            ReviewKeyDisposition::Dispatch(ReviewKeyCommand::EndCurrentPass)
        );
        assert_eq!(
            review_key_disposition(ReviewPhase::Prompt, Some('4' as i32), None, false),
            ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ActivateRating(3))
        );
        assert_eq!(
            review_key_disposition(ReviewPhase::Prompt, Some('e' as i32), None, false),
            ReviewKeyDisposition::Skip
        );
    }

    #[test]
    fn response_phase_review_keys_dispatch_response_actions() {
        let phase = ReviewPhase::Response {
            guess: PromptGuess::ProbablyKnow,
        };

        assert_eq!(
            review_key_disposition(phase, Some('f' as i32), None, true),
            ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ToggleFlag)
        );
        assert_eq!(
            review_key_disposition(phase, Some('p' as i32), None, true),
            ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ToggleReviewAudio)
        );
        assert_eq!(
            review_key_disposition(phase, Some('e' as i32), None, false),
            ReviewKeyDisposition::Dispatch(ReviewKeyCommand::EditRevealedCard)
        );
        assert_eq!(
            review_key_disposition(phase, Some('1' as i32), None, false),
            ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ActivateRating(0))
        );
        assert_eq!(
            review_key_disposition(phase, Some(' ' as i32), None, false),
            ReviewKeyDisposition::Skip
        );
        assert_eq!(
            review_key_disposition(phase, Some('d' as i32), None, true),
            ReviewKeyDisposition::Skip
        );
    }
}
