use blizz_ui::components::hint::HintComponent;
use blizz_ui::components::mascot::MascotComponent;
use blizz_ui::components::selector::SelectorComponent;
use blizz_ui::components::text::TextComponent;
use blizz_ui::components::text_entry::TextEntryComponent;
use blizz_ui::components::timer_bar::TimerBarComponent;

use crate::wizard::WizardStep;

const SUBMIT_HINT: &str = "(hit enter to submit)";
const CONTINUE_HINT: &str = "(hit enter to continue)";
const SELECT_HINT: &str = "(\u{2191}\u{2193} select \u{00b7} enter confirm)";

pub struct UiController {
  pub mascot: MascotComponent,
  pub question: TextComponent,
  pub text_entry: TextEntryComponent,
  pub selector: SelectorComponent,
  pub hint: HintComponent,
  pub timer_bar: TimerBarComponent,
}

pub fn new(step: &WizardStep) -> UiController {
  UiController {
    mascot: MascotComponent::blizz(),
    question: TextComponent::new(step.question()),
    text_entry: text_entry_for(step),
    selector: selector_for(step),
    hint: HintComponent::new(step.hint_text()),
    timer_bar: TimerBarComponent::new(),
  }
}

pub fn swap_to(ui: &mut UiController, step: &WizardStep) {
  ui.question.text = step.question().to_string();
  ui.question.reveal = 0.0;

  let was_visible = ui.text_entry.visible;
  let was_open = ui.text_entry.open;
  ui.text_entry = text_entry_for(step);
  if was_visible && ui.text_entry.visible {
    ui.text_entry.open = was_open;
  }

  let old_selector_visible = ui.selector.visible;
  let old_selector_open = ui.selector.open;
  ui.selector = selector_for(step);
  if old_selector_visible && ui.selector.visible {
    ui.selector.open = old_selector_open;
  }

  let old_hint = ui.hint.text;
  let new_hint = step.hint_text();
  ui.hint.text = new_hint;
  if old_hint != new_hint {
    ui.hint.fade = 0.0;
  }

  ui.timer_bar = TimerBarComponent::new();
}

fn text_entry_for(step: &WizardStep) -> TextEntryComponent {
  match step {
    WizardStep::Prompt {
      env_var, default, ..
    } => TextEntryComponent::prompt(env_var.clone(), default.clone()),
    WizardStep::Dialog { .. } | WizardStep::Select { .. } => TextEntryComponent::hidden(),
  }
}

fn selector_for(step: &WizardStep) -> SelectorComponent {
  match step {
    WizardStep::Select {
      options,
      default_index,
      ..
    } => SelectorComponent::from_options(options.clone(), *default_index),
    _ => SelectorComponent::hidden(),
  }
}

impl WizardStep {
  pub(crate) fn hint_text(&self) -> &'static str {
    match self {
      Self::Prompt { .. } => SUBMIT_HINT,
      Self::Dialog { .. } => CONTINUE_HINT,
      Self::Select { .. } => SELECT_HINT,
    }
  }
}

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

  // ── new() ─────────────────────────────────────────────────

  #[test]
  fn new_prompt_has_visible_box_no_selector() {
    let step = WizardStep::prompt("Q", "VAR", "default");
    let ui = new(&step);
    assert!(ui.text_entry.visible);
    assert_eq!(ui.text_entry.label, "VAR");
    assert_eq!(ui.text_entry.content, "default");
    assert!(!ui.selector.visible);
    assert_eq!(ui.hint.text, SUBMIT_HINT);
  }

  #[test]
  fn new_dialog_has_invisible_box_no_selector() {
    let step = WizardStep::dialog("Message");
    let ui = new(&step);
    assert!(!ui.text_entry.visible);
    assert!(!ui.selector.visible);
    assert_eq!(ui.hint.text, CONTINUE_HINT);
  }

  #[test]
  fn new_select_has_invisible_box_and_selector() {
    let step = WizardStep::select("Pick", "CHOICE", vec![("A", "a"), ("B", "b")]);
    let ui = new(&step);
    assert!(!ui.text_entry.visible);
    assert!(ui.selector.visible);
    assert_eq!(ui.selector.options.len(), 2);
    assert_eq!(ui.selector.selected, 0);
    assert_eq!(ui.hint.text, SELECT_HINT);
  }

  #[test]
  fn new_select_uses_default_index() {
    let step = WizardStep::select("Q", "V", vec![("A", "a"), ("B", "b")]).default_index(1);
    let ui = new(&step);
    assert_eq!(ui.selector.selected, 1);
  }

  #[test]
  fn new_confirm_has_two_options() {
    let step = WizardStep::confirm("Sure?", "OK", Branch::Next, Branch::Finish);
    let ui = new(&step);
    assert_eq!(ui.selector.options.len(), 2);
    assert_eq!(ui.selector.options[0].value, "yes");
    assert_eq!(ui.selector.options[1].value, "no");
  }

  // ── selector_for ──────────────────────────────────────────

  #[test]
  fn selector_for_prompt_is_hidden() {
    let step = WizardStep::prompt("Q", "V", "D");
    let sel = selector_for(&step);
    assert!(!sel.visible);
  }

  #[test]
  fn selector_for_dialog_is_hidden() {
    let step = WizardStep::dialog("M");
    let sel = selector_for(&step);
    assert!(!sel.visible);
  }

  #[test]
  fn selector_for_select_populates_options() {
    let step = WizardStep::select("Q", "V", vec![("X", "x"), ("Y", "y"), ("Z", "z")]);
    let sel = selector_for(&step);
    assert_eq!(sel.options.len(), 3);
    assert_eq!(sel.open, 0.0);
    assert_eq!(sel.reveal, 0.0);
  }

  // ── text_entry_for ─────────────────────────────────────────

  #[test]
  fn text_entry_for_select_is_invisible() {
    let step = WizardStep::select("Q", "V", vec![("A", "a")]);
    let bx = text_entry_for(&step);
    assert!(!bx.visible);
  }

  #[test]
  fn new_starts_timer_bar_hidden() {
    let ui = new(&WizardStep::dialog("x"));
    assert!(!ui.timer_bar.visible);
    assert_eq!(ui.timer_bar.progress, 1.0);
  }

  #[test]
  fn swap_to_resets_timer_bar() {
    let mut ui = new(&WizardStep::dialog("a"));
    ui.timer_bar.visible = true;
    ui.timer_bar.progress = 0.3;
    swap_to(&mut ui, &WizardStep::dialog("b"));
    assert!(!ui.timer_bar.visible);
    assert_eq!(ui.timer_bar.progress, 1.0);
  }

  // ── swap_to ───────────────────────────────────────────────

  #[test]
  fn swap_to_select_creates_selector() {
    let prompt = WizardStep::prompt("Q1", "V1", "D");
    let mut ui = new(&prompt);
    assert!(!ui.selector.visible);

    let select = WizardStep::select("Q2", "V2", vec![("A", "a")]);
    swap_to(&mut ui, &select);
    assert!(ui.selector.visible);
    assert_eq!(ui.question.text, "Q2");
  }

  #[test]
  fn swap_to_prompt_hides_selector() {
    let select = WizardStep::select("Q1", "V1", vec![("A", "a")]);
    let mut ui = new(&select);
    assert!(ui.selector.visible);

    let prompt = WizardStep::prompt("Q2", "V2", "D");
    swap_to(&mut ui, &prompt);
    assert!(!ui.selector.visible);
    assert!(ui.text_entry.visible);
  }

  #[test]
  fn swap_to_select_preserves_open_progress() {
    let sel1 = WizardStep::select("Q1", "V1", vec![("A", "a")]);
    let mut ui = new(&sel1);
    ui.selector.open = 0.75;
    ui.selector.visible = true;

    let sel2 = WizardStep::select("Q2", "V2", vec![("B", "b"), ("C", "c")]);
    swap_to(&mut ui, &sel2);

    assert_eq!(ui.selector.open, 0.75);
    assert_eq!(ui.selector.options.len(), 2);
  }

  #[test]
  fn swap_to_resets_hint_fade_when_hint_changes() {
    let prompt = WizardStep::prompt("Q", "V", "D");
    let mut ui = new(&prompt);
    ui.hint.fade = 1.0;

    let select = WizardStep::select("Q2", "V2", vec![("A", "a")]);
    swap_to(&mut ui, &select);
    assert_eq!(ui.hint.fade, 0.0);
  }

  #[test]
  fn swap_to_preserves_hint_fade_when_hint_unchanged() {
    let sel1 = WizardStep::select("Q1", "V1", vec![("A", "a")]);
    let mut ui = new(&sel1);
    ui.hint.fade = 0.8;

    let sel2 = WizardStep::select("Q2", "V2", vec![("B", "b")]);
    swap_to(&mut ui, &sel2);
    assert_eq!(ui.hint.fade, 0.8);
  }

  // ── hint_text ─────────────────────────────────────────────

  #[test]
  fn hint_text_for_each_step_type() {
    assert_eq!(WizardStep::prompt("Q", "V", "D").hint_text(), SUBMIT_HINT);
    assert_eq!(WizardStep::dialog("M").hint_text(), CONTINUE_HINT);
    assert_eq!(
      WizardStep::select("Q", "V", vec![("A", "a")]).hint_text(),
      SELECT_HINT
    );
  }

  #[test]
  fn hint_char_count_differs_from_byte_len_for_select() {
    let char_width = SELECT_HINT.chars().count();
    let byte_len = SELECT_HINT.len();
    assert!(
      byte_len > char_width,
      "SELECT_HINT contains multi-byte chars; byte len ({byte_len}) must exceed char count ({char_width})"
    );
  }

  #[test]
  fn hint_centering_uses_char_count_not_byte_len() {
    let char_width = SELECT_HINT.chars().count() as u16;
    let byte_width = SELECT_HINT.len() as u16;
    let tw: u16 = 80;

    let correct_col = blizz_ui::layout::centered_column(tw, char_width);
    let wrong_col = blizz_ui::layout::centered_column(tw, byte_width);

    assert!(
      correct_col > wrong_col,
      "char-based centering ({correct_col}) should be further right than byte-based ({wrong_col})"
    );
    assert_eq!(correct_col, (tw - char_width) / 2);
  }

  // ── SelectorComponent constructors ──────────────────────────

  #[test]
  fn hidden_selector_is_not_visible() {
    let sel = SelectorComponent::hidden();
    assert!(!sel.visible);
    assert!(sel.options.is_empty());
  }

  #[test]
  fn from_options_populates_and_is_visible() {
    let opts = vec![
      SelectOption {
        label: "A".into(),
        value: "a".into(),
      },
      SelectOption {
        label: "B".into(),
        value: "b".into(),
      },
    ];
    let sel = SelectorComponent::from_options(opts, 1);
    assert!(sel.visible);
    assert_eq!(sel.selected, 1);
    assert_eq!(sel.options.len(), 2);
  }
}
