///|
/// The sides a `mask--*` utility drives, in the order upstream emits them.
let mask_edges : Array[(String, Array[String])] = [
  ("mask-t", ["top"]),
  ("mask-r", ["right"]),
  ("mask-b", ["bottom"]),
  ("mask-l", ["left"]),
  ("mask-x", ["right", "left"]),
  ("mask-y", ["top", "bottom"]),
]

///|
/// The plumbing every mask utility re-declares, so that several of them
/// combine: the image is composed from the three gradient slots, and the linear
/// slot from the four sides.
fn mask_composition_declarations() -> Array[Declaration] {
  [
    decl(
      "mask-image", "var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic)",
    ),
    decl("mask-composite", "intersect"),
    decl(
      "--tw-mask-linear", "var(--tw-mask-left), var(--tw-mask-right), var(--tw-mask-bottom), var(--tw-mask-top)",
    ),
  ]
}

///|
/// A mask stop is a position or a color, the same split the gradient stops
/// make — but a bare number here is a spacing multiplier, and an untyped
/// arbitrary value is a color unless it reads as a measurement.
fn mask_stop_value(
  theme : Map[String, String],
  key : String,
) -> (String, Bool)? {
  // A percentage stop is a whole number here, unlike the gradient stops.
  if key.has_suffix("%") {
    let number = key[:key.length() - 1].to_owned()
    return if is_nonnegative_integer(number) { Some((key, true)) } else { None }
  }
  match arbitrary_typed_value(key) {
    Some((value, Some("color"))) => return Some((value, false))
    Some((value, Some(_))) => return Some((value, true))
    // Without a typehint a mask stop is a position: upstream lists the length
    // types before the color one, and a bare `var()` infers nothing. A negative
    // measurement is not a position at all.
    Some((value, None)) =>
      return if value.has_prefix("-") && is_number_with_unit(value) {
        None
      } else {
        Some((value, true))
      }
    None => ()
  }
  match keyword_color(key) {
    Some(value) => return Some((value, false))
    None => ()
  }
  match theme_css_value(theme, "--color-\{key}") {
    Some(value) => return Some((value, false))
    None => ()
  }
  numeric_spacing(theme, key).map(fn(value) { (value, true) })
}

///|
/// `mask-t-from-50%` and friends: a linear gradient masking one edge, or two
/// for the `x` and `y` forms. The value is a stop, read the same way a gradient
/// stop is — a position or a color.
fn mask_edge_utility(
  theme : Map[String, String],
  name : String,
) -> Array[Declaration]? {
  guard name.has_prefix("mask-") else { return None }
  for entry in mask_edges {
    let (root, sides) = entry
    for stop in ["from", "to"] {
      let prefix = "\{root}-\{stop}"
      guard prefixed_value(name, prefix) is Some(key) else { continue }
      guard mask_stop_value(theme, key) is Some((value, position)) else {
        return None
      }
      let declarations = mask_composition_declarations()
      for side in sides {
        declarations.push(
          decl(
            "--tw-mask-\{side}",
            "linear-gradient(to \{side}, var(--tw-mask-\{side}-from-color) var(--tw-mask-\{side}-from-position), var(--tw-mask-\{side}-to-color) var(--tw-mask-\{side}-to-position))",
          ),
        )
        let slot = if position { "position" } else { "color" }
        declarations.push(decl("--tw-mask-\{side}-\{stop}-\{slot}", value))
      }
      return Some(declarations)
    }
  }
  None
}

///|
/// The three gradient mask slots. Each composes a gradient of its own kind from
/// a `*-stops` list, and takes `from`/`to` stops the way the edges do.
let mask_gradients : Array[(String, String, String)] = [
  (
    "mask-linear", "linear", "var(--tw-mask-linear-position), var(--tw-mask-linear-from-color) var(--tw-mask-linear-from-position), var(--tw-mask-linear-to-color) var(--tw-mask-linear-to-position)",
  ),
  (
    "mask-conic", "conic", "from var(--tw-mask-conic-position), var(--tw-mask-conic-from-color) var(--tw-mask-conic-from-position), var(--tw-mask-conic-to-color) var(--tw-mask-conic-to-position)",
  ),
  (
    "mask-radial", "radial", "var(--tw-mask-radial-shape) var(--tw-mask-radial-size) at var(--tw-mask-radial-position), var(--tw-mask-radial-from-color) var(--tw-mask-radial-from-position), var(--tw-mask-radial-to-color) var(--tw-mask-radial-to-position)",
  ),
]

///|
/// `mask-linear-45`, `mask-conic-from-20%`, `mask-radial-[circle]` and the rest
/// of the gradient masks.
fn mask_gradient_utility(
  theme : Map[String, String],
  name : String,
) -> Array[Declaration]? {
  guard name.has_prefix("mask-") else { return None }
  for entry in mask_gradients {
    let (root, kind, stops) = entry
    let slot = "--tw-mask-\{kind}"
    for stop in ["from", "to"] {
      guard prefixed_value(name, "\{root}-\{stop}") is Some(key) else {
        continue
      }
      guard mask_stop_value(theme, key) is Some((value, position)) else {
        return None
      }
      let declarations = mask_composition_declarations()
      declarations.remove(2) |> ignore
      declarations.push(decl("\{slot}-stops", stops))
      declarations.push(decl(slot, "\{kind}-gradient(var(\{slot}-stops))"))
      let field = if position { "position" } else { "color" }
      declarations.push(decl("\{slot}-\{stop}-\{field}", value))
      return Some(declarations)
    }
    // `mask-radial-at-*` moves the radial center; the keyword forms are statics.
    if kind == "radial" && prefixed_value(name, "\{root}-at") is Some(key) {
      // The keyword positions are statics; only an arbitrary one lands here.
      match arbitrary_value(key) {
        Some(value) => return Some([decl("\{slot}-position", value)])
        None => ()
      }
    }
    guard prefixed_value(name, root) is Some(key) else { continue }
    // The bare form sets the gradient's own parameter: an angle for the linear
    // and conic masks, a size for the radial one.
    let property = if kind == "radial" {
      "\{slot}-size"
    } else {
      "\{slot}-position"
    }
    let resolved = match arbitrary_value(key) {
      Some(value) => Some(value)
      None =>
        if kind == "radial" {
          None
        } else if key == "0" {
          // As with the spacing scale, a zero multiplier folds away.
          Some("0deg")
        } else if key == "1" {
          Some("1deg")
        } else if is_nonnegative_integer(key) {
          Some("calc(1deg * \{key})")
        } else {
          None
        }
    }
    // A keyword form — `mask-radial-at-bottom`, `mask-radial-circle` — is a
    // static utility, so leave it to that rather than claiming the name.
    guard resolved is Some(value) else { continue }
    let declarations = mask_composition_declarations()
    declarations.remove(2) |> ignore
    declarations.push(
      decl(slot, "\{kind}-gradient(var(\{slot}-stops, var(\{property})))"),
    )
    declarations.push(decl(property, value))
    return Some(declarations)
  }
  None
}

///|
/// `mask-position-*` and `mask-size-*` write their property outright.
fn mask_placement_utility(
  theme : Map[String, String],
  name : String,
) -> Array[Declaration]? {
  for entry in [("mask-position", "mask-position"), ("mask-size", "mask-size")] {
    let (root, property) = entry
    guard prefixed_value(name, root) is Some(key) else { continue }
    let value = match arbitrary_value(key) {
      Some(value) => Some(value)
      None => theme_css_value(theme, "--\{property}-\{key}")
    }
    guard value is Some(value) else { return None }
    return Some([decl(property, value)])
  }
  None
}

///|
/// `mask-radial-at-*` moves the radial center. The keyword forms name a corner
/// or an edge; the two-word ones join with a space.
fn mask_radial_position_utility(name : String) -> Array[Declaration]? {
  guard prefixed_value(name, "mask-radial-at") is Some(key) else { return None }
  for
    position in [
      "center", "top", "right", "bottom", "left", "top-left", "top-right", "bottom-left",
      "bottom-right",
    ] {
    if key == position {
      return Some([
        decl("--tw-mask-radial-position", replace_all(position, "-", " ")),
      ])
    }
  }
  None
}