///|
/// Hue, saturation, value and alpha representation.
pub(all) struct Hsv {
  hue : Double
  saturation : Double
  value : Double
  alpha : Double
} derive(Debug)

///|
pub fn Hsv::hue(self : Hsv) -> Double {
  self.hue
}

///|
pub fn Hsv::saturation(self : Hsv) -> Double {
  self.saturation
}

///|
pub fn Hsv::value(self : Hsv) -> Double {
  self.value
}

///|
pub fn Hsv::alpha(self : Hsv) -> Double {
  self.alpha
}

///|
fn wrap_hue(hue : Double) -> Double {
  let wrapped = hue % 360.0
  if wrapped < 0.0 {
    wrapped + 360.0
  } else {
    wrapped
  }
}

///|
pub fn hsv(
  hue~ : Double,
  saturation~ : Double,
  value~ : Double,
  alpha? : Double = 1.0,
) -> Hsv {
  {
    hue: wrap_hue(hue),
    saturation: clamp01(saturation),
    value: clamp01(value),
    alpha: clamp01(alpha),
  }
}

///|
pub fn Rgba::to_hsv(self : Rgba) -> Hsv {
  let maximum = self.r.max(self.g).max(self.b)
  let minimum = self.r.min(self.g).min(self.b)
  let chroma = maximum - minimum
  let hue = if chroma == 0.0 {
    0.0
  } else if maximum == self.r {
    60.0 * ((self.g - self.b) / chroma % 6.0)
  } else if maximum == self.g {
    60.0 * ((self.b - self.r) / chroma + 2.0)
  } else {
    60.0 * ((self.r - self.g) / chroma + 4.0)
  }
  let saturation = if maximum == 0.0 { 0.0 } else { chroma / maximum }
  hsv(hue~, saturation~, value=maximum, alpha=self.a)
}

///|
pub fn Hsv::to_rgba(self : Hsv) -> Rgba {
  let chroma = self.value * self.saturation
  let section = self.hue / 60.0
  let x = chroma * (1.0 - (section % 2.0 - 1.0).abs())
  let (red, green, blue) = if section < 1.0 {
    (chroma, x, 0.0)
  } else if section < 2.0 {
    (x, chroma, 0.0)
  } else if section < 3.0 {
    (0.0, chroma, x)
  } else if section < 4.0 {
    (0.0, x, chroma)
  } else if section < 5.0 {
    (x, 0.0, chroma)
  } else {
    (chroma, 0.0, x)
  }
  let match_value = self.value - chroma
  rgba(
    r=red + match_value,
    g=green + match_value,
    b=blue + match_value,
    a=self.alpha,
  )
}

///|
fn hue_delta(start : Double, end : Double) -> Double {
  let mut delta = (end - start) % 360.0
  if delta > 180.0 {
    delta = delta - 360.0
  } else if delta < -180.0 {
    delta = delta + 360.0
  }
  delta
}

///|
pub fn interpolate_hsv(start : Hsv, end : Hsv, t : Double) -> Hsv {
  hsv(
    hue=start.hue + hue_delta(start.hue, end.hue) * t,
    saturation=interpolate_scalar(start.saturation, end.saturation, t),
    value=interpolate_scalar(start.value, end.value, t),
    alpha=interpolate_scalar(start.alpha, end.alpha, t),
  )
}

///|
pub fn interpolate_rgba_hsv(start : Rgba, end : Rgba, t : Double) -> Rgba {
  interpolate_hsv(start.to_hsv(), end.to_hsv(), t).to_rgba()
}

///|
pub fn Rgba::luminance(self : Rgba) -> Double {
  0.2126 * self.r + 0.7152 * self.g + 0.0722 * self.b
}