///|
/// Recognising shapes in a shrubbery group.
///
/// Every one of these is a pattern match over a group's item array, written
/// once here so that the rules live in one place and a reader can check the
/// surface syntax against them without following the lowering.

///|
/// Split a group's items into the head, its trailing block, and whether it had
/// alternatives.
///
/// The three structural laws of the shrubbery tree make this total: a `Block`
/// is the last item or the second-to-last with `Alts` after it, and `Alts` is
/// always last. So looking at the tail is enough, and there is no case where a
/// block hides in the middle.
fn split_block(
  items : ArrayView[@sast.Node],
) -> (ArrayView[@sast.Node], @sast.Node?, Bool) {
  let n = items.length()
  if n == 0 {
    return (items, None, false)
  }
  match items[n - 1].it {
    Alts(_) =>
      if n >= 2 {
        match items[n - 2].it {
          Block(_) => (items[0:n - 2], Some(items[n - 2]), true)
          _ => (items[0:n - 1], None, true)
        }
      } else {
        (items[0:0], None, true)
      }
    Block(_) => (items[0:n - 1], Some(items[n - 1]), false)
    _ => (items, None, false)
  }
}

///|
/// `Id(name)` immediately followed by `Parens`: a call.
///
/// Adjacency is not checked, and cannot be: shrubbery's tree does not record
/// whitespace, so `class (card)` and `class(card)` are the same nodes. That is
/// fine here -- there is nothing for the second spelling to mean instead --
/// and it is the same fact that makes an explicit relation keyword necessary
/// in a selector.
fn as_call(
  items : ArrayView[@sast.Node],
) -> (String, ArrayView[@sast.Node], ArrayView[@sast.Node])? {
  match items {
    [{ it: Id(name), .. }, { it: Parens(args), .. }, ..] =>
      Some((name, args[:], items[2:]))
    _ => None
  }
}

///|
/// `Op("--")` then `Id`: a custom property or a dashed identifier.
///
/// The two can never be confused with an ordinary name: a shrubbery identifier
/// may not begin with `-`, and `--` on its own is not a property.
fn as_dashed(items : ArrayView[@sast.Node]) -> String? {
  match items {
    [{ it: Op("--"), .. }, { it: Id(n), .. }] => Some("--" + @names.unkebab(n))
    _ => None
  }
}

///|
/// The single string literal inside a call's arguments, if that is all it has.
///
/// This is the literal escape: in a position where CSS never admits a string,
/// one means "take this name exactly, with no transform". It is how
/// `class("btn__primary")` and `ident("side_bar")` say what `kebab` cannot.
fn literal_string(args : ArrayView[@sast.Node]) -> String? {
  match args {
    [{ it: Group([{ it: Lit(Str(s)), .. }]), .. }] => Some(s)
    _ => None
  }
}

///|
/// The single bare identifier inside a call's arguments.
fn single_ident(args : ArrayView[@sast.Node]) -> String? {
  match args {
    [{ it: Group([{ it: Id(n), .. }]), .. }] => Some(n)
    _ => None
  }
}

///|
/// A call argument that names something: an identifier, transformed, or a
/// string, taken literally.
fn name_arg(args : ArrayView[@sast.Node]) -> String? {
  match single_ident(args) {
    Some(n) => Some(@names.unkebab(n))
    None => literal_string(args)
  }
}

///|
/// The single number inside a call's arguments, with its source spelling.
fn single_number(args : ArrayView[@sast.Node]) -> @ast.Number? {
  match args {
    [{ it: Group([n]), .. }] => number_of(n)
    _ => None
  }
}

///|
/// A shrubbery literal as a CSS number, keeping how it was written.
///
/// A rational is deliberately NOT handled here: `1/2` lexes as one `Rat`
/// literal, and in CSS that is two numbers with a slash between them. It is
/// unfolded where values are lowered, so `1/2` and `1 / 2` produce the same
/// three components.
fn number_of(n : @sast.Node) -> @ast.Number? {
  match n.it {
    Lit(Int_(i)) => {
      let repr = i.to_string()
      Some({ repr, value: bigint_to_double(i), is_int: true, })
    }
    Lit(Flo(f)) =>
      Some({ repr: @swrite.double_to_string(f), value: f, is_int: false, })
    _ => None
  }
}

///|
fn bigint_to_double(i : @bigint.BigInt) -> Double {
  @string.parse_double(i.to_string()) catch {
    _ => 0.0
  }
}

///|
/// The groups inside a `Parens` or `Brackets`, as views.
fn arg_groups(args : ArrayView[@sast.Node]) -> Array[ArrayView[@sast.Node]] {
  let out : Array[ArrayView[@sast.Node]] = []
  for g in args {
    match g.it {
      Group(xs) => out.push(xs[:])
      _ => out.push(one(g))
    }
  }
  out
}

///|
/// A single node as a one-element view, for the case where a group's contents
/// arrive unwrapped.
fn one(n : @sast.Node) -> ArrayView[@sast.Node] {
  let a : Array[@sast.Node] = [n]
  a[:]
}

///|
/// The span covering a run of nodes, or a fallback when it is empty.
fn span_of(
  items : ArrayView[@sast.Node],
  fallback : @basic.Span,
) -> @basic.Span {
  if items.length() == 0 {
    fallback
  } else {
    items[0].span.merge(items[items.length() - 1].span)
  }
}