use rand::Rng;

use blizz_ui::components::selector::ListNavigate;

use crate::wizard::WizardStep;
use crate::wizard::next_step_index;
use crate::wizard::state::{Direction, StateResult, WizardAction, WizardState};
use crate::wizard::ui_controller::UiController;

use super::exit::Exit;
use super::transition::Transition;

pub struct SelectInput {
  steps: Vec<WizardStep>,
  step_idx: usize,
}

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

impl WizardState for SelectInput {
  fn tick(&mut self, _ui: &mut UiController, _rng: &mut Box<dyn Rng>) -> StateResult {
    StateResult::Continue
  }

  fn handle_action(&mut self, action: WizardAction, ui: &mut UiController) -> StateResult {
    match action {
      WizardAction::Exit => StateResult::Finished,
      WizardAction::Navigate(dir) => handle_navigate(ui, dir),
      WizardAction::Submit(_) => handle_submit(self, ui),
      WizardAction::Resize(_, _) => StateResult::Continue,
    }
  }
}

fn handle_navigate(ui: &mut UiController, dir: Direction) -> StateResult {
  let nav = match dir {
    Direction::Up => ListNavigate::Up,
    Direction::Down => ListNavigate::Down,
  };
  ui.selector.navigate(nav);
  StateResult::Continue
}

fn handle_submit(state: &mut SelectInput, ui: &UiController) -> StateResult {
  let step = &state.steps[state.step_idx];
  let output = build_output(step, ui);

  let next_idx = next_step_index(state.step_idx, &state.steps, &output);
  let selected_value = output.as_ref().map(|(_, v)| v.clone()).unwrap_or_default();

  let next: Box<dyn WizardState> = match next_idx {
    Some(idx) => Box::new(Transition::new(
      state.steps.clone(),
      state.step_idx,
      idx,
      selected_value,
    )),
    None => Box::new(Exit::new(
      state.steps.clone(),
      state.step_idx,
      selected_value,
    )),
  };

  StateResult::Done { next, output }
}

fn build_output(step: &WizardStep, ui: &UiController) -> Option<(String, String)> {
  let env_var = step.env_var()?;
  let value = ui
    .selector
    .options
    .get(ui.selector.selected)
    .map(|opt| opt.value.clone())
    .unwrap_or_default();
  Some((env_var.to_string(), value))
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::wizard::SelectOption;
  use blizz_ui::components::selector::SelectorComponent;

  fn make_ui_with_selector(options: Vec<(&str, &str)>, selected: usize) -> UiController {
    let opts: Vec<SelectOption> = options
      .into_iter()
      .map(|(l, v)| SelectOption {
        label: l.into(),
        value: v.into(),
      })
      .collect();

    let step = crate::wizard::WizardStep::dialog("dummy");
    let mut ui = crate::wizard::ui_controller::new(&step);
    ui.selector = SelectorComponent::from_options(opts, selected);
    ui
  }

  #[test]
  fn navigate_down_increments_selection() {
    let mut ui = make_ui_with_selector(vec![("A", "a"), ("B", "b"), ("C", "c")], 0);
    handle_navigate(&mut ui, Direction::Down);
    assert_eq!(ui.selector.selected, 1);
  }

  #[test]
  fn navigate_down_wraps_to_first() {
    let mut ui = make_ui_with_selector(vec![("A", "a"), ("B", "b")], 1);
    handle_navigate(&mut ui, Direction::Down);
    assert_eq!(ui.selector.selected, 0);
  }

  #[test]
  fn navigate_up_decrements_selection() {
    let mut ui = make_ui_with_selector(vec![("A", "a"), ("B", "b"), ("C", "c")], 2);
    handle_navigate(&mut ui, Direction::Up);
    assert_eq!(ui.selector.selected, 1);
  }

  #[test]
  fn navigate_up_wraps_to_last() {
    let mut ui = make_ui_with_selector(vec![("A", "a"), ("B", "b"), ("C", "c")], 0);
    handle_navigate(&mut ui, Direction::Up);
    assert_eq!(ui.selector.selected, 2);
  }

  #[test]
  fn navigate_empty_options_is_noop() {
    let step = crate::wizard::WizardStep::dialog("dummy");
    let mut ui = crate::wizard::ui_controller::new(&step);
    handle_navigate(&mut ui, Direction::Down);
    assert_eq!(ui.selector.selected, 0);
  }

  #[test]
  fn build_output_returns_selected_value() {
    let ui = make_ui_with_selector(vec![("Yes", "yes"), ("No", "no")], 1);
    let step = crate::wizard::WizardStep::select("Q", "ANSWER", vec![("Yes", "yes"), ("No", "no")]);
    let output = build_output(&step, &ui);
    assert_eq!(output, Some(("ANSWER".into(), "no".into())));
  }

  #[test]
  fn build_output_returns_first_when_selected_zero() {
    let ui = make_ui_with_selector(vec![("A", "alpha"), ("B", "beta")], 0);
    let step = crate::wizard::WizardStep::select("Q", "V", vec![("A", "alpha"), ("B", "beta")]);
    let output = build_output(&step, &ui);
    assert_eq!(output, Some(("V".into(), "alpha".into())));
  }

  #[test]
  fn build_output_returns_none_for_dialog() {
    let ui = make_ui_with_selector(vec![("A", "a")], 0);
    let step = crate::wizard::WizardStep::dialog("Msg");
    let output = build_output(&step, &ui);
    assert_eq!(output, None);
  }

  #[test]
  fn build_output_with_hidden_selector_returns_empty_value() {
    let step = crate::wizard::WizardStep::select("Q", "V", vec![("A", "a")]);
    let dummy_step = crate::wizard::WizardStep::dialog("dummy");
    let ui = crate::wizard::ui_controller::new(&dummy_step);
    let output = build_output(&step, &ui);
    assert_eq!(output, Some(("V".into(), String::new())));
  }
}
