use rand::Rng;

use super::dissolve::Dissolve;
use crate::wizard::WizardStep;
use crate::wizard::state::{StateResult, WizardAction, WizardState};
use crate::wizard::ui_controller::UiController;

const ENCODE_SPEED: f64 = 0.04;
const BOX_CLOSE_SPEED: f64 = 0.08;
const FADE_SPEED: f64 = 0.06;

pub struct Exit {
  steps: Vec<WizardStep>,
  step_idx: usize,
  prev_input: String,
}

impl Exit {
  pub fn new(steps: Vec<WizardStep>, step_idx: usize, prev_input: String) -> Self {
    Self {
      steps,
      step_idx,
      prev_input,
    }
  }
}

impl WizardState for Exit {
  fn tick(&mut self, ui: &mut UiController, rng: &mut Box<dyn Rng>) -> StateResult {
    ui.text_entry.tick_exit_close(ENCODE_SPEED, BOX_CLOSE_SPEED);

    ui.selector.tick_exit_close(ENCODE_SPEED, BOX_CLOSE_SPEED);

    ui.question.reveal = (ui.question.reveal - ENCODE_SPEED).max(0.0);
    ui.hint.fade = (ui.hint.fade - FADE_SPEED).max(0.0);

    if is_exit_complete(ui) {
      ui.mascot.floating = false;
      let next = Dissolve::new(&mut ui.mascot, rng);
      return StateResult::Done {
        next: Box::new(next),
        output: None,
      };
    }

    StateResult::Continue
  }

  fn handle_action(&mut self, action: WizardAction, _ui: &mut UiController) -> StateResult {
    match action {
      WizardAction::Exit => StateResult::Finished,
      _ => StateResult::Continue,
    }
  }
}

fn is_exit_complete(ui: &UiController) -> bool {
  if ui.question.reveal > 0.0 {
    return false;
  }
  if ui.hint.fade > 0.0 {
    return false;
  }
  if ui.text_entry.visible && ui.text_entry.open > 0.0 {
    return false;
  }
  if ui.selector.visible && ui.selector.open > 0.0 {
    return false;
  }
  true
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::wizard::ui_controller;
  use crate::wizard::{Branch, WizardStep};

  fn tick_until_done(state: &mut Exit, ui: &mut UiController) -> usize {
    let mut rng: Box<dyn Rng> = Box::new(rand::rng());
    let mut ticks = 0;
    loop {
      ticks += 1;
      if ticks > 5000 {
        panic!("exit animation did not complete within 5000 ticks");
      }
      match state.tick(ui, &mut rng) {
        StateResult::Done { .. } => return ticks,
        StateResult::Continue => {}
        StateResult::Finished => panic!("unexpected Finished"),
      }
    }
  }

  fn make_select_ui() -> (UiController, Vec<WizardStep>) {
    let step = WizardStep::select("Pick one", "CHOICE", vec![("A", "a"), ("B", "b")]);
    let steps = vec![step.clone()];
    let mut ui = ui_controller::new(&step);
    ui.question.reveal = 1.0;
    ui.hint.fade = 1.0;
    ui.selector.open = 1.0;
    ui.selector.reveal = 1.0;
    (ui, steps)
  }

  fn make_prompt_ui() -> (UiController, Vec<WizardStep>) {
    let step = WizardStep::prompt("Name?", "NAME", "default");
    let steps = vec![step.clone()];
    let mut ui = ui_controller::new(&step);
    ui.question.reveal = 1.0;
    ui.hint.fade = 1.0;
    ui.text_entry.open = 1.0;
    ui.text_entry.label_reveal = 1.0;
    ui.text_entry.content_reveal = 1.0;
    (ui, steps)
  }

  fn make_confirm_ui() -> (UiController, Vec<WizardStep>) {
    let step = WizardStep::confirm("Sure?", "OK", Branch::Next, Branch::Finish);
    let steps = vec![step.clone()];
    let mut ui = ui_controller::new(&step);
    ui.question.reveal = 1.0;
    ui.hint.fade = 1.0;
    ui.selector.open = 1.0;
    ui.selector.reveal = 1.0;
    (ui, steps)
  }

  #[test]
  fn exit_animates_selector_closed() {
    let (mut ui, steps) = make_select_ui();
    let mut state = Exit::new(steps, 0, String::new());

    assert!(ui.selector.visible);
    assert_eq!(ui.selector.open, 1.0);
    assert_eq!(ui.selector.reveal, 1.0);

    tick_until_done(&mut state, &mut ui);

    assert_eq!(ui.selector.open, 0.0);
    assert_eq!(ui.selector.reveal, 0.0);
  }

  #[test]
  fn exit_animates_confirm_selector_closed() {
    let (mut ui, steps) = make_confirm_ui();
    let mut state = Exit::new(steps, 0, String::new());

    tick_until_done(&mut state, &mut ui);

    assert_eq!(ui.selector.open, 0.0);
    assert_eq!(ui.selector.reveal, 0.0);
  }

  #[test]
  fn exit_animates_text_entry_closed() {
    let (mut ui, steps) = make_prompt_ui();
    let mut state = Exit::new(steps, 0, String::new());

    assert!(ui.text_entry.visible);
    assert_eq!(ui.text_entry.open, 1.0);

    tick_until_done(&mut state, &mut ui);

    assert_eq!(ui.text_entry.open, 0.0);
  }

  #[test]
  fn exit_fades_question_and_hint() {
    let (mut ui, steps) = make_select_ui();
    let mut state = Exit::new(steps, 0, String::new());

    tick_until_done(&mut state, &mut ui);

    assert_eq!(ui.question.reveal, 0.0);
    assert_eq!(ui.hint.fade, 0.0);
  }

  #[test]
  fn exit_selector_reveal_decreases_before_open() {
    let (mut ui, steps) = make_select_ui();
    let mut state = Exit::new(steps, 0, String::new());
    let mut rng: Box<dyn Rng> = Box::new(rand::rng());

    state.tick(&mut ui, &mut rng);

    assert!(ui.selector.reveal < 1.0, "reveal should start decreasing");
    assert_eq!(
      ui.selector.open, 1.0,
      "open should not decrease until reveal reaches 0"
    );
  }

  #[test]
  fn is_exit_complete_false_while_selector_open() {
    let (mut ui, _) = make_select_ui();
    ui.question.reveal = 0.0;
    ui.hint.fade = 0.0;
    ui.selector.reveal = 0.0;
    ui.selector.open = 0.5;

    assert!(!is_exit_complete(&ui));
  }

  #[test]
  fn is_exit_complete_true_when_all_zero() {
    let (mut ui, _) = make_select_ui();
    ui.question.reveal = 0.0;
    ui.hint.fade = 0.0;
    ui.selector.reveal = 0.0;
    ui.selector.open = 0.0;

    assert!(is_exit_complete(&ui));
  }

  #[test]
  fn is_exit_complete_waits_for_text_entry() {
    let (mut ui, _) = make_prompt_ui();
    ui.question.reveal = 0.0;
    ui.hint.fade = 0.0;
    ui.text_entry.label_reveal = 0.0;
    ui.text_entry.content_reveal = 0.0;
    ui.text_entry.open = 0.3;

    assert!(!is_exit_complete(&ui));
  }
}
