//! a slider element that allows selecting a value in a range

use derive_setters::Setters;
use glam::{Vec2, vec2};

use crate::{
  draw::UiDrawCommand,
  element::{MeasureContext, ProcessContext, UiElement},
  layout::{Size2d, compute_size},
  measure::Response,
  rect::FillColor,
  signal::{trigger::SignalTriggerArg, Signal},
};

/// Follow mode for the slider
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub enum SliderFollowMode {
  /// Slider will change based on the absolute mouse position in the slider
  ///
  /// This is the default mode and is recommended for most use cases
  #[default]
  Absolute,

  /// Slider will change based on the difference between the current and starting mouse position
  ///
  /// This is an experimental option and does not currently work well for sliders with large step sizes
  Relative,
}

/// A slider element that allows selecting a value in a range
#[derive(Setters)]
#[setters(prefix = "with_")]
pub struct Slider {
  /// Value of the slider, should be in range 0..1
  ///
  /// Out of range values will be clamped
  pub value: f32,

  /// Size of the element
  #[setters(into)]
  pub size: Size2d,

  /// Color of the slider handle
  #[setters(into)]
  pub handle_color: FillColor,

  /// Color of the slider track
  #[setters(into)]
  pub track_color: FillColor,

  /// Follow mode
  pub follow_mode: SliderFollowMode,

  #[setters(skip)]
  pub on_change: Option<SignalTriggerArg<f32>>,
}

impl Default for Slider {
  fn default() -> Self {
    Self {
      value: 0.0,
      size: Size2d::default(),
      handle_color: (0.0, 0.0, 1.0).into(),
      track_color: (0.5, 0.5, 0.5).into(),
      follow_mode: SliderFollowMode::default(),
      on_change: None
    }
  }
}

impl Slider {
  pub const DEFAULT_HEIGHT: f32 = 21.0;

  pub fn new(value: f32) -> Self {
    Self {
      value,
      ..Default::default()
    }
  }

  pub fn on_change<S: Signal, T: Fn(f32) -> S + 'static>(self, f: T) -> Self {
    Self {
      on_change: Some(SignalTriggerArg::new(f)),
      ..self
    }
  }
}

impl UiElement for Slider {
  fn name(&self) -> &'static str {
    "Slider"
  }

  fn measure(&self, ctx: MeasureContext) -> Response {
    Response {
      size: compute_size(ctx.layout, self.size, (ctx.layout.max_size.x, Self::DEFAULT_HEIGHT).into()),
      ..Default::default()
    }
  }

  fn process(&self, ctx: ProcessContext) {
    //TODO: unhardcode this
    let bgrect_height_ratio = 0.33;

    ctx.draw.add(UiDrawCommand::Rectangle {
      position: ctx.layout.position + ctx.measure.size * vec2(0., 0.5 - bgrect_height_ratio / 2.),
      size: ctx.measure.size * vec2(1., bgrect_height_ratio),
      color: self.track_color.into(),
      texture: None,
      rounded_corners: None,
    });

    let value = self.value.clamp(0., 1.);
    let handle_size = vec2(15., ctx.measure.size.y);
    ctx.draw.add(UiDrawCommand::Rectangle {
      position: ctx.layout.position + ((ctx.measure.size.x - handle_size.x) * value) * Vec2::X,
      size: handle_size,
      color: self.handle_color.into(),
      texture: None,
      rounded_corners: None,
    });

    //handle events
    if let Some(res) = ctx.input.check_active(ctx.measure.rect(ctx.layout.position)) {
      let new_value = match self.follow_mode {
        SliderFollowMode::Absolute => ((res.position_in_rect.x - handle_size.x / 2.) / (ctx.measure.size.x - handle_size.x)).clamp(0., 1.),
        SliderFollowMode::Relative => {
          let delta = res.position_in_rect.x - res.last_position_in_rect.x;
          let delta_ratio = delta / (ctx.measure.size.x - handle_size.x);
          (self.value + delta_ratio).clamp(0., 1.)
        }
      };
      if let Some(signal) = &self.on_change {
        signal.fire(ctx.signal, new_value);
      }
      //TODO call signal with new value
    }
  }
}

//TODO