///|
fn clamp_byte(value : Int) -> Byte {
  let clamped = if value < 0 { 0 } else if value > 255 { 255 } else { value }
  clamped.to_byte()
}

///|
fn hex_byte(value : Byte) -> String {
  let text = value.to_int().to_string(radix=16).to_upper()
  if text.length() == 1 {
    "0\{text}"
  } else if text.length() == 0 {
    "00"
  } else {
    text
  }
}

///|
fn parse_hex_byte(text : StringView, start : Int) -> Int {
  let end = start + 2
  if start < 0 || text.length() < end {
    return 0
  }
  let view = text[start:end]
  @string.parse_int(view, base=16) catch {
    _ => 0
  }
}

///|
pub fn rgb_to_hsl(r : Byte, g : Byte, b : Byte) -> (Double, Double, Double) {
  let fr = r.to_double() / 255.0
  let fg = g.to_double() / 255.0
  let fb = b.to_double() / 255.0
  let max_val = if fr > fg {
    if fr > fb {
      fr
    } else {
      fb
    }
  } else if fg > fb {
    fg
  } else {
    fb
  }
  let min_val = if fr < fg {
    if fr < fb {
      fr
    } else {
      fb
    }
  } else if fg < fb {
    fg
  } else {
    fb
  }
  let l = (max_val + min_val) / 2.0
  if max_val == min_val {
    return (0.0, 0.0, l)
  }
  let d = max_val - min_val
  let s = if l > 0.5 {
    d / (2.0 - max_val - min_val)
  } else {
    d / (max_val + min_val)
  }
  let mut h = if max_val == fr {
    let mut value = (fg - fb) / d
    if fg < fb {
      value = value + 6.0
    }
    value
  } else if max_val == fg {
    (fb - fr) / d + 2.0
  } else {
    (fr - fg) / d + 4.0
  }
  h = h / 6.0
  (h, s, l)
}

///|
fn hue_to_rgb(p : Double, q : Double, t : Double) -> Double {
  let mut value = t
  if value < 0.0 {
    value = value + 1.0
  }
  if value > 1.0 {
    value = value - 1.0
  }
  if value < 1.0 / 6.0 {
    return p + (q - p) * 6.0 * value
  }
  if value < 0.5 {
    return q
  }
  if value < 2.0 / 3.0 {
    return p + (q - p) * (2.0 / 3.0 - value) * 6.0
  }
  p
}

///|
pub fn hsl_to_rgb(h : Double, s : Double, l : Double) -> (Byte, Byte, Byte) {
  let (fr, fg, fb) = if s == 0.0 {
    (l, l, l)
  } else {
    let q = if l < 0.5 { l * (1.0 + s) } else { l + s - s * l }
    let p = 2.0 * l - q
    (
      hue_to_rgb(p, q, h + 1.0 / 3.0),
      hue_to_rgb(p, q, h),
      hue_to_rgb(p, q, h - 1.0 / 3.0),
    )
  }
  let r = clamp_byte((fr * 255.0 + 0.5).to_int())
  let g = clamp_byte((fg * 255.0 + 0.5).to_int())
  let b = clamp_byte((fb * 255.0 + 0.5).to_int())
  (r, g, b)
}

///|
pub fn theme_color(base_color : String, tint : Double) -> String {
  if tint == 0.0 {
    return "FF\{base_color}"
  }
  let view = base_color[:]
  let r_val = parse_hex_byte(view, 0)
  let g_val = parse_hex_byte(view, 2)
  let b_val = parse_hex_byte(view, 4)
  let (h, s, l) = rgb_to_hsl(
    clamp_byte(r_val),
    clamp_byte(g_val),
    clamp_byte(b_val),
  )
  let adjusted_l = if tint < 0.0 {
    l * (1.0 + tint)
  } else {
    l * (1.0 - tint) + (1.0 - (1.0 - tint))
  }
  let (r, g, b) = hsl_to_rgb(h, s, adjusted_l)
  "FF\{hex_byte(r)}\{hex_byte(g)}\{hex_byte(b)}"
}