///|
/// Declaration values.
///
/// A value is the one group inside a declaration's block. Most of it lowers one
/// node at a time; the interesting cases are the calls that are not CSS
/// functions -- units, `percent`, `hex`, `important` -- and the rational
/// literal, which the shrubbery lexer produces for `1/2` and CSS reads as three
/// things.

///|
/// A value group, and whether `important()` was in it.
fn Lowerer::value_of(
  self : Lowerer,
  g : @sast.Node,
) -> (Array[@ast.ComponentValue], Bool) raise @err.ShrubCssError {
  let items = match g.it {
    Group(xs) => xs[:]
    _ => return ([], false)
  }
  self.values(items)
}

///|
fn Lowerer::values(
  self : Lowerer,
  items : ArrayView[@sast.Node],
) -> (Array[@ast.ComponentValue], Bool) raise @err.ShrubCssError {
  let out : Array[@ast.ComponentValue] = []
  let mut important = false
  let mut i = 0
  while i < items.length() {
    let rest = items[i:]
    // `--brand` in value position is a dashed identifier, which is what
    // `var(--brand)` wants.
    match
      as_dashed(rest[0:if rest.length() >= 2 { 2 } else { rest.length() }]) {
      Some(name) => {
        out.push(Ident(name))
        i = i + 2
        continue
      }
      None => ()
    }
    match as_call(rest) {
      Some((name, args, _)) => {
        match self.value_call(name, args, items[i]) {
          Call(v) => out.push(v)
          Important => important = true
        }
        i = i + 2
        continue
      }
      None => ()
    }
    let n = items[i]
    match n.it {
      Id(name) => out.push(Ident(@names.unkebab(name)))
      Lit(Str(s)) => out.push(Str(s))
      Lit(Int_(_)) | Lit(Flo(_)) =>
        match number_of(n) {
          Some(num) => out.push(Num(num))
          None => out.push(Bogus(self.bogus(BadSelector, n)))
        }
      // `1/2` is one rational literal to the lexer and three things to CSS.
      // Unfolding it here is what makes `1/2` and `1 / 2` agree.
      Lit(Rat(a, b)) => {
        out.push(
          Num({ repr: a.to_string(), value: bigint_to_double(a), is_int: true, }),
        )
        out.push(Slash)
        out.push(
          Num({ repr: b.to_string(), value: bigint_to_double(b), is_int: true, }),
        )
      }
      Op("/") => out.push(Slash)
      Op(o) => out.push(Delim(o))
      Parens(gs) => out.push(Paren(self.comma_list(gs[:])))
      // A bracket is CSS's comma-separated value list, which has no brackets
      // of its own -- so it is spliced in. CSS's own `[...]` is written
      // `bracket(...)`, because the two cannot share a spelling: a comma may
      // only appear inside a bracket, so the bracket has to mean the list.
      Brackets(gs) =>
        for v in self.comma_list(gs[:]) {
          out.push(v)
        }
      Block(_) => out.push(Bogus(self.bogus(SemicolonDeclarations, n)))
      Braces(_) => out.push(Bogus(self.bogus(BracesUnsupported, n)))
      Quotes(_) => out.push(Bogus(self.bogus(QuotesUnsupported, n)))
      Kw(k) => out.push(Bogus(self.bogus(StrayKeyword(k), n)))
      _ => out.push(Bogus(self.bogus(BadSelector, n)))
    }
    i = i + 1
  }
  (out, important)
}

///|
/// What a call in value position turned out to be.
priv enum CallResult {
  Call(@ast.ComponentValue)
  /// `important()` is not a value at all: it sets a flag on the declaration.
  Important
}

///|
/// A call in value position.
///
/// The order matters. A unit is checked before an ordinary function, but only
/// when its arguments are exactly one number -- so `s(0.2)` is two tenths of a
/// second while a hypothetical `s(a, b)` stays a function call. That guard is
/// what keeps short unit names from swallowing the function namespace.
fn Lowerer::value_call(
  self : Lowerer,
  name : String,
  args : ArrayView[@sast.Node],
  at : @sast.Node,
) -> CallResult raise @err.ShrubCssError {
  match name {
    "important" => if args.length() == 0 { return Important }
    "percent" =>
      match single_number(args) {
        Some(n) => return Call(Percentage(n))
        None => ()
      }
    "hex" => return Call(self.hex_of(args, at))
    "url" =>
      match literal_string(args) {
        Some(s) => return Call(Url(s))
        None => ()
      }
    "ident" =>
      match literal_string(args) {
        Some(s) => return Call(Ident(s))
        None => ()
      }
    "dim" => return Call(self.dim_of(args, at))
    "delim" =>
      match literal_string(args) {
        Some(s) => return Call(Delim(s))
        None => ()
      }
    "bracket" => return Call(Bracket(self.comma_list(args)))
    _ => ()
  }
  // A unit, if the name is one and the argument is a single number.
  match @names.unit_of(name) {
    Some(unit) =>
      match single_number(args) {
        Some(n) => return Call(Dimension(n, unit))
        None => ()
      }
    None => ()
  }
  Call(Function(@names.unkebab(name), self.comma_list(args)))
}

///|
/// `hex(f7f7f7)` and `hex("007700")`.
///
/// The string form is not a stylistic alternative -- it is required whenever
/// the digits start with a `0`, because by the time this layer runs the lexer
/// has already read `007700` as the integer 7700 and the zeros are gone. There
/// is no way to recover them, so this refuses rather than guessing, and the
/// diagnostic names the fix.
fn Lowerer::hex_of(
  self : Lowerer,
  args : ArrayView[@sast.Node],
  at : @sast.Node,
) -> @ast.ComponentValue raise @err.ShrubCssError {
  match literal_string(args) {
    Some(s) => return Hex(s)
    None => ()
  }
  match single_ident(args) {
    Some(n) => return Hex(n)
    None => ()
  }
  match single_number(args) {
    Some(n) =>
      if is_hex_length(n.repr.length()) {
        Hex(n.repr)
      } else {
        Bogus(self.bogus(HexDigitsLost, at))
      }
    None => Bogus(self.bogus(BadCallShape("hex"), at))
  }
}

///|
/// The digit counts CSS admits: `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`.
fn is_hex_length(n : Int) -> Bool {
  n == 3 || n == 4 || n == 6 || n == 8
}

///|
/// `dim(10, "newunit")`: the escape for a unit this library does not know.
fn Lowerer::dim_of(
  self : Lowerer,
  args : ArrayView[@sast.Node],
  at : @sast.Node,
) -> @ast.ComponentValue raise @err.ShrubCssError {
  let gs = arg_groups(args)
  if gs.length() == 2 {
    match (gs[0], gs[1]) {
      ([n], [{ it: Lit(Str(u)), .. }]) =>
        match number_of(n) {
          Some(num) => return Dimension(num, u)
          None => ()
        }
      _ => ()
    }
  }
  Bogus(self.bogus(BadCallShape("dim"), at))
}

///|
/// A call's or bracket's arguments, with commas put back between the groups.
///
/// Shrubbery turns `a, b` inside brackets into two groups, so the comma is
/// structural there and absent from the tree. CSS wants it as a component
/// value, since a comma means different things in different properties and the
/// generic value model does not interpret it.
fn Lowerer::comma_list(
  self : Lowerer,
  args : ArrayView[@sast.Node],
) -> Array[@ast.ComponentValue] raise @err.ShrubCssError {
  let out : Array[@ast.ComponentValue] = []
  let mut first = true
  for g in args {
    if !first {
      out.push(Comma)
    }
    first = false
    let items = match g.it {
      Group(xs) => xs[:]
      _ => one(g)
    }
    let (vs, _) = self.values(items)
    for v in vs {
      out.push(v)
    }
  }
  out
}