///|
/// 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 and its trailing block.
///
/// 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 `div ()` and `div()` are the same nodes. There is nothing for
/// the second spelling to mean instead, so nothing is lost.
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
  }
}

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

///|
/// One argument that is a lone string literal.
fn group_string(arg : @sast.Node) -> String? {
  match arg.it {
    Group(xs) =>
      if xs.length() == 1 {
        match xs[0].it {
          Lit(Str(s)) => Some(s)
          _ => None
        }
      } else {
        None
      }
    _ => None
  }
}

///|
/// The two string literals inside a call's arguments.
fn literal_pair(args : ArrayView[@sast.Node]) -> (String, String)? {
  match args {
    [
      { it: Group([{ it: Lit(Str(a)), .. }]), .. },
      { it: Group([{ it: Lit(Str(b)), .. }]), .. },
    ] => Some((a, b))
    _ => None
  }
}

///|
/// An `@{...}` body, as one string.
///
/// At-notation is shrubbery's indentation-stripping literal text block, and it
/// is exactly what `script`, `style`, `pre` and `textarea` need. Its tree shape
/// is a `Parens` holding one `Group` holding one `Brackets` of string pieces,
/// with an explicit `"\n"` piece between lines -- so concatenating the pieces
/// reconstructs the text with its shared indentation already removed.
///
/// A body containing anything but string pieces -- an `@` escape with a nested
/// term in it -- answers `None`, and the caller falls back to reading it as
/// ordinary children.
fn as_at_text(node : @sast.Node) -> String? {
  let groups = match node.it {
    Parens(gs) => gs
    _ => return None
  }
  if groups.length() != 1 {
    return None
  }
  let inner = match groups[0].it {
    Group(xs) => xs
    _ => return None
  }
  if inner.length() != 1 {
    return None
  }
  let pieces = match inner[0].it {
    Brackets(ps) => ps
    _ => return None
  }
  let buf = StringBuilder()
  for p in pieces {
    match p.it {
      Group(xs) =>
        if xs.length() == 1 {
          match xs[0].it {
            Lit(Str(s)) => buf.write_string(s)
            _ => return None
          }
        } else {
          return None
        }
      _ => return None
    }
  }
  Some(buf.to_string())
}

///|
/// The groups inside a `Block`.
fn block_groups(node : @sast.Node) -> Array[@sast.Node] {
  match node.it {
    Block(gs) => gs
    _ => []
  }
}