///|
fn matches_simple_selector(node : @dom.Node, selector : StringView) -> Bool {
  let selector = selector.trim()
  if selector.is_empty() {
    return false
  }
  if node.kind != Element {
    return non_element_simple_selector_matches(node, selector)
  }
  let mut pos = 0
  match selector.get_char(pos).unwrap() {
    '*' => {
      pos += 1
      if pos < selector.length() {
        match selector.get_char(pos) {
          Some('.') | Some('#') | Some('[') | Some(':') => ()
          _ => return false
        }
      }
    }
    '.' | '#' | '[' | ':' => ()
    _ => {
      let end = next_simple_selector_marker(selector, pos)
      let tag = selector[pos:end]
      if tag.is_empty() ||
        !selector_identifier_is_valid(tag) ||
        !tag_selector_matches(node, tag) {
        return false
      }
      pos = end
    }
  }
  while pos < selector.length() {
    let marker = selector.get_char(pos).unwrap()
    if marker == '[' {
      guard find_attribute_selector_end(selector, pos) is Some(end) else {
        return false
      }
      let attribute = selector[pos + 1:end]
      if !attribute_selector_matches(node, attribute) {
        return false
      }
      pos = end + 1
      continue
    }
    if marker == ':' {
      let start = pos + marker.utf16_len()
      let end = next_simple_selector_marker(selector, start)
      let name = selector[start:end]
      if name.is_empty() || !pseudo_selector_matches(node, name) {
        return false
      }
      pos = end
      continue
    }
    if marker != '.' && marker != '#' {
      return false
    }
    let start = pos + marker.utf16_len()
    let end = next_simple_selector_marker(selector, start)
    let name = selector[start:end]
    if name.is_empty() || !selector_identifier_is_valid(name) {
      return false
    }
    if marker == '.' {
      if !class_selector_matches(node, name) {
        return false
      }
    } else if !id_selector_matches(node, name) {
      return false
    }
    pos = end
  }
  true
}

///|
fn simple_selector_is_valid(selector : StringView) -> Bool {
  let selector = selector.trim()
  if selector.is_empty() {
    return false
  }
  let mut pos = 0
  match selector.get_char(pos).unwrap() {
    '*' => {
      pos += 1
      if pos < selector.length() {
        match selector.get_char(pos) {
          Some('.') | Some('#') | Some('[') | Some(':') => ()
          _ => return false
        }
      }
    }
    '.' | '#' | '[' | ':' => ()
    _ => {
      let end = next_simple_selector_marker(selector, pos)
      let tag = selector[pos:end]
      if tag.is_empty() || !selector_identifier_is_valid(tag) {
        return false
      }
      pos = end
    }
  }
  while pos < selector.length() {
    let marker = selector.get_char(pos).unwrap()
    if marker == '[' {
      guard find_attribute_selector_end(selector, pos) is Some(end) else {
        return false
      }
      let attribute = selector[pos + 1:end]
      if !attribute_selector_is_valid(attribute) {
        return false
      }
      pos = end + 1
      continue
    }
    if marker == ':' {
      let start = pos + marker.utf16_len()
      let end = next_simple_selector_marker(selector, start)
      let name = selector[start:end]
      if name.is_empty() || !pseudo_selector_is_valid(name) {
        return false
      }
      pos = end
      continue
    }
    if marker != '.' && marker != '#' {
      return false
    }
    let start = pos + marker.utf16_len()
    let end = next_simple_selector_marker(selector, start)
    let name = selector[start:end]
    if name.is_empty() || !selector_identifier_is_valid(name) {
      return false
    }
    pos = end
  }
  true
}

///|
fn non_element_simple_selector_matches(
  node : @dom.Node,
  selector : StringView,
) -> Bool {
  let mut pos = 0
  while pos < selector.length() {
    guard selector.get_char(pos) is Some(':') else { return false }
    let start = pos + ':'.utf16_len()
    let end = next_simple_selector_marker(selector, start)
    guard selector.get_view(start~, end~) is Some(name) else { return false }
    if name.is_empty() || !pseudo_selector_matches(node, name) {
      return false
    }
    pos = end
  }
  true
}

///|
fn non_element_simple_selector_matches_with_context(
  node : @dom.Node,
  selector : StringView,
  context : SelectorMatchContext,
  depth : Int,
) -> Bool raise @core.HtmlError {
  let mut pos = 0
  while pos < selector.length() {
    context.tick()
    guard selector.get_char(pos) is Some(':') else { return false }
    let start = pos + ':'.utf16_len()
    let end = next_simple_selector_marker(selector, start)
    guard selector.get_view(start~, end~) is Some(name) else { return false }
    if name.is_empty() ||
      !pseudo_selector_matches_with_context(node, name, context, depth) {
      return false
    }
    pos = end
  }
  true
}

///|
fn matches_simple_selector_with_context(
  node : @dom.Node,
  selector : StringView,
  context : SelectorMatchContext,
  depth : Int,
) -> Bool raise @core.HtmlError {
  context.tick()
  let selector = selector.trim()
  if selector.is_empty() {
    return false
  }
  if node.kind != Element {
    return non_element_simple_selector_matches_with_context(
      node, selector, context, depth,
    )
  }
  let mut pos = 0
  match selector.get_char(pos).unwrap() {
    '*' => {
      pos += 1
      if pos < selector.length() {
        match selector.get_char(pos) {
          Some('.') | Some('#') | Some('[') | Some(':') => ()
          _ => return false
        }
      }
    }
    '.' | '#' | '[' | ':' => ()
    _ => {
      let end = next_simple_selector_marker(selector, pos)
      let tag = selector[pos:end]
      if tag.is_empty() ||
        !selector_identifier_is_valid(tag) ||
        !tag_selector_matches(node, tag) {
        return false
      }
      pos = end
    }
  }
  while pos < selector.length() {
    context.tick()
    let marker = selector.get_char(pos).unwrap()
    if marker == '[' {
      guard find_attribute_selector_end(selector, pos) is Some(end) else {
        return false
      }
      let attribute = selector[pos + 1:end]
      if !attribute_selector_matches(node, attribute) {
        return false
      }
      pos = end + 1
      continue
    }
    if marker == ':' {
      let start = pos + marker.utf16_len()
      let end = next_simple_selector_marker(selector, start)
      let name = selector[start:end]
      if name.is_empty() ||
        !pseudo_selector_matches_with_context(node, name, context, depth) {
        return false
      }
      pos = end
      continue
    }
    if marker != '.' && marker != '#' {
      return false
    }
    let start = pos + marker.utf16_len()
    let end = next_simple_selector_marker(selector, start)
    let name = selector[start:end]
    if name.is_empty() || !selector_identifier_is_valid(name) {
      return false
    }
    if marker == '.' {
      if !class_selector_matches(node, name) {
        return false
      }
    } else if !id_selector_matches(node, name) {
      return false
    }
    pos = end
  }
  true
}