///|
priv struct SassColor {
  red : Double
  green : Double
  blue : Double
  alpha : Double
  format : String
  original : String?
  hsl_channels : (Double, Double, Double)?
}

///|
fn color(
  red : Double,
  green : Double,
  blue : Double,
  alpha? : Double = 1.0,
  format? : String = "auto",
  original? : String? = None,
) -> SassColor {
  { red, green, blue, alpha, format, original, hsl_channels: None, }
}

///|
fn color_hex_digit(c : UInt16) -> Int? {
  let n = c.to_int()
  if n >= 48 && n <= 57 {
    Some(n - 48)
  } else if n >= 65 && n <= 70 {
    Some(n - 55)
  } else if n >= 97 && n <= 102 {
    Some(n - 87)
  } else {
    None
  }
}

///|
fn color_literal(text : String) -> SassValue? raise ParseError {
  if named_colors.get(text.to_lower()) is Some((r, g, b, a)) {
    return Some(
      Color(
        color(
          r.to_double(),
          g.to_double(),
          b.to_double(),
          alpha=a.to_double(),
          original=Some(text),
        ),
      ),
    )
  }
  if !text.has_prefix("#") {
    return None
  }
  let n = text.length() - 1
  if ![3, 4, 6, 8].contains(n) {
    raise Invalid("invalid hex color length")
  }
  let digits = []
  for i in 1.. {
        raise Invalid("invalid hex color")
      }),
    )
  }
  let short = n <= 4
  let channel = fn(i : Int) -> Double {
    (if short {
      digits[i] * 17
    } else {
      digits[i * 2] * 16 + digits[i * 2 + 1]
    }).to_double()
  }
  let alpha = if n == 4 || n == 8 { channel(3) / 255.0 } else { 1.0 }
  Some(
    Color(
      color(
        channel(0),
        channel(1),
        channel(2),
        alpha~,
        original=if n == 3 || n == 6 { Some(text) } else { None },
      ),
    ),
  )
}

///|
fn color_hsl(
  hue : Double,
  saturation : Double,
  lightness : Double,
  alpha : Double,
  format : String,
) -> SassColor {
  let h = (hue - (hue / 360.0).floor() * 360.0) / 60.0
  let s = saturation / 100.0
  let l = lightness / 100.0
  let c = (1.0 - (2.0 * l - 1.0).abs()) * s
  let x = c * (1.0 - (h - (h / 2.0).floor() * 2.0 - 1.0).abs())
  let m = l - c / 2.0
  let (r, g, b) = if h < 1.0 {
    (c, x, 0.0)
  } else if h < 2.0 {
    (x, c, 0.0)
  } else if h < 3.0 {
    (0.0, c, x)
  } else if h < 4.0 {
    (0.0, x, c)
  } else if h < 5.0 {
    (x, 0.0, c)
  } else {
    (c, 0.0, x)
  }
  {
    ..color((r + m) * 255.0, (g + m) * 255.0, (b + m) * 255.0, alpha~, format~),
    hsl_channels: Some((h * 60.0, saturation, lightness)),
  }
}

///|
fn SassColor::hsl(self : SassColor) -> (Double, Double, Double) {
  if self.hsl_channels is Some(channels) {
    return channels
  }
  let r = self.red / 255.0
  let g = self.green / 255.0
  let b = self.blue / 255.0
  let hi = r.max(g).max(b)
  let lo = r.min(g).min(b)
  let d = hi - lo
  let l = (hi + lo) / 2.0
  if d.abs() < 0.000000000001 {
    return (0.0, 0.0, l * 100.0)
  }
  let h = if hi == r {
    (g - b) / d
  } else if hi == g {
    (b - r) / d + 2.0
  } else {
    (r - g) / d + 4.0
  }
  let h = h * 60.0
  (
    h - (h / 360.0).floor() * 360.0,
    d / (1.0 - (2.0 * l - 1.0).abs()) * 100.0,
    l * 100.0,
  )
}

///|
fn SassColor::css(self : SassColor) -> String {
  if self.original is Some(text) {
    return text
  }
  let alpha = if self.alpha == 1.0 {
    ""
  } else {
    ", " + number_text(self.alpha)
  }
  if self.format == "hsl" {
    let (h, s, l) = self.hsl()
    return (if self.alpha == 1.0 { "hsl(" } else { "hsla(" }) +
      number_text(h) +
      ", " +
      number_text(s) +
      "%, " +
      number_text(l) +
      "%" +
      alpha +
      ")"
  }
  let rgb = [self.red, self.green, self.blue]
  let integers = rgb.iter().all(n => (n - n.round()).abs() < 0.0000000001)
  if self.format == "auto" && integers && self.alpha == 1.0 {
    let alphabet = "0123456789abcdef"
    let hex = "#" +
      rgb
      .map(n => {
        let byte = n.round().to_int().max(0).min(255)
        alphabet[byte / 16:byte / 16 + 1].to_owned() +
        alphabet[byte % 16:byte % 16 + 1].to_owned()
      })
      .join("")
    let mut shortest = hex
    for name, (r, g, b, a) in named_colors {
      if a == 1 &&
        r == self.red.round().to_int() &&
        g == self.green.round().to_int() &&
        b == self.blue.round().to_int() &&
        (
          name.length() < shortest.length() ||
          (name.length() == shortest.length() && name < shortest)
        ) {
        shortest = name
      }
    }
    return shortest
  }
  let percent = !integers || self.format == "percent"
  (if self.alpha == 1.0 { "rgb(" } else { "rgba(" }) +
  rgb
  .map(n => {
    if percent {
      number_text(n / 255.0 * 100.0) + "%"
    } else {
      number_text(n.round())
    }
  })
  .join(", ") +
  alpha +
  ")"
}

///|
fn require_color(value : SassValue) -> SassColor raise ParseError {
  match value {
    Color(c) => c
    _ => raise Invalid("expected color")
  }
}

///|
fn color_component(
  value : SassValue,
  scale : Double,
  percent_only? : Bool = false,
) -> Double raise ParseError {
  let n = require_number(value)
  if n.amount.is_nan() || n.amount.is_inf() {
    raise Invalid("finite color component required")
  }
  if n.numerator == ["%"] && n.denominator.is_empty() {
    return n.amount * scale / 100.0
  }
  if n.unitless() && !percent_only {
    return n.amount
  }
  raise Invalid("invalid color component unit")
}

///|
fn color_mix(a : SassColor, b : SassColor, weight : Double) -> SassColor {
  let p = weight / 100.0
  let w = 2.0 * p - 1.0
  let alpha = a.alpha - b.alpha
  let adjusted = if (w * alpha + 1.0).abs() < 0.000000000001 {
    w
  } else {
    (w + alpha) / (1.0 + w * alpha)
  }
  let first = (adjusted + 1.0) / 2.0
  color(
    a.red * first + b.red * (1.0 - first),
    a.green * first + b.green * (1.0 - first),
    a.blue * first + b.blue * (1.0 - first),
    alpha=a.alpha * p + b.alpha * (1.0 - p),
  )
}