use std::io::Write;

use crossterm::{
  queue,
  style::{Color, Print, ResetColor, SetForegroundColor},
};

use crate::{Component, Renderer};

const ANSI_GRAYSCALE_START: u8 = 232;
const GRAYSCALE_RANGE: f64 = 15.0;

pub struct HintComponent {
  pub text: &'static str,
  pub fade: f64,
}

impl HintComponent {
  pub fn new(text: &'static str) -> Self {
    Self { text, fade: 0.0 }
  }
}

#[cfg(not(tarpaulin_include))]
impl Component for HintComponent {
  fn render<W: Write>(&self, renderer: &mut Renderer<W>) -> std::io::Result<u16> {
    if self.fade <= 0.0 {
      return Ok(0);
    }
    renderer.with_panel(|writer, _panel, _rng| {
      let color = fade_color(self.fade);
      queue!(
        writer,
        SetForegroundColor(color),
        Print(self.text),
        ResetColor
      )?;
      Ok(1)
    })
  }
}

fn fade_color(fade: f64) -> Color {
  if fade >= 1.0 {
    return Color::DarkGrey;
  }
  Color::AnsiValue(ANSI_GRAYSCALE_START + (fade * GRAYSCALE_RANGE).round() as u8)
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::LayoutPanel;
  use crate::test_helpers::test_renderer;

  #[test]
  fn new_starts_invisible() {
    let hint = HintComponent::new("(hit enter)");
    assert_eq!(hint.fade, 0.0);
    assert_eq!(hint.text, "(hit enter)");
  }

  #[test]
  fn fade_color_returns_dark_grey_at_full() {
    assert_eq!(fade_color(1.0), Color::DarkGrey);
    assert_eq!(fade_color(1.5), Color::DarkGrey);
  }

  #[test]
  fn fade_color_returns_ansi_gradient_below_one() {
    let color = fade_color(0.5);
    match color {
      Color::AnsiValue(v) => {
        assert!(v >= 232);
        assert!(v <= 247);
      }
      _ => panic!("expected AnsiValue"),
    }
  }

  #[test]
  fn fade_color_at_zero_is_darkest() {
    assert_eq!(fade_color(0.0), Color::AnsiValue(ANSI_GRAYSCALE_START));
  }

  #[test]
  fn render_skips_when_invisible() {
    let hint = HintComponent::new("test");
    let panel = LayoutPanel::centered(80, 4, 10);
    let mut renderer = test_renderer();
    renderer.draw(&hint, panel).unwrap();
    assert!(renderer.writer.is_empty());
  }

  #[test]
  fn render_writes_when_visible() {
    let mut hint = HintComponent::new("hello");
    hint.fade = 1.0;
    let panel = LayoutPanel::centered(80, 5, 10);
    let mut renderer = test_renderer();
    renderer.draw(&hint, panel).unwrap();
    assert!(!renderer.writer.is_empty());
    let output = String::from_utf8(renderer.writer).unwrap();
    assert!(output.contains("hello"));
  }
}
