///|
fn selector_list_part(
  selector : StringView,
  start : Int,
  end : Int,
) -> StringView? {
  let part = selector[start:end].trim()
  if part.is_empty() {
    None
  } else {
    Some(part)
  }
}

///|
fn selector_list_parts(selector : StringView) -> Array[StringView]? {
  let selector = selector.trim()
  if selector.is_empty() {
    return None
  }
  let parts : Array[StringView] = []
  let mut start = 0
  let mut pos = 0
  let mut bracket_depth = 0
  let mut paren_depth = 0
  let mut quote : Char? = None
  let mut escaped = false
  let mut seen_part = false
  while pos < selector.length() {
    let ch = selector.get_char(pos).unwrap()
    match quote {
      Some(q) =>
        if escaped {
          escaped = false
        } else if ch == '\\' {
          escaped = true
        } else if ch == q {
          quote = None
        }
      None =>
        if bracket_depth > 0 && (ch == '"' || ch == '\'') {
          quote = Some(ch)
          escaped = false
        } else if ch == '[' {
          bracket_depth += 1
        } else if ch == ']' && bracket_depth > 0 {
          bracket_depth -= 1
        } else if bracket_depth == 0 && ch == '(' {
          paren_depth += 1
        } else if bracket_depth == 0 && ch == ')' && paren_depth > 0 {
          paren_depth -= 1
        } else if ch == ',' && bracket_depth == 0 && paren_depth == 0 {
          match selector_list_part(selector, start, pos) {
            Some(part) => {
              seen_part = true
              parts.push(part)
            }
            None => if !seen_part { return None }
          }
          start = pos + ch.utf16_len()
        }
    }
    pos += ch.utf16_len()
  }
  match selector_list_part(selector, start, selector.length()) {
    Some(part) => {
      parts.push(part)
      Some(parts)
    }
    None => if seen_part { Some(parts) } else { None }
  }
}

///|
priv enum SelectorCombinator {
  DescendantCombinator
  ChildCombinator
  AdjacentSiblingCombinator
  GeneralSiblingCombinator
} derive(Eq)

///|
fn push_complex_selector_part(
  parts : Array[StringView],
  combinators : Array[SelectorCombinator],
  part : StringView,
  pending : SelectorCombinator,
) -> Unit {
  if !parts.is_empty() {
    combinators.push(pending)
  }
  parts.push(part)
}

///|
fn split_complex_selector(
  selector : StringView,
) -> (Array[StringView], Array[SelectorCombinator])? {
  let selector = selector.trim()
  if selector.is_empty() {
    return None
  }
  let parts : Array[StringView] = []
  let combinators : Array[SelectorCombinator] = []
  let mut start = 0
  let mut pos = 0
  let mut bracket_depth = 0
  let mut paren_depth = 0
  let mut quote : Char? = None
  let mut escaped = false
  let mut pending = DescendantCombinator
  while pos < selector.length() {
    let ch = selector.get_char(pos).unwrap()
    match quote {
      Some(q) =>
        if escaped {
          escaped = false
        } else if ch == '\\' {
          escaped = true
        } else if ch == q {
          quote = None
        }
      None =>
        if bracket_depth > 0 && (ch == '"' || ch == '\'') {
          quote = Some(ch)
          escaped = false
        } else if ch == '[' {
          bracket_depth += 1
        } else if ch == ']' && bracket_depth > 0 {
          bracket_depth -= 1
        } else if bracket_depth == 0 && ch == '(' {
          paren_depth += 1
        } else if bracket_depth == 0 && ch == ')' && paren_depth > 0 {
          paren_depth -= 1
        } else if bracket_depth == 0 &&
          paren_depth == 0 &&
          is_html_whitespace_char(ch) {
          match selector_list_part(selector, start, pos) {
            Some(part) => {
              push_complex_selector_part(parts, combinators, part, pending)
              pending = DescendantCombinator
            }
            None => ()
          }
          start = pos + ch.utf16_len()
        } else if bracket_depth == 0 &&
          paren_depth == 0 &&
          (ch == '>' || ch == '+' || ch == '~') {
          match selector_list_part(selector, start, pos) {
            Some(part) =>
              push_complex_selector_part(parts, combinators, part, pending)
            None =>
              if parts.is_empty() || pending != DescendantCombinator {
                return None
              }
          }
          pending = if ch == '>' {
            ChildCombinator
          } else if ch == '+' {
            AdjacentSiblingCombinator
          } else {
            GeneralSiblingCombinator
          }
          start = pos + ch.utf16_len()
        }
    }
    pos += ch.utf16_len()
  }
  match selector_list_part(selector, start, selector.length()) {
    Some(part) => push_complex_selector_part(parts, combinators, part, pending)
    None => return None
  }
  Some((parts, combinators))
}

///|
fn selector_identifier_end(selector : StringView, start : Int) -> Int {
  guard selector.get_char(start) is Some(first) else { return start }
  if !selector_identifier_start(first) {
    return start
  }
  let mut pos = start + first.utf16_len()
  while pos < selector.length() {
    match selector.get_char(pos) {
      Some(ch) if selector_identifier_char(ch) => pos += ch.utf16_len()
      _ => return pos
    }
  }
  pos
}

///|
fn selector_allows_non_elements(selector : StringView) -> Bool {
  let mut pos = 0
  let mut bracket_depth = 0
  let mut paren_depth = 0
  let mut quote : Char? = None
  let mut escaped = false
  for ch in selector {
    match quote {
      Some(q) =>
        if escaped {
          escaped = false
        } else if ch == '\\' {
          escaped = true
        } else if ch == q {
          quote = None
        }
      None =>
        if bracket_depth > 0 && (ch == '"' || ch == '\'') {
          quote = Some(ch)
          escaped = false
        } else if ch == '[' {
          bracket_depth += 1
        } else if ch == ']' && bracket_depth > 0 {
          bracket_depth -= 1
        } else if bracket_depth == 0 && ch == '(' {
          paren_depth += 1
        } else if bracket_depth == 0 && ch == ')' && paren_depth > 0 {
          paren_depth -= 1
        } else if bracket_depth == 0 && paren_depth == 0 && ch == ':' {
          let start = pos + ch.utf16_len()
          let end = selector_identifier_end(selector, start)
          if end > start {
            let name = selector[start:end]
            if @syn.lower_ascii(name) == "comment" {
              return true
            }
          }
        }
    }
    pos += ch.utf16_len()
  }
  false
}