#![allow(unexpected_cfgs)]

pub mod render;
pub mod state;
pub mod states;
pub mod ui_controller;

use std::io::{Write, stdout};
use std::sync::Arc;
use std::time::{Duration, Instant};

use crossterm::event::{self, Event, KeyCode, KeyModifiers};
use rand::rngs::ThreadRng;
use serde_json::Value;

use crate::input_buffer::{self, InputBuffer};
use crate::layout;
use crate::mascot::MascotFrames;
use crate::mascot_fx;

use render::RenderContext;
use state::{Direction, StateResult, WizardAction, WizardState};
use states::entrance::Entrance;

const DEFAULT_TICK_RATE: Duration = Duration::from_millis(30);
const DEFAULT_GARBLE_TICKS: usize = 7;
const DEFAULT_BOX_OPEN_SPEED: f64 = 4.0;

#[derive(Clone)]
pub struct BranchFn(Arc<dyn Fn(&Value) -> Branch>);

/// Manual impl required because `WizardStep` derives Debug but `Arc<dyn Fn>`
/// doesn't implement it. Closures carry no runtime name, so we can't provide
/// more detail than the type itself.
impl std::fmt::Debug for BranchFn {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    f.debug_tuple("BranchFn").field(&"<closure>").finish()
  }
}

impl BranchFn {
  pub(crate) fn call(&self, value: &Value) -> Branch {
    (self.0)(value)
  }
}

#[derive(Debug, Clone)]
pub struct SelectOption {
  pub label: String,
  pub value: String,
}

pub trait SelectEnum: Sized {
  fn options() -> Vec<SelectOption>;
  fn from_value(s: &str) -> Option<Self>;
}

#[derive(Debug, Clone)]
pub enum WizardStep {
  Prompt {
    question: String,
    env_var: String,
    default: String,
    id: Option<&'static str>,
    branch: Option<BranchFn>,
  },
  Dialog {
    message: String,
    id: Option<&'static str>,
    branch: Option<BranchFn>,
  },
  Select {
    question: String,
    env_var: String,
    options: Vec<SelectOption>,
    default_index: usize,
    id: Option<&'static str>,
    branch: Option<BranchFn>,
  },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Branch {
  Next,
  Goto(&'static str),
  Finish,
}

#[derive(Debug, Clone)]
pub struct WizardOptions {
  pub tick_rate: Duration,
  pub garble_ticks: usize,
  pub box_open_speed: f64,
}

impl Default for WizardOptions {
  fn default() -> Self {
    Self {
      tick_rate: DEFAULT_TICK_RATE,
      garble_ticks: DEFAULT_GARBLE_TICKS,
      box_open_speed: DEFAULT_BOX_OPEN_SPEED,
    }
  }
}

impl WizardStep {
  pub fn prompt(question: &str, env_var: &str, default: &str) -> Self {
    Self::Prompt {
      question: question.to_string(),
      env_var: env_var.to_string(),
      default: default.to_string(),
      id: None,
      branch: None,
    }
  }

  pub fn dialog(message: &str) -> Self {
    Self::Dialog {
      message: message.to_string(),
      id: None,
      branch: None,
    }
  }

  pub fn select(question: &str, env_var: &str, options: Vec<(&str, &str)>) -> Self {
    Self::Select {
      question: question.to_string(),
      env_var: env_var.to_string(),
      options: options
        .into_iter()
        .map(|(label, value)| SelectOption {
          label: label.to_string(),
          value: value.to_string(),
        })
        .collect(),
      default_index: 0,
      id: None,
      branch: None,
    }
  }

  pub fn select_enum<T: SelectEnum>(question: &str, env_var: &str) -> Self {
    Self::Select {
      question: question.to_string(),
      env_var: env_var.to_string(),
      options: T::options(),
      default_index: 0,
      id: None,
      branch: None,
    }
  }

  pub fn confirm(question: &str, env_var: &str, on_yes: Branch, on_no: Branch) -> Self {
    let key = env_var.to_string();
    Self::Select {
      question: question.to_string(),
      env_var: env_var.to_string(),
      options: vec![
        SelectOption {
          label: "Yes".into(),
          value: "yes".into(),
        },
        SelectOption {
          label: "No".into(),
          value: "no".into(),
        },
      ],
      default_index: 0,
      id: None,
      branch: Some(BranchFn(Arc::new(move |answers| {
        let val = answers.get(&key).and_then(|v| v.as_str()).unwrap_or("yes");
        if val == "yes" { on_yes } else { on_no }
      }))),
    }
  }

  pub fn default_index(mut self, idx: usize) -> Self {
    if let Self::Select { default_index, .. } = &mut self {
      *default_index = idx;
    }
    self
  }

  pub fn id(mut self, id: &'static str) -> Self {
    match &mut self {
      Self::Prompt { id: slot, .. }
      | Self::Dialog { id: slot, .. }
      | Self::Select { id: slot, .. } => *slot = Some(id),
    }
    self
  }

  pub fn on_submit(mut self, f: impl Fn(&Value) -> Branch + 'static) -> Self {
    let branch_fn = BranchFn(Arc::new(f));
    match &mut self {
      Self::Prompt { branch: slot, .. }
      | Self::Dialog { branch: slot, .. }
      | Self::Select { branch: slot, .. } => *slot = Some(branch_fn),
    }
    self
  }

  pub(crate) fn question(&self) -> &str {
    match self {
      Self::Prompt { question, .. } | Self::Select { question, .. } => question,
      Self::Dialog { message, .. } => message,
    }
  }

  pub(crate) fn env_var(&self) -> Option<&str> {
    match self {
      Self::Prompt { env_var, .. } | Self::Select { env_var, .. } => Some(env_var),
      Self::Dialog { .. } => None,
    }
  }

  pub(crate) fn default_value(&self) -> &str {
    match self {
      Self::Prompt { default, .. } => default,
      Self::Select {
        options,
        default_index,
        ..
      } => options
        .get(*default_index)
        .map(|o| o.value.as_str())
        .unwrap_or(""),
      Self::Dialog { .. } => "",
    }
  }

  pub(crate) fn step_id(&self) -> Option<&'static str> {
    match self {
      Self::Prompt { id, .. } | Self::Dialog { id, .. } | Self::Select { id, .. } => *id,
    }
  }

  pub(crate) fn branch_fn(&self) -> Option<&BranchFn> {
    match self {
      Self::Prompt { branch, .. } | Self::Dialog { branch, .. } | Self::Select { branch, .. } => {
        branch.as_ref()
      }
    }
  }

  pub(crate) fn select_options(&self) -> Option<&[SelectOption]> {
    match self {
      Self::Select { options, .. } => Some(options),
      _ => None,
    }
  }

  pub(crate) fn select_default_index(&self) -> usize {
    match self {
      Self::Select { default_index, .. } => *default_index,
      _ => 0,
    }
  }
}

#[cfg(not(tarpaulin_include))]
pub fn run_wizard(steps: &[WizardStep]) -> std::io::Result<Value> {
  run_wizard_with_options(steps, &WizardOptions::default())
}

#[cfg(not(tarpaulin_include))]
pub fn run_wizard_with_options(
  steps: &[WizardStep],
  _options: &WizardOptions,
) -> std::io::Result<Value> {
  validate_branch_targets(steps);

  let mut stdout = stdout();
  let mut rng = rand::rng();

  layout::enable_raw_mode()?;
  layout::enter_alternate_screen(&mut stdout)?;
  let result = wizard_loop(&mut stdout, &mut rng, steps);
  layout::leave_alternate_screen(&mut stdout)?;
  let _ = layout::disable_raw_mode();

  result
}

#[cfg(not(tarpaulin_include))]
fn wizard_loop<W: Write>(
  writer: &mut W,
  rng: &mut ThreadRng,
  steps: &[WizardStep],
) -> std::io::Result<Value> {
  let mascot = MascotFrames::blizz();
  let steps_vec: Vec<WizardStep> = steps.to_vec();
  let started = Instant::now();
  let mut terminal_size = layout::terminal_size()?;

  let mut ui = ui_controller::new(&steps_vec[0]);
  let entrance = Entrance::new(mascot, steps_vec.clone(), 0);

  let mut current_state: Box<dyn WizardState> = Box::new(entrance);
  let mut results = serde_json::Map::new();
  let mut entrance_tick: usize = 0;
  let mut dissolve_tick: usize = 0;
  let mut dissolve_state: Option<mascot_fx::DissolveState> = None;
  let mut input_buf = input_buffer::new();

  let tick_rate = DEFAULT_TICK_RATE;

  loop {
    let render_ctx = RenderContext {
      mascot,
      terminal_size,
      elapsed: started.elapsed(),
      distance_map: mascot_fx::build_distance_map(mascot.lines()),
    };

    render::render(
      writer,
      &ui,
      &render_ctx,
      rng,
      dissolve_state.as_ref(),
      entrance_tick,
      dissolve_tick,
      &input_buf,
    )?;
    writer.flush()?;

    let action = drain_events(&mut input_buf, tick_rate)?;

    let result = match &action {
      Some(WizardAction::Exit) => {
        return Err(std::io::Error::new(
          std::io::ErrorKind::Interrupted,
          "wizard cancelled",
        ));
      }
      Some(a) => current_state.handle_action(a.clone(), &mut ui),
      None => current_state.tick(&mut ui, rng),
    };

    match result {
      StateResult::Continue => {}
      StateResult::Done { next, output } => {
        if let Some((k, v)) = output {
          results.insert(k, Value::String(v));
          input_buf = input_buffer::new();
        }
        current_state = next;
      }
      StateResult::Finished => {
        return Ok(Value::Object(results));
      }
    }

    if ui.mascot.entrance < 1.0 && ui.mascot.dissolve <= 0.0 {
      entrance_tick += 1;
    }
    if ui.mascot.dissolve > 0.0 && ui.mascot.dissolve < 1.0 {
      dissolve_tick += 1;
    }

    if ui.mascot.dissolve > 0.0 && dissolve_state.is_none() {
      dissolve_state = Some(mascot_fx::build_dissolve_state(mascot.lines(), rng));
    }

    if let Some(WizardAction::Resize(w, h)) = &action {
      terminal_size = layout::size(*w, *h);
    }
  }
}

#[cfg(not(tarpaulin_include))]
fn drain_events(
  input_buf: &mut InputBuffer,
  tick_rate: Duration,
) -> std::io::Result<Option<WizardAction>> {
  if !event::poll(tick_rate)? {
    return Ok(None);
  }

  let mut action = None;
  while event::poll(Duration::ZERO)? {
    if let Some(new_action) = classify_event(&event::read()?, input_buf) {
      let is_terminal = matches!(new_action, WizardAction::Exit | WizardAction::Submit(_));
      action = Some(new_action);
      if is_terminal {
        break;
      }
    }
  }
  Ok(action)
}

#[cfg(not(tarpaulin_include))]
fn classify_event(evt: &Event, input_buf: &mut InputBuffer) -> Option<WizardAction> {
  match evt {
    Event::Key(key) if is_exit_key(key) => Some(WizardAction::Exit),
    Event::Key(key) => handle_input_key(key, input_buf),
    Event::Resize(w, h) => Some(WizardAction::Resize(*w, *h)),
    _ => None,
  }
}

#[cfg(not(tarpaulin_include))]
fn handle_input_key(
  key: &crossterm::event::KeyEvent,
  buf: &mut InputBuffer,
) -> Option<WizardAction> {
  let shift = key.modifiers.contains(KeyModifiers::SHIFT);
  let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);

  match key.code {
    KeyCode::Enter => return Some(WizardAction::Submit(input_buffer::text(buf).to_string())),
    KeyCode::Up => return Some(WizardAction::Navigate(Direction::Up)),
    KeyCode::Down => return Some(WizardAction::Navigate(Direction::Down)),
    KeyCode::Backspace => input_buffer::backspace(buf),
    KeyCode::Delete => input_buffer::delete(buf),
    KeyCode::Left if ctrl && shift => input_buffer::select_word_left(buf),
    KeyCode::Right if ctrl && shift => input_buffer::select_word_right(buf),
    KeyCode::Left if ctrl => input_buffer::move_word_left(buf),
    KeyCode::Right if ctrl => input_buffer::move_word_right(buf),
    KeyCode::Left if shift => input_buffer::select_left(buf),
    KeyCode::Right if shift => input_buffer::select_right(buf),
    KeyCode::Home if shift => input_buffer::select_home(buf),
    KeyCode::End if shift => input_buffer::select_end(buf),
    KeyCode::Left => input_buffer::move_left(buf),
    KeyCode::Right => input_buffer::move_right(buf),
    KeyCode::Home => input_buffer::move_home(buf),
    KeyCode::End => input_buffer::move_end(buf),
    KeyCode::Char('a') if ctrl => input_buffer::select_all(buf),
    KeyCode::Char(c) => input_buffer::insert_char(buf, c),
    _ => {}
  }
  None
}

#[cfg(not(tarpaulin_include))]
fn is_exit_key(key: &crossterm::event::KeyEvent) -> bool {
  key.code == KeyCode::Esc
    || (key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL))
}

pub(crate) fn next_step_index(
  current: usize,
  steps: &[WizardStep],
  output: &Option<(String, String)>,
) -> Option<usize> {
  let step = &steps[current];

  if let Some(branch_fn) = step.branch_fn() {
    let results = build_results_value(output);
    match branch_fn.call(&results) {
      Branch::Next => sequential_next(current, steps),
      Branch::Goto(id) => find_step_by_id(steps, id),
      Branch::Finish => None,
    }
  } else {
    sequential_next(current, steps)
  }
}

fn sequential_next(current: usize, steps: &[WizardStep]) -> Option<usize> {
  let next = current + 1;
  if next < steps.len() { Some(next) } else { None }
}

fn find_step_by_id(steps: &[WizardStep], id: &str) -> Option<usize> {
  steps.iter().position(|s| s.step_id() == Some(id))
}

fn build_results_value(output: &Option<(String, String)>) -> Value {
  let mut map = serde_json::Map::new();
  if let Some((k, v)) = output {
    map.insert(k.clone(), Value::String(v.clone()));
  }
  Value::Object(map)
}

fn validate_branch_targets(steps: &[WizardStep]) {
  let ids: Vec<Option<&str>> = steps.iter().map(|s| s.step_id()).collect();

  for step in steps {
    if let Some(branch_fn) = step.branch_fn() {
      let dummy = Value::Object(serde_json::Map::new());
      if let Branch::Goto(target) = branch_fn.call(&dummy)
        && !ids.contains(&Some(target))
      {
        panic!(
          "WizardStep branch targets non-existent id: '{}'. Available ids: {:?}",
          target,
          ids.iter().filter_map(|id| *id).collect::<Vec<_>>()
        );
      }
    }
  }
}

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

  // ── BranchFn ──────────────────────────────────────────────

  #[test]
  fn branch_fn_call_invokes_closure() {
    let bf = BranchFn(Arc::new(|_| Branch::Finish));
    let dummy = Value::Object(serde_json::Map::new());
    assert_eq!(bf.call(&dummy), Branch::Finish);
  }

  #[test]
  fn branch_fn_clone_shares_arc() {
    let bf = BranchFn(Arc::new(|_| Branch::Next));
    let bf2 = bf.clone();
    let dummy = Value::Object(serde_json::Map::new());
    assert_eq!(bf2.call(&dummy), Branch::Next);
  }

  #[test]
  fn branch_fn_debug_shows_type() {
    let bf = BranchFn(Arc::new(|_| Branch::Next));
    assert_eq!(format!("{bf:?}"), "BranchFn(\"<closure>\")");
  }

  // ── SelectOption ──────────────────────────────────────────

  #[test]
  fn select_option_clone() {
    let opt = SelectOption {
      label: "Yes".into(),
      value: "yes".into(),
    };
    let opt2 = opt.clone();
    assert_eq!(opt2.label, "Yes");
    assert_eq!(opt2.value, "yes");
  }

  // ── WizardStep::select() ─────────────────────────────────

  #[test]
  fn select_constructor_builds_correct_options() {
    let step = WizardStep::select(
      "Pick one",
      "CHOICE",
      vec![("A", "a"), ("B", "b"), ("C", "c")],
    );
    assert_eq!(step.question(), "Pick one");
    assert_eq!(step.env_var(), Some("CHOICE"));
    assert_eq!(step.default_value(), "a");
    assert_eq!(step.select_default_index(), 0);
    let opts = step.select_options().unwrap();
    assert_eq!(opts.len(), 3);
    assert_eq!(opts[0].label, "A");
    assert_eq!(opts[0].value, "a");
    assert_eq!(opts[2].label, "C");
    assert_eq!(opts[2].value, "c");
  }

  #[test]
  fn select_default_index_builder() {
    let step = WizardStep::select("Pick", "X", vec![("A", "a"), ("B", "b")]).default_index(1);
    assert_eq!(step.select_default_index(), 1);
    assert_eq!(step.default_value(), "b");
  }

  #[test]
  fn select_default_index_on_non_select_is_noop() {
    let step = WizardStep::prompt("Q", "V", "D").default_index(5);
    assert_eq!(step.default_value(), "D");
  }

  // ── WizardStep::confirm() ────────────────────────────────

  #[test]
  fn confirm_creates_yes_no_options() {
    let step = WizardStep::confirm("Sure?", "OK", Branch::Next, Branch::Finish);
    let opts = step.select_options().unwrap();
    assert_eq!(opts.len(), 2);
    assert_eq!(opts[0].value, "yes");
    assert_eq!(opts[1].value, "no");
  }

  #[test]
  fn confirm_branch_routes_yes() {
    let step = WizardStep::confirm("Sure?", "OK", Branch::Next, Branch::Finish);
    let bf = step.branch_fn().unwrap();
    let mut map = serde_json::Map::new();
    map.insert("OK".into(), Value::String("yes".into()));
    assert_eq!(bf.call(&Value::Object(map)), Branch::Next);
  }

  #[test]
  fn confirm_branch_routes_no() {
    let step = WizardStep::confirm("Sure?", "OK", Branch::Next, Branch::Finish);
    let bf = step.branch_fn().unwrap();
    let mut map = serde_json::Map::new();
    map.insert("OK".into(), Value::String("no".into()));
    assert_eq!(bf.call(&Value::Object(map)), Branch::Finish);
  }

  #[test]
  fn confirm_branch_defaults_to_yes_when_missing() {
    let step = WizardStep::confirm("Sure?", "OK", Branch::Next, Branch::Finish);
    let bf = step.branch_fn().unwrap();
    let empty = Value::Object(serde_json::Map::new());
    assert_eq!(bf.call(&empty), Branch::Next);
  }

  // ── WizardStep::select_enum() ────────────────────────────

  struct FakeEnum;
  impl SelectEnum for FakeEnum {
    fn options() -> Vec<SelectOption> {
      vec![
        SelectOption {
          label: "Opt1".into(),
          value: "opt1".into(),
        },
        SelectOption {
          label: "Opt2".into(),
          value: "opt2".into(),
        },
      ]
    }
    fn from_value(s: &str) -> Option<Self> {
      match s {
        "opt1" | "opt2" => Some(FakeEnum),
        _ => None,
      }
    }
  }

  #[test]
  fn select_enum_constructor_uses_trait() {
    let step = WizardStep::select_enum::<FakeEnum>("Pick", "VAR");
    let opts = step.select_options().unwrap();
    assert_eq!(opts.len(), 2);
    assert_eq!(opts[0].label, "Opt1");
    assert_eq!(opts[1].value, "opt2");
  }

  // ── Builder chaining ─────────────────────────────────────

  #[test]
  fn id_builder_works_on_select() {
    let step = WizardStep::select("Q", "V", vec![("A", "a")]).id("my-step");
    assert_eq!(step.step_id(), Some("my-step"));
  }

  #[test]
  fn on_submit_builder_works_on_select() {
    let step = WizardStep::select("Q", "V", vec![("A", "a")]).on_submit(|_| Branch::Finish);
    assert!(step.branch_fn().is_some());
  }

  #[test]
  fn on_submit_accepts_closure() {
    let target = Branch::Goto("somewhere");
    let step = WizardStep::prompt("Q", "V", "D").on_submit(move |_| target);
    let bf = step.branch_fn().unwrap();
    let dummy = Value::Object(serde_json::Map::new());
    assert_eq!(bf.call(&dummy), Branch::Goto("somewhere"));
  }

  // ── Accessor coverage for Select ─────────────────────────

  #[test]
  fn select_accessors() {
    let step = WizardStep::select("Q", "V", vec![("L", "v")]);
    assert_eq!(step.question(), "Q");
    assert_eq!(step.env_var(), Some("V"));
    assert_eq!(step.step_id(), None);
    assert!(step.branch_fn().is_none());
    assert!(step.select_options().is_some());
  }

  #[test]
  fn prompt_has_no_select_options() {
    let step = WizardStep::prompt("Q", "V", "D");
    assert!(step.select_options().is_none());
    assert_eq!(step.select_default_index(), 0);
  }

  #[test]
  fn dialog_has_no_select_options() {
    let step = WizardStep::dialog("Msg");
    assert!(step.select_options().is_none());
    assert_eq!(step.env_var(), None);
  }

  // ── next_step_index ──────────────────────────────────────

  #[test]
  fn next_step_index_advances_sequentially() {
    let steps = vec![WizardStep::dialog("A"), WizardStep::dialog("B")];
    assert_eq!(next_step_index(0, &steps, &None), Some(1));
  }

  #[test]
  fn next_step_index_returns_none_at_end() {
    let steps = vec![WizardStep::dialog("A")];
    assert_eq!(next_step_index(0, &steps, &None), None);
  }

  #[test]
  fn next_step_index_uses_branch_fn_finish() {
    let steps = vec![
      WizardStep::select("Q", "V", vec![("A", "a")]).on_submit(|_| Branch::Finish),
      WizardStep::dialog("Never reached"),
    ];
    let output = Some(("V".into(), "a".into()));
    assert_eq!(next_step_index(0, &steps, &output), None);
  }

  #[test]
  fn next_step_index_uses_branch_fn_goto() {
    let steps = vec![
      WizardStep::dialog("A").on_submit(|_| Branch::Goto("target")),
      WizardStep::dialog("B"),
      WizardStep::dialog("C").id("target"),
    ];
    assert_eq!(next_step_index(0, &steps, &None), Some(2));
  }

  #[test]
  fn next_step_index_confirm_yes_goes_next() {
    let steps = vec![
      WizardStep::confirm("Sure?", "OK", Branch::Next, Branch::Finish),
      WizardStep::dialog("After"),
    ];
    let output = Some(("OK".into(), "yes".into()));
    assert_eq!(next_step_index(0, &steps, &output), Some(1));
  }

  #[test]
  fn next_step_index_confirm_no_finishes() {
    let steps = vec![
      WizardStep::confirm("Sure?", "OK", Branch::Next, Branch::Finish),
      WizardStep::dialog("After"),
    ];
    let output = Some(("OK".into(), "no".into()));
    assert_eq!(next_step_index(0, &steps, &output), None);
  }

  // ── validate_branch_targets ──────────────────────────────

  #[test]
  fn validate_branch_targets_passes_with_valid_targets() {
    let steps = vec![
      WizardStep::select("Q", "V", vec![("A", "a")]).on_submit(|_| Branch::Next),
      WizardStep::dialog("End"),
    ];
    validate_branch_targets(&steps);
  }

  #[test]
  #[should_panic(expected = "non-existent id")]
  fn validate_branch_targets_panics_on_missing_goto() {
    let steps =
      vec![WizardStep::select("Q", "V", vec![("A", "a")]).on_submit(|_| Branch::Goto("nope"))];
    validate_branch_targets(&steps);
  }

  // ── build_results_value ──────────────────────────────────

  #[test]
  fn build_results_value_with_output() {
    let output = Some(("KEY".into(), "VAL".into()));
    let val = build_results_value(&output);
    assert_eq!(val.get("KEY").and_then(|v| v.as_str()), Some("VAL"));
  }

  #[test]
  fn build_results_value_without_output() {
    let val = build_results_value(&None);
    assert_eq!(val, Value::Object(serde_json::Map::new()));
  }
}
