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;
use cosimo::{PromptGuess, RecallFeedback};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RatingButton {
    One,
    Two,
    Three,
    Four,
}

impl RatingButton {
    fn from_digit(digit: u8) -> Option<Self> {
        match digit {
            1 => Some(Self::One),
            2 => Some(Self::Two),
            3 => Some(Self::Three),
            4 => Some(Self::Four),
            _ => None,
        }
    }

    pub(crate) fn prompt_guess(self) -> PromptGuess {
        match self {
            Self::One => PromptGuess::DontKnow,
            Self::Two => PromptGuess::ProbablyDontKnow,
            Self::Three => PromptGuess::ProbablyKnow,
            Self::Four => PromptGuess::CertainlyKnow,
        }
    }

    pub(crate) fn index(self) -> usize {
        match self {
            Self::One => 0,
            Self::Two => 1,
            Self::Three => 2,
            Self::Four => 3,
        }
    }

    pub(crate) fn recall_feedback(self) -> RecallFeedback {
        match self {
            Self::One => RecallFeedback::Again,
            Self::Two => RecallFeedback::Hard,
            Self::Three => RecallFeedback::Good,
            Self::Four => RecallFeedback::Easy,
        }
    }
}

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

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

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ReviewKeyBinding {
    General,
    Button,
}

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_button_key_handler(
        ui.ready_add_card_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_button_key_handler(
        ui.ready_edit_card_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_button_key_handler(
        ui.ready_remove_card_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_button_key_handler(
        ui.start_cycle_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_button_key_handler(
        ui.force_start_cycle_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_button_key_handler(
        ui.review_flagged_cards_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_button_key_handler(
        ui.start_exam_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_button_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_button_key_handler(
        ui.reveal_response_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_button_key_handler(
        ui.guess_flag_card_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_button_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_button_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_button_key_handler(
        ui.feedback_edit_card_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );
    bind_review_button_key_handler(
        ui.feedback_flag_card_button,
        Rc::clone(&state),
        Rc::clone(&dispatch),
    );

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

    for button in ui.feedback_buttons {
        bind_review_button_key_handler(button, Rc::clone(&state), Rc::clone(&dispatch));
    }
    bind_review_button_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,
{
    bind_review_key_handler_with_binding(widget, state, dispatch, ReviewKeyBinding::General);
}

fn bind_review_button_key_handler<W, F>(widget: W, state: Rc<RefCell<AppState>>, dispatch: Rc<F>)
where
    W: WxWidget + WindowEvents + Copy + 'static,
    F: Fn(ReviewKeyCommand) -> bool + 'static,
{
    bind_review_key_handler_with_binding(widget, state, dispatch, ReviewKeyBinding::Button);
}

fn bind_review_key_handler_with_binding<W, F>(
    widget: W,
    state: Rc<RefCell<AppState>>,
    dispatch: Rc<F>,
    binding: ReviewKeyBinding,
) 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_for_binding(
                binding,
                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),
            }
        }
    });
}

#[cfg(test)]
pub(crate) fn review_key_disposition(
    phase: ReviewPhase,
    unicode_key: Option<i32>,
    key_code: Option<i32>,
    control_down: bool,
) -> ReviewKeyDisposition {
    review_key_disposition_for_binding(
        ReviewKeyBinding::General,
        phase,
        unicode_key,
        key_code,
        control_down,
    )
}

fn review_key_disposition_for_binding(
    binding: ReviewKeyBinding,
    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) {
        if matches!(binding, ReviewKeyBinding::Button) {
            return ReviewKeyDisposition::Skip;
        }
        return ReviewKeyDisposition::Dispatch(ReviewKeyCommand::RevealResponse);
    }

    match key_to_digit(unicode_key, key_code) {
        Some(0) => ReviewKeyDisposition::Dispatch(ReviewKeyCommand::EndCurrentPass),
        Some(digit) => match RatingButton::from_digit(digit) {
            Some(rating_button) => {
                ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ActivateRating(rating_button))
            }
            None => ReviewKeyDisposition::Skip,
        },
        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(RatingButton::Four))
        );
        assert_eq!(
            review_key_disposition(ReviewPhase::Prompt, Some('e' as i32), None, false),
            ReviewKeyDisposition::Skip
        );
    }

    #[test]
    fn prompt_phase_button_space_is_left_to_the_native_button() {
        assert_eq!(
            review_key_disposition_for_binding(
                ReviewKeyBinding::Button,
                ReviewPhase::Prompt,
                Some(' ' as i32),
                None,
                false,
            ),
            ReviewKeyDisposition::Skip
        );
        assert_eq!(
            review_key_disposition_for_binding(
                ReviewKeyBinding::Button,
                ReviewPhase::Prompt,
                Some('f' as i32),
                None,
                true,
            ),
            ReviewKeyDisposition::Dispatch(ReviewKeyCommand::ToggleFlag)
        );
        assert_eq!(
            review_key_disposition_for_binding(
                ReviewKeyBinding::General,
                ReviewPhase::Prompt,
                Some(' ' as i32),
                None,
                false,
            ),
            ReviewKeyDisposition::Dispatch(ReviewKeyCommand::RevealResponse)
        );
    }

    #[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(RatingButton::One))
        );
        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
        );
    }
}
