///|
/// Selectors.
///
/// Two passes. The first splits on relation keywords, which is the only
/// structure a selector has; the second reads each run between them as one
/// compound. Juxtaposition inside a run is a compound and never a descendant --
/// shrubbery's tree does not record whitespace, so a space cannot mean
/// anything, and a relation has to be written.

///|
/// A rule's head as a selector list.
///
/// A `[a, b]` bracket at the head is the list. `is(a, b)` is NOT: `:is()` gives
/// every arm the specificity of its most specific member, so spelling a list
/// that way would silently change what the stylesheet matches. The bracket is
/// the list; `is()` stays the pseudo-class it already was.
fn Lowerer::selector_list(
  self : Lowerer,
  head : ArrayView[@sast.Node],
  at : @basic.Span,
) -> Array[@ast.Selector] raise @err.ShrubCssError {
  match head {
    [{ it: Brackets(gs), .. }] => {
      let out : Array[@ast.Selector] = []
      for g in gs {
        let items = match g.it {
          Group(xs) => xs[:]
          _ => one(g)
        }
        out.push(self.selector(items, g.span))
      }
      if out.length() == 0 {
        out.push(Bogus(self.bogus_at(BadSelector, at)))
      }
      out
    }
    _ => [self.selector(head, at)]
  }
}

///|
/// One selector.
fn Lowerer::selector(
  self : Lowerer,
  items : ArrayView[@sast.Node],
  at : @basic.Span,
) -> @ast.Selector raise @err.ShrubCssError {
  if items.length() == 0 {
    return Bogus(self.bogus_at(BadSelector, at))
  }
  // Split into runs separated by relation keywords.
  let runs : Array[ArrayView[@sast.Node]] = []
  let combs : Array[@ast.Combinator] = []
  let mut from = 0
  for i, n in items {
    match n.it {
      Kw(k) =>
        match @names.combinator_of(k) {
          Some(c) => {
            runs.push(items[from:i])
            combs.push(to_ast_combinator(c))
            from = i + 1
          }
          // Not a relation. Left for `compound`, which sees the same node
          // and has somewhere to put the `Bogus` -- reporting here too would
          // make one mistake into two complaints.
          None => ()
        }
      _ => ()
    }
  }
  runs.push(items[from:])
  // A leading empty run means the selector began with a relation, which a
  // nested rule may do: `~child first_child()`.
  let leading = runs.length() > 0 && runs[0].length() == 0 && combs.length() > 0
  let start = if leading { 1 } else { 0 }
  if runs[runs.length() - 1].length() == 0 {
    self.error(DanglingCombinator, span_of(items, at))
  }
  let mut acc : @ast.Selector? = None
  let mut ci = if leading { 1 } else { 0 }
  let mut ri = start
  while ri < runs.length() {
    let run = runs[ri]
    if run.length() == 0 {
      ri = ri + 1
      ci = ci + 1
      continue
    }
    let comp = self.compound(run, at)
    acc = match acc {
      None => Some(Simple(comp))
      Some(prev) => {
        let c = if ci - 1 < combs.length() && ci >= 1 {
          combs[ci - 1]
        } else {
          Descendant
        }
        Some(Complex(prev, c, comp))
      }
    }
    ri = ri + 1
    ci = ci + 1
  }
  let sel = match acc {
    Some(s) => s
    None => Bogus(self.bogus_at(BadSelector, at))
  }
  if leading {
    Relative(combs[0], sel)
  } else {
    sel
  }
}

///|
fn to_ast_combinator(c : @names.Combinator) -> @ast.Combinator {
  match c {
    Descendant => Descendant
    Child => Child
    NextSibling => NextSibling
    SubsequentSibling => SubsequentSibling
    Column => Column
  }
}

///|
/// One element's worth: a type selector and its qualifiers.
fn Lowerer::compound(
  self : Lowerer,
  items : ArrayView[@sast.Node],
  at : @basic.Span,
) -> @ast.Compound raise @err.ShrubCssError {
  let quals : Array[@ast.Qualifier] = []
  let mut type_sel : @ast.TypeSelector? = None
  let mut ns : @ast.NsPrefix? = None
  let mut i = 0
  while i < items.length() {
    let rest = items[i:]
    match as_call(rest) {
      Some((name, args, _)) => {
        i = i + 2
        match self.selector_call(name, args, items[i - 2], ns) {
          Type(t) => {
            if type_sel is Some(_) {
              self.error(TypeSelectorNotFirst, items[i - 2].span)
            }
            type_sel = Some(t)
            ns = None
          }
          // A namespace binds to its type selector, and the surface writes it
          // AFTER the name -- `circle ns(svg)` -- so when a type has already
          // been read the prefix is applied to it rather than left pending.
          Ns(p) =>
            match type_sel {
              Some(Named(_, n)) => type_sel = Some(Named(Some(p), n))
              Some(Universal(_)) => type_sel = Some(Universal(Some(p)))
              None => ns = Some(p)
            }
          Qual(q) => quals.push(q)
        }
        continue
      }
      None => ()
    }
    let n = items[i]
    match n.it {
      Id(name) =>
        // A bare identifier is a type selector, and only the first one can be:
        // two touching element names is the mistake a CSS habit produces, so
        // it gets its own diagnostic naming the relation.
        if has_bogus(quals) {
          // The compound is already broken and already reported. A second
          // complaint about the same run is noise.
          quals.push(Bogus(bogus_node(BadSelector, n)))
        } else if type_sel is Some(_) || quals.length() > 0 {
          // Unless the first name was an at-rule's. `@media screen:` parses
          // cleanly -- at-notation swallows the `@` and leaves `media screen`
          // behind -- so this is the only place the mistake can be caught, and
          // "an at-rule is a call" is the useful thing to say, not "these two
          // names touch".
          let kind : @kind.ErrorKind = if at_rule_head(type_sel) {
            SigilSelector("@")
          } else {
            TypeSelectorNotFirst
          }
          self.error(kind, n.span)
          quals.push(Bogus(bogus_node(kind, n)))
        } else {
          type_sel = Some(Named(ns, @names.unkebab(name)))
          ns = None
        }
      // CSS's own sigils, which this syntax does not use. Each gets the
      // sentence naming what to write instead.
      Op(".") => {
        quals.push(Bogus(self.bogus(SigilSelector("."), n)))
        // Swallow the name the sigil was attached to, so `.card` is one
        // complaint rather than a sigil complaint and then a stray-name one.
        match items[i + 1:] {
          [{ it: Id(_), .. }, ..] => i = i + 1
          _ => ()
        }
      }
      Op("&") => quals.push(Bogus(self.bogus(SigilSelector("&"), n)))
      Op("*") => quals.push(Bogus(self.bogus(SigilSelector("*"), n)))
      Op(">") => quals.push(Bogus(self.bogus(OperatorCombinator(">"), n)))
      Op("+") => quals.push(Bogus(self.bogus(OperatorCombinator("+"), n)))
      Op("::") =>
        // `::before` is legal shrubbery and was the second draft's spelling,
        // so it is worth naming precisely rather than as a stray operator.
        quals.push(Bogus(self.bogus(SigilSelector("::"), n)))
      Kw(k) => quals.push(Bogus(self.bogus(StrayKeyword(k), n)))
      Brackets(_) => quals.push(Bogus(self.bogus(SigilSelector("["), n)))
      _ => quals.push(Bogus(self.bogus(BadSelector, n)))
    }
    i = i + 1
  }
  // A namespace with nothing to qualify.
  match ns {
    Some(_) => if type_sel is None { self.error(BadSelector, at) }
    None => ()
  }
  { type_sel, quals, span: cspan(span_of(items, at)), }
}

///|
/// What a call in selector position turned out to be.
priv enum SelCall {
  Type(@ast.TypeSelector)
  Ns(@ast.NsPrefix)
  Qual(@ast.Qualifier)
}

///|
fn Lowerer::selector_call(
  self : Lowerer,
  name : String,
  args : ArrayView[@sast.Node],
  at : @sast.Node,
  ns : @ast.NsPrefix?,
) -> SelCall raise @err.ShrubCssError {
  match name {
    "any" => return Type(Universal(ns))
    "tag" =>
      match name_arg(args) {
        Some(n) => return Type(Named(ns, n))
        None => return Qual(Bogus(self.bogus(BadCallShape("tag"), at)))
      }
    "class" =>
      match name_arg(args) {
        Some(n) => return Qual(Class(n))
        None => return Qual(Bogus(self.bogus(BadCallShape("class"), at)))
      }
    "id" =>
      match name_arg(args) {
        Some(n) => return Qual(Id(n))
        None => return Qual(Bogus(self.bogus(BadCallShape("id"), at)))
      }
    "parent" => if args.length() == 0 { return Qual(Nesting) }
    "ns" =>
      if args.length() == 0 {
        return Ns(None_)
      } else {
        match args {
          [{ it: Group([{ it: Id("any"), .. }, { it: Parens([]), .. }]), .. }] =>
            return Ns(Any)
          _ =>
            match name_arg(args) {
              Some(n) => return Ns(Named(n))
              None => ()
            }
        }
      }
    "has_attr" => return Qual(self.attr_of(args, at))
    "element" => return Qual(self.element_of(args, at))
    _ => ()
  }
  let css_name = @names.unkebab(name)
  if @names.is_selector_pseudo(css_name) {
    let sels : Array[@ast.Selector] = []
    for g in args {
      let items = match g.it {
        Group(xs) => xs[:]
        _ => one(g)
      }
      sels.push(self.selector(items, g.span))
    }
    return Qual(Pseudo(Sub(css_name, sels)))
  }
  if @names.is_nth_pseudo(css_name) {
    return Qual(self.nth_of(css_name, args, at))
  }
  if css_name == "lang" {
    let langs : Array[String] = []
    for g in arg_groups(args) {
      match g {
        [{ it: Id(n), .. }] => langs.push(n)
        [{ it: Lit(Str(s)), .. }] => langs.push(s)
        _ => ()
      }
    }
    return Qual(Pseudo(Lang(langs)))
  }
  if css_name == "dir" {
    match name_arg(args) {
      Some(d) => return Qual(Pseudo(Dir(d)))
      None => ()
    }
  }
  if @names.is_simple_pseudo(css_name) && args.length() == 0 {
    return Qual(Pseudo(Simple(css_name)))
  }
  // Not a name any table knows. Kept as an unknown pseudo-class rather than
  // dropped, with a warning -- CSS grows selectors faster than this table does.
  self.error(UnknownSelectorCall(name), at.span)
  Qual(Pseudo(Unknown(css_name, self.comma_list(args))))
}

///|
/// `has_attr(type = "text")`, and the five other matchers.
fn Lowerer::attr_of(
  self : Lowerer,
  args : ArrayView[@sast.Node],
  at : @sast.Node,
) -> @ast.Qualifier raise @err.ShrubCssError {
  let gs = arg_groups(args)
  if gs.length() != 1 {
    return Bogus(self.bogus(BadCallShape("has_attr"), at))
  }
  let mut items = gs[0]
  // A leading `ns(...)` qualifies the attribute name.
  let mut ns : @ast.NsPrefix? = None
  match as_call(items) {
    Some((n, a, rest)) if n == "ns" =>
      match self.selector_call(n, a, at, None) {
        Ns(p) => {
          ns = Some(p)
          items = rest
        }
        _ => ()
      }
    _ => ()
  }
  // A trailing `~i` or `~s` flag.
  let mut case_ : @ast.AttrCase = Default
  if items.length() > 0 {
    match items[items.length() - 1].it {
      Kw("i") => {
        case_ = Insensitive
        items = items[0:items.length() - 1]
      }
      Kw("s") => {
        case_ = Sensitive
        items = items[0:items.length() - 1]
      }
      _ => ()
    }
  }
  let name = match items {
    [{ it: Id(n), .. }, ..] => @names.unkebab(n)
    [{ it: Lit(Str(s)), .. }, ..] => s
    _ => return Bogus(self.bogus(BadCallShape("has_attr"), at))
  }
  if items.length() == 1 {
    return Attr({ ns, name, matcher: None, case_, })
  }
  let op : @ast.AttrOp? = match items[1].it {
    Op("=") => Some(Exact)
    Op("~=") => Some(Includes)
    Op("|=") => Some(DashMatch)
    Op("^=") => Some(Prefix)
    Op("$=") => Some(Suffix)
    Op("*=") => Some(Substring)
    _ => None
  }
  match op {
    None => Bogus(self.bogus(BadCallShape("has_attr"), at))
    Some(o) =>
      match items[2:] {
        [{ it: Lit(Str(s)), .. }] =>
          Attr({ ns, name, matcher: Some((o, Str(s))), case_, })
        [{ it: Id(n), .. }] =>
          Attr({
            ns,
            name,
            matcher: Some((o, Ident(@names.unkebab(n)))),
            case_,
          })
        _ => Bogus(self.bogus(BadCallShape("has_attr"), at))
      }
  }
}

///|
/// `element(before)`, `element(part(x))`.
fn Lowerer::element_of(
  self : Lowerer,
  args : ArrayView[@sast.Node],
  at : @sast.Node,
) -> @ast.Qualifier raise @err.ShrubCssError {
  match name_arg(args) {
    Some(n) => return Element(Simple(n))
    None => ()
  }
  // A functional pseudo-element: `element(part(shadow_button))`.
  let gs = arg_groups(args)
  if gs.length() == 1 {
    match as_call(gs[0]) {
      Some((fname, fargs, _)) => {
        let css_name = @names.unkebab(fname)
        if @names.is_selector_pseudo(css_name) {
          let sels : Array[@ast.Selector] = []
          for g in fargs {
            let items = match g.it {
              Group(xs) => xs[:]
              _ => one(g)
            }
            sels.push(self.selector(items, g.span))
          }
          return Element(Sub(css_name, sels))
        }
        return Element(Fn(css_name, self.comma_list(fargs)))
      }
      None => ()
    }
  }
  Bogus(self.bogus(BadCallShape("element"), at))
}

///|
/// `nth_child(2 n + 1)`, with an optional `of` clause.
///
/// The formula is spelled with a space because `2n` is a lexical error in
/// shrubbery -- a number may not run into letters. This is the last place that
/// rule still shows, now that dimensions are calls.
fn Lowerer::nth_of(
  self : Lowerer,
  css_name : String,
  args : ArrayView[@sast.Node],
  at : @sast.Node,
) -> @ast.Qualifier raise @err.ShrubCssError {
  let gs = arg_groups(args)
  if gs.length() == 0 {
    return Bogus(self.bogus(BadCallShape(css_name), at))
  }
  let mut formula = gs[0]
  let mut of_ : Array[@ast.Selector]? = None
  // `of` splits the formula from the selector list that follows it.
  for i, n in formula {
    match n.it {
      Id("of") => {
        let sel_items = formula[i + 1:]
        if sel_items.length() > 0 {
          of_ = Some([self.selector(sel_items, n.span)])
        }
        formula = formula[0:i]
        break
      }
      _ => ()
    }
  }
  match parse_anb(formula) {
    Some(anb) => Pseudo(Nth(css_name, anb, of_))
    None => {
      self.error(BadAnB, at.span)
      Pseudo(Unknown(css_name, self.comma_list(args)))
    }
  }
}

///|
/// The `An+B` shapes, over shrubbery nodes.
fn parse_anb(items : ArrayView[@sast.Node]) -> @ast.AnB? {
  let ts : Array[@sast.Shrub] = []
  for n in items {
    ts.push(n.it)
  }
  match ts {
    [Id("odd")] => Some({ a: 2, b: 1, })
    [Id("even")] => Some({ a: 2, b: 0, })
    [Id("n")] => Some({ a: 1, b: 0, })
    [Lit(Int_(b))] => Some({ a: 0, b: int_of(b), })
    [Op("-"), Id("n")] => Some({ a: -1, b: 0, })
    [Lit(Int_(a)), Id("n")] => Some({ a: int_of(a), b: 0, })
    [Id("n"), Op(s), Lit(Int_(b))] => signed(1, s, b)
    [Op("-"), Id("n"), Op(s), Lit(Int_(b))] => signed(-1, s, b)
    [Lit(Int_(a)), Id("n"), Op(s), Lit(Int_(b))] => signed(int_of(a), s, b)
    _ => None
  }
}

///|
fn signed(a : Int, op : String, b : @bigint.BigInt) -> @ast.AnB? {
  match op {
    "+" => Some({ a, b: int_of(b), })
    "-" => Some({ a, b: -int_of(b), })
    _ => None
  }
}

///|
fn int_of(b : @bigint.BigInt) -> Int {
  @string.parse_int(b.to_string()) catch {
    _ => 0
  }
}

///|
/// Whether anything in this compound has already failed.
fn has_bogus(quals : Array[@ast.Qualifier]) -> Bool {
  for q in quals {
    if q is Bogus(_) {
      return true
    }
  }
  false
}

///|
/// Whether a type selector's name is an at-rule's.
fn at_rule_head(t : @ast.TypeSelector?) -> Bool {
  match t {
    Some(Named(_, n)) => @names.is_at_rule(n)
    _ => false
  }
}

///|
/// A `Bogus` at a span, for the case where there is no node to point at.
fn Lowerer::bogus_at(
  self : Lowerer,
  kind : @kind.ErrorKind,
  at : @basic.Span,
) -> @ast.Bogus raise @err.ShrubCssError {
  self.error(kind, at)
  @ast.Bogus::new(
    @csskind.ErrorKind::Unexpected(kind.code()),
    cspan(at),
    text="",
  )
}