// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// CSS selectors: the statically decidable subset of Selectors Level 4 —
/// type and universal selectors, `#id`, `.class`, attribute selectors with
/// every operator and the case flag, the four combinators, selector lists,
/// the logical pseudo-classes (`:not`, `:is`, `:where`, `:has`), the
/// structural pseudo-classes, and the state pseudo-classes approximated by
/// their attribute (a tree has no history, no focus and no hover).
///
/// Dynamic pseudo-classes (`:hover`, `:focus`, …) are not statically
/// decidable, so they are a parse error naming the pseudo-class rather than a
/// selector that silently matches nothing.

///|
/// What stands between two compound selectors.
priv enum Combinator {
  Descendant
  Child
  NextSibling
  LaterSibling
}

///|
/// One attribute requirement: `[a]`, `[a=v]`, `[a~=v]`, `[a|=v]`, `[a^=v]`,
/// `[a$=v]`, `[a*=v]`, each optionally with the `i` or `s` flag.
priv struct AttrReq {
  name : String
  op : String
  value : String
  flag : String
}

///|
/// A pseudo-class, with its argument already parsed.
priv enum Pseudo {
  FirstChild
  LastChild
  OnlyChild
  NthChild(AnPlusB)
  NthLastChild(AnPlusB)
  FirstOfType
  LastOfType
  NthOfType(AnPlusB)
  NthLastOfType(AnPlusB)
  OnlyOfType
  Empty
  Root
  AnyLink
  Link
  Checked
  Disabled
  Enabled
  Required
  Optional
  Not(Array[Complex])
  Is(Array[Complex])
  Where(Array[Complex])
  Has(Array[Complex])
}

///|
/// One compound selector: the pieces that must all hold on one element.
priv struct Compound {
  mut tag : String?
  mut id : String?
  classes : Array[String]
  attrs : Array[AttrReq]
  pseudos : Array[Pseudo]
}

///|
/// A complex selector: compounds joined by combinators, the last compound the
/// subject. The first pair's combinator is the leading one — meaningful only
/// inside `:has()`, where the selector is relative to the element it hangs on.
priv struct Complex {
  parts : Array[(Combinator, Compound)]
}

///|
/// `an+b`, the argument of the positional pseudo-classes.
priv struct AnPlusB {
  a : Int32
  b : Int32
}

///|
/// Parse a selector list, raising on the first thing that is not one.
fn css_parse(query : String) -> Array[Complex] raise Refused {
  let out : Array[Complex] = []
  for part in split_top(query, ',') {
    out.push(parse_complex(part))
  }
  out
}

///|
/// Split on a separator that sits at the top level — outside brackets,
/// parentheses and quotes, where it means something else.
fn split_top(text : String, sep : Char) -> Array[String] {
  let out : Array[String] = []
  let mut depth = 0
  let mut quote : Char? = None
  let mut run = StringBuilder()
  for ch in text {
    match quote {
      Some(q) => {
        run.write_char(ch)
        if ch == q {
          quote = None
        }
      }
      None =>
        if ch == '"' || ch == '\'' {
          quote = Some(ch)
          run.write_char(ch)
        } else if ch == '(' || ch == '[' {
          depth = depth + 1
          run.write_char(ch)
        } else if ch == ')' || ch == ']' {
          depth = depth - 1
          run.write_char(ch)
        } else if ch == sep && depth == 0 {
          out.push(run.to_string())
          run = StringBuilder()
        } else {
          run.write_char(ch)
        }
    }
  }
  out.push(run.to_string())
  out
}

///|
/// Parse one complex selector: compounds joined by combinators, where
/// whitespace is the descendant combinator.
fn parse_complex(text : String) -> Complex raise Refused {
  let parts : Array[(Combinator, Compound)] = []
  let n = text.length()
  let mut i = 0
  fn skip_ws() -> Unit {
    while i < n &&
          (
            text.at(i) == 0x20 ||
            text.at(i) == 0x09 ||
            text.at(i) == 0x0A ||
            text.at(i) == 0x0D
          ) {
      i = i + 1
    }
  }
  let mut pending : Combinator = Descendant
  skip_ws()
  if i < n && is_combinator_char(text.at(i)) {
    pending = combinator_of(text.at(i))
    i = i + 1
    skip_ws()
  }
  while i < n {
    let (compound, next) = parse_compound(text, i)
    if compound_is_empty(compound) {
      raise BadQuery(at=i, why="a compound selector is empty")
    }
    parts.push((pending, compound))
    i = next
    pending = Descendant
    skip_ws()
    if i < n && is_combinator_char(text.at(i)) {
      pending = combinator_of(text.at(i))
      i = i + 1
      skip_ws()
    }
  }
  if parts.is_empty() {
    raise BadQuery(at=0, why="a complex selector needs a compound")
  }
  Complex::{ parts, }
}

///|
fn is_combinator_char(unit : UInt16) -> Bool {
  unit == 0x3E || unit == 0x2B || unit == 0x7E
}

///|
fn combinator_of(unit : UInt16) -> Combinator {
  match unit {
    0x3E => Child
    0x2B => NextSibling
    _ => LaterSibling
  }
}

///|
fn compound_is_empty(compound : Compound) -> Bool {
  compound.tag is None &&
  compound.id is None &&
  compound.classes.is_empty() &&
  compound.attrs.is_empty() &&
  compound.pseudos.is_empty()
}

///|
/// Parse one compound selector starting at `from`, answering it and the index
/// just past it.
fn parse_compound(
  text : String,
  from : Int32,
) -> (Compound, Int32) raise Refused {
  let compound : Compound = {
    tag: None,
    id: None,
    classes: [],
    attrs: [],
    pseudos: [],
  }
  let n = text.length()
  let mut i = from
  let mut fresh = true
  while i < n {
    let unit = text.at(i)
    if unit == 0x2A && fresh {
      // *
      i = i + 1
      let (tag, next) = read_type(text, i, "*")
      compound.tag = Some(tag)
      i = next
      fresh = false
    } else if unit == 0x23 {
      // #id
      i = i + 1
      let (name, next) = read_name(text, i)
      if name.is_empty() {
        raise BadQuery(at=i, why="an id selector needs a name")
      }
      compound.id = Some(name)
      i = next
      fresh = false
    } else if unit == 0x2E {
      // .class
      i = i + 1
      let (name, next) = read_name(text, i)
      if name.is_empty() {
        raise BadQuery(at=i, why="a class selector needs a name")
      }
      compound.classes.push(name)
      i = next
      fresh = false
    } else if unit == 0x5B {
      // [attr]
      let (req, next) = parse_attr(text, i)
      compound.attrs.push(req)
      i = next
      fresh = false
    } else if unit == 0x3A {
      // :pseudo; ::pseudo-element selects nothing in a tree
      if i + 1 < n && text.at(i + 1) == 0x3A {
        raise BadQuery(at=i, why="a pseudo-element selects nothing in a tree")
      }
      let start = i
      i = i + 1
      let (name, next) = read_name(text, i)
      i = next
      if i < n && text.at(i) == 0x28 {
        let (arg, after) = read_parens(text, i)
        compound.pseudos.push(parse_functional(name, arg, start))
        i = after
      } else {
        compound.pseudos.push(parse_simple(name, start))
      }
      fresh = false
    } else if unit == 0x7B && fresh {
      // a Clark name: {uri}local
      let (tag, next) = read_clark(text, i)
      compound.tag = Some(tag)
      i = next
      fresh = false
    } else if fresh && (is_name_start(unit) || unit == 0x7C) {
      // a type selector, with an optional space prefix
      let head = if unit == 0x7C {
        ""
      } else {
        let (name, next) = read_name(text, i)
        i = next
        name
      }
      let (tag, next) = read_type(text, i, head)
      compound.tag = Some(tag)
      i = next
      fresh = false
    } else {
      break
    }
  }
  (compound, i)
}

///|
/// A `{uri}local` name in a type selector: the URI may carry colons and
/// slashes, so it is read up to its closing brace rather than as a name.
fn read_clark(text : String, from : Int32) -> (String, Int32) {
  let close = match find_char(text, from, '}') {
    Some(at) => at
    None => text.length()
  }
  let (part, next) = read_name(text, close + 1)
  (text[from:close + 1].to_owned() + part, next)
}

///|
/// A type selector's local part, keeping the prefix when there is one:
/// `rect`, `svg|rect`, `*|rect` and `|rect` all reach `name_matches` as
/// written, because the tree carries Clark names and keeps no prefix map.
fn read_type(text : String, from : Int32, head : String) -> (String, Int32) {
  if from < text.length() && text.at(from) == 0x7C {
    let (part, next) = read_name(text, from + 1)
    (head + "|" + part, next)
  } else {
    (head, from)
  }
}

///|
/// Read a CSS identifier, with the one escape form worth supporting: a
/// backslash and the character it stands for.
fn read_name(text : String, from : Int32) -> (String, Int32) {
  let out = StringBuilder()
  let n = text.length()
  let mut i = from
  while i < n {
    let unit = text.at(i)
    if unit == 0x5C && i + 1 < n {
      out.write_char(u16_char(text.at(i + 1)))
      i = i + 2
    } else if is_name_char(unit) {
      out.write_char(u16_char(unit))
      i = i + 1
    } else {
      break
    }
  }
  (out.to_string(), i)
}

///|
fn is_name_start(unit : UInt16) -> Bool {
  is_name_char(unit) && !(unit >= 0x30 && unit <= 0x39)
}

///|
fn is_name_char(unit : UInt16) -> Bool {
  (unit >= 0x61 && unit <= 0x7A) ||
  (unit >= 0x41 && unit <= 0x5A) ||
  (unit >= 0x30 && unit <= 0x39) ||
  unit == 0x2D ||
  unit == 0x5F ||
  unit > 0x7F
}

///|
/// `[attr]`, `[attr op value]`, with the optional case flag.
fn parse_attr(text : String, from : Int32) -> (AttrReq, Int32) raise Refused {
  let close = match find_close(text, from) {
    Some(at) => at
    None => raise BadQuery(at=from, why="an attribute selector is never closed")
  }
  let inner = text[from + 1:close].trim().to_owned()
  let n = inner.length()
  let mut at = 0
  while at < n && is_name_char(inner.at(at)) {
    at = at + 1
  }
  let name = inner[0:at].to_owned()
  if name.is_empty() {
    raise BadQuery(at=from + 1, why="an attribute selector needs a name")
  }
  let rest = inner[at:n].trim()
  if rest.is_empty() {
    return (AttrReq::{ name, op: "", value: "", flag: "", }, close + 1)
  }
  let two = if rest.length() >= 2 { rest[0:2].to_owned() } else { "" }
  let op = if two == "~=" ||
    two == "|=" ||
    two == "^=" ||
    two == "$=" ||
    two == "*=" {
    two
  } else if rest.at(0) == 0x3D {
    "="
  } else {
    raise BadQuery(
      at=from + 1 + at,
      why="an attribute operator is not one this library knows",
    )
  }
  let tail = if op.length() == 2 {
    rest[2:rest.length()].trim()
  } else {
    rest[1:rest.length()].trim()
  }
  let flag = if tail.length() >= 2 &&
    (tail.has_suffix(" i") || tail.has_suffix(" s")) {
    tail[tail.length() - 1:tail.length()].to_owned()
  } else {
    ""
  }
  let bare = if flag.is_empty() {
    tail
  } else {
    tail[0:tail.length() - 2].trim()
  }
  (
    AttrReq::{ name, op, value: strip_quotes(bare.to_owned()), flag, },
    close + 1,
  )
}

///|
/// The `]` that closes an attribute selector: past any quoted value, which
/// may carry a bracket of its own.
fn find_close(text : String, from : Int32) -> Int32? {
  let n = text.length()
  let mut i = from + 1
  let mut quote : Char? = None
  while i < n {
    let unit = text.at(i)
    match quote {
      Some(q) => if u16_char(unit) == q { quote = None }
      None =>
        if unit == 0x22 || unit == 0x27 {
          quote = Some(u16_char(unit))
        } else if unit == 0x5D {
          return Some(i)
        }
    }
    i = i + 1
  }
  None
}

///|
/// The text between balanced parentheses starting at the `(`.
fn read_parens(text : String, from : Int32) -> (String, Int32) {
  let n = text.length()
  let mut depth = 0
  let mut i = from
  let out = StringBuilder()
  while i < n {
    let unit = text.at(i)
    if unit == 0x28 {
      depth = depth + 1
      if depth > 1 {
        out.write_char(u16_char(unit))
      }
    } else if unit == 0x29 {
      depth = depth - 1
      if depth == 0 {
        return (out.to_string(), i + 1)
      }
      out.write_char(u16_char(unit))
    } else {
      out.write_char(u16_char(unit))
    }
    i = i + 1
  }
  (out.to_string(), n)
}

///|
fn strip_quotes(text : String) -> String {
  let n = text.length()
  if n >= 2 &&
    (
      (text.at(0) == 0x27 && text.at(n - 1) == 0x27) ||
      (text.at(0) == 0x22 && text.at(n - 1) == 0x22)
    ) {
    return text[1:n - 1].to_owned()
  }
  text
}

///|
/// A pseudo-class with no argument.
fn parse_simple(name : String, at : Int32) -> Pseudo raise Refused {
  match name {
    "first-child" => FirstChild
    "last-child" => LastChild
    "only-child" => OnlyChild
    "first-of-type" => FirstOfType
    "last-of-type" => LastOfType
    "only-of-type" => OnlyOfType
    "empty" => Empty
    "root" => Root
    "link" => Link
    "any-link" => AnyLink
    "checked" => Checked
    "disabled" => Disabled
    "enabled" => Enabled
    "required" => Required
    "optional" => Optional
    _ =>
      raise BadQuery(
        at~,
        why="the pseudo-class :\{name} is not statically decidable",
      )
  }
}

///|
/// A pseudo-class with an argument: `an+b` for the positional ones, a selector
/// list for the logical ones.
fn parse_functional(
  name : String,
  arg : String,
  at : Int32,
) -> Pseudo raise Refused {
  match name {
    "nth-child" => NthChild(parse_an_plus_b(arg, at))
    "nth-last-child" => NthLastChild(parse_an_plus_b(arg, at))
    "nth-of-type" => NthOfType(parse_an_plus_b(arg, at))
    "nth-last-of-type" => NthLastOfType(parse_an_plus_b(arg, at))
    "not" => Not(parse_inner(arg, at))
    "is" => Is(parse_inner(arg, at))
    "where" => Where(parse_inner(arg, at))
    "has" => Has(parse_inner(arg, at))
    _ =>
      raise BadQuery(
        at~,
        why="the pseudo-class :\{name}(..) is not statically decidable",
      )
  }
}

///|
/// The selector list inside a logical pseudo-class.
fn parse_inner(arg : String, at : Int32) -> Array[Complex] raise Refused {
  let out : Array[Complex] = []
  for part in split_top(arg, ',') {
    out.push(parse_complex(part))
  }
  if out.is_empty() {
    raise BadQuery(at~, why="a logical pseudo-class needs a selector")
  }
  out
}

///|
/// `an+b` in its spellings: `2n+1`, `odd`, `even`, `-n+3`, `n`, `3`.
fn parse_an_plus_b(text : String, at : Int32) -> AnPlusB raise Refused {
  let s = text.trim().to_lower().to_owned()
  if s == "odd" {
    return { a: 2, b: 1, }
  }
  if s == "even" {
    return { a: 2, b: 0, }
  }
  let rest = if s.has_prefix("+") { s[1:s.length()].to_owned() } else { s }
  let mark = match find_char(rest, 0, 'n') {
    Some(k) => k
    None =>
      match parse_int(rest) {
        Some(v) => return { a: 0, b: v, }
        None => raise BadQuery(at~, why="\{text} is not an an+b argument")
      }
  }
  let a_text = rest[0:mark].to_owned()
  let b_text = rest[mark + 1:rest.length()].trim().to_owned()
  let a = if a_text.is_empty() {
    1
  } else if a_text == "-" {
    -1
  } else {
    match parse_int(a_text) {
      Some(v) => v
      None => raise BadQuery(at~, why="\{text} is not an an+b argument")
    }
  }
  let b = if b_text.is_empty() {
    0
  } else {
    match parse_int(b_text) {
      Some(v) => v
      None => raise BadQuery(at~, why="\{text} is not an an+b argument")
    }
  }
  { a, b, }
}

///|
fn parse_int(text : StringView) -> Int32? {
  Some(@string.parse_int(text)) catch {
    _ => None
  }
}

///|
/// Whether `count` — one-based — is one of the positions `an+b` names.
fn an_plus_b_holds(anb : AnPlusB, count : Int32) -> Bool {
  if anb.a == 0 {
    return count == anb.b
  }
  let k = count - anb.b
  k % anb.a == 0 && k / anb.a >= 0
}

// Matching. The subject is the last compound; the chain is walked
// right-to-left, backtracking where a combinator allows more than one answer.

///|
fn complex_list_matches(complexes : Array[Complex], element : Element) -> Bool {
  for complex in complexes {
    if complex_matches(complex, element) {
      return true
    }
  }
  false
}

///|
fn complex_matches(complex : Complex, element : Element) -> Bool {
  let last = complex.parts.length() - 1
  let (_, subject) = complex.parts[last]
  if !compound_matches(subject, element) {
    return false
  }
  chain_matches(complex, last - 1, element)
}

///|
/// `idx` is the compound to the left of `element`; the combinator between
/// them sits in `parts[idx + 1]`.
fn chain_matches(complex : Complex, idx : Int32, element : Element) -> Bool {
  if idx < 0 {
    return true
  }
  let comb = complex.parts[idx + 1].0
  let compound = complex.parts[idx].1
  match comb {
    Child =>
      match element.parent {
        Some(parent) =>
          compound_matches(compound, parent) &&
          chain_matches(complex, idx - 1, parent)
        None => false
      }
    Descendant => {
      let mut current = element.parent
      while current is Some(ancestor) {
        if compound_matches(compound, ancestor) &&
          chain_matches(complex, idx - 1, ancestor) {
          return true
        }
        current = ancestor.parent
      }
      false
    }
    NextSibling =>
      match previous_element(element) {
        Some(sibling) =>
          compound_matches(compound, sibling) &&
          chain_matches(complex, idx - 1, sibling)
        None => false
      }
    LaterSibling =>
      match element.parent {
        Some(parent) => {
          let siblings = element_children(parent)
          let mut found = false
          for sibling in siblings {
            if physical_equal(sibling, element) {
              break
            }
            if compound_matches(compound, sibling) &&
              chain_matches(complex, idx - 1, sibling) {
              found = true
              break
            }
          }
          found
        }
        None => false
      }
  }
}

///|
fn compound_matches(compound : Compound, element : Element) -> Bool {
  match compound.tag {
    Some(tag) => if !name_matches(element.name, tag) { return false }
    None => ()
  }
  match compound.id {
    Some(id) =>
      match element.attr("id") {
        Some(have) => if have != id { return false }
        None => return false
      }
    None => ()
  }
  for wanted in compound.classes {
    if !element.has_class(wanted) {
      return false
    }
  }
  for req in compound.attrs {
    if !attr_matches(element, req) {
      return false
    }
  }
  for pseudo in compound.pseudos {
    if !pseudo_matches(pseudo, element) {
      return false
    }
  }
  true
}

///|
/// The one name-matching rule XPath and CSS share: the whole `{uri}local`
/// name, or — when the test carries no `{` — the local part. A prefix in the
/// test matches by local part too, because the tree keeps no prefix map and a
/// wrong resolution is worse than an honest one.
fn name_matches(full : String, probe : String) -> Bool {
  if probe == "*" {
    return true
  }
  if full == probe {
    return true
  }
  if probe.contains("|") {
    let tail = probe[probe.find("|").unwrap() + 1:probe.length()].to_owned()
    return tail == "*" || local_of(full) == tail
  }
  if !probe.contains("{") {
    return local_of(full) == probe
  }
  false
}

///|
fn attr_matches(element : Element, req : AttrReq) -> Bool {
  match element.attr(req.name) {
    None => false
    Some(have) =>
      match req.op {
        "" => true
        "=" => same_text(have, req.value, req.flag)
        "~=" =>
          if req.value.is_empty() {
            false
          } else {
            let mut found = false
            for part in have.split(" ") {
              if !part.is_empty() && same_text(part, req.value, req.flag) {
                found = true
                break
              }
            }
            found
          }
        "|=" => have == req.value || have.has_prefix(req.value + "-")
        "^=" => !req.value.is_empty() && starts_with(have, req.value, req.flag)
        "$=" => !req.value.is_empty() && ends_with(have, req.value, req.flag)
        "*=" => !req.value.is_empty() && holds_within(have, req.value, req.flag)
        _ => false
      }
  }
}

///|
fn same_text(a : StringView, b : StringView, flag : String) -> Bool {
  if flag == "i" {
    ascii_lower(a) == ascii_lower(b)
  } else {
    a == b
  }
}

///|
fn starts_with(text : StringView, part : StringView, flag : String) -> Bool {
  if part.length() > text.length() {
    return false
  }
  same_text(text[0:part.length()].to_owned(), part, flag)
}

///|
fn ends_with(text : StringView, part : StringView, flag : String) -> Bool {
  let n = text.length()
  let m = part.length()
  if m > n {
    return false
  }
  same_text(text[n - m:n].to_owned(), part, flag)
}

///|
fn holds_within(text : StringView, part : StringView, flag : String) -> Bool {
  let m = part.length()
  let n = text.length()
  if m == 0 || m > n {
    return false
  }
  let mut k = 0
  while k + m <= n {
    if same_text(text[k:k + m].to_owned(), part, flag) {
      return true
    }
    k = k + 1
  }
  false
}

///|
fn ascii_lower(text : StringView) -> String {
  let out = StringBuilder()
  for ch in text {
    if ch >= 'A' && ch <= 'Z' {
      out.write_char((ch.to_int() + 0x20).unsafe_to_char())
    } else {
      out.write_char(ch)
    }
  }
  out.to_string()
}

///|
fn pseudo_matches(pseudo : Pseudo, element : Element) -> Bool {
  match pseudo {
    FirstChild => previous_element(element) is None
    LastChild => next_element(element) is None
    OnlyChild =>
      previous_element(element) is None && next_element(element) is None
    NthChild(anb) =>
      match element.parent {
        Some(parent) => {
          let siblings = element_children(parent)
          let mut at = 0
          for k = 0; k < siblings.length(); k = k + 1 {
            if physical_equal(siblings[k], element) {
              at = k + 1
              break
            }
          }
          at > 0 && an_plus_b_holds(anb, at)
        }
        None => false
      }
    NthLastChild(anb) =>
      match element.parent {
        Some(parent) => {
          let siblings = element_children(parent)
          let mut at = 0
          for k = siblings.length() - 1; k >= 0; k = k - 1 {
            if physical_equal(siblings[k], element) {
              at = siblings.length() - k
              break
            }
          }
          at > 0 && an_plus_b_holds(anb, at)
        }
        None => false
      }
    FirstOfType => of_type_position(element, from_end=false) == 1
    LastOfType => of_type_position(element, from_end=true) == 1
    OnlyOfType =>
      of_type_position(element, from_end=false) == 1 &&
      of_type_position(element, from_end=true) == 1
    NthOfType(anb) =>
      an_plus_b_holds(anb, of_type_position(element, from_end=false))
    NthLastOfType(anb) =>
      an_plus_b_holds(anb, of_type_position(element, from_end=true))
    Empty => {
      let mut empty = true
      for child in element.children {
        match child {
          ElementNode(_) => empty = false
          TextNode(text) => if !text.trim().is_empty() { empty = false }
          CDataNode(text) => if !text.trim().is_empty() { empty = false }
          _ => ()
        }
      }
      empty
    }
    Root =>
      match element.parent {
        None => true
        Some(parent) => parent.name == "#document"
      }
    AnyLink => element.has_attr("href")
    Link => element.has_attr("href")
    Checked => element.has_attr("checked") || element.has_attr("selected")
    Disabled => element.has_attr("disabled")
    Enabled => !element.has_attr("disabled")
    Required => element.has_attr("required")
    Optional => !element.has_attr("required")
    Not(list) => !complex_list_matches(list, element)
    Is(list) => complex_list_matches(list, element)
    Where(list) => complex_list_matches(list, element)
    Has(list) => has_matches(list, element)
  }
}

///|
/// One-based position among the siblings that share this element's name.
fn of_type_position(element : Element, from_end~ : Bool) -> Int32 {
  match element.parent {
    Some(parent) => {
      let siblings = element_children(parent)
      let same : Array[Element] = []
      for sibling in siblings {
        if sibling.name == element.name {
          same.push(sibling)
        }
      }
      let mut at = 0
      if from_end {
        for k = same.length() - 1; k >= 0; k = k - 1 {
          if physical_equal(same[k], element) {
            at = same.length() - k
            break
          }
        }
      } else {
        for k = 0; k < same.length(); k = k + 1 {
          if physical_equal(same[k], element) {
            at = k + 1
            break
          }
        }
      }
      at
    }
    None => 1
  }
}

///|
/// `:has(relative)`: does any element the relative selector can reach from
/// `element` match it?
fn has_matches(list : Array[Complex], element : Element) -> Bool {
  for complex in list {
    let (lead, first) = complex.parts[0]
    let candidates : Array[Element] = []
    match lead {
      Descendant =>
        for data in descendant_elements(element) {
          candidates.push(data)
        }
      Child =>
        for child in element.children {
          if child is ElementNode(data) {
            candidates.push(data)
          }
        }
      NextSibling =>
        match next_element(element) {
          Some(sibling) => candidates.push(sibling)
          None => ()
        }
      LaterSibling =>
        match element.parent {
          Some(parent) => {
            let siblings = element_children(parent)
            let mut past = false
            for sibling in siblings {
              if past {
                candidates.push(sibling)
              }
              if physical_equal(sibling, element) {
                past = true
              }
            }
          }
          None => ()
        }
    }
    for candidate in candidates {
      if compound_matches(first, candidate) &&
        relative_chain(complex, 0, candidate) {
        return true
      }
    }
  }
  false
}

///|
/// The rest of a relative selector, walked left-to-right from `element`.
fn relative_chain(complex : Complex, idx : Int32, element : Element) -> Bool {
  if idx + 1 >= complex.parts.length() {
    return true
  }
  let (comb, compound) = complex.parts[idx + 1]
  match comb {
    Descendant => {
      for data in descendant_elements(element) {
        if compound_matches(compound, data) &&
          relative_chain(complex, idx + 1, data) {
          return true
        }
      }
      false
    }
    Child => {
      for child in element.children {
        if child is ElementNode(data) &&
          compound_matches(compound, data) &&
          relative_chain(complex, idx + 1, data) {
          return true
        }
      }
      false
    }
    NextSibling =>
      match next_element(element) {
        Some(sibling) =>
          compound_matches(compound, sibling) &&
          relative_chain(complex, idx + 1, sibling)
        None => false
      }
    LaterSibling =>
      match element.parent {
        Some(parent) => {
          let siblings = element_children(parent)
          let mut past = false
          for sibling in siblings {
            if past && compound_matches(compound, sibling) {
              if relative_chain(complex, idx + 1, sibling) {
                return true
              }
            }
            if physical_equal(sibling, element) {
              past = true
            }
          }
          false
        }
        None => false
      }
  }
}

///|
/// The element descendants of an element, in document order.
fn descendant_elements(element : Element) -> Array[Element] {
  let out : Array[Element] = []
  element.walk(fn(node) { if node is ElementNode(data) { out.push(data) } })
  out
}

///|
/// The element children of a parent, in order.
fn element_children(parent : Element) -> Array[Element] {
  let out : Array[Element] = []
  for child in parent.children {
    if child is ElementNode(data) {
      out.push(data)
    }
  }
  out
}

///|
fn previous_element(element : Element) -> Element? {
  match element.parent {
    Some(parent) => {
      let siblings = element_children(parent)
      for k = 1; k < siblings.length(); k = k + 1 {
        if physical_equal(siblings[k], element) {
          return Some(siblings[k - 1])
        }
      }
      None
    }
    None => None
  }
}

///|
fn next_element(element : Element) -> Element? {
  match element.parent {
    Some(parent) => {
      let siblings = element_children(parent)
      for k = 0; k < siblings.length() - 1; k = k + 1 {
        if physical_equal(siblings[k], element) {
          return Some(siblings[k + 1])
        }
      }
      None
    }
    None => None
  }
}

///|
/// The first place a character occurs at or after `from`, if it does.
fn find_char(src : String, from : Int32, unit : Char) -> Int32? {
  let n = src.length()
  let mut i = from
  while i < n {
    if u16_char(src.at(i)) == unit {
      return Some(i)
    }
    i = i + 1
  }
  None
}

///|
/// A code unit as a character, with a stand-in for the lone surrogates.
fn u16_char(unit : UInt16) -> Char {
  match unit.to_char() {
    Some(c) => c
    None => '?'
  }
}