///|
fn is_html_whitespace_char(ch : Char) -> Bool {
  match ch {
    ' ' | '\t' | '\n' | '\r' | '\u{000C}' => true
    _ => false
  }
}

///|
fn next_simple_selector_marker(selector : StringView, start : Int) -> Int {
  let mut pos = start
  let mut paren_depth = 0
  while pos < selector.length() {
    let ch = selector.get_char(pos).unwrap()
    if ch == '(' {
      paren_depth += 1
    } else if ch == ')' && paren_depth > 0 {
      paren_depth -= 1
    } else if paren_depth == 0 &&
      (ch == '.' || ch == '#' || ch == '[' || ch == ':') {
      return pos
    }
    pos += ch.utf16_len()
  }
  selector.length()
}

///|
fn find_attribute_selector_end(selector : StringView, start : Int) -> Int? {
  let mut pos = start + 1
  let mut quote : Char? = None
  let mut escaped = 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 ch == '"' || ch == '\'' {
          quote = Some(ch)
          escaped = false
        } else if ch == ']' {
          return Some(pos)
        }
    }
    pos += ch.utf16_len()
  }
  None
}

///|
fn selector_identifier_start(ch : Char) -> Bool {
  ch == '_' || ch == '-' || ch.is_ascii_alphabetic() || ch.to_int() > 127
}

///|
fn selector_identifier_char(ch : Char) -> Bool {
  selector_identifier_start(ch) || ch.is_digit(10)
}

///|
fn selector_identifier_is_valid(name : StringView) -> Bool {
  if name.is_empty() {
    return false
  }
  guard name.get_char(0) is Some(first) else { return false }
  if !selector_identifier_start(first) {
    return false
  }
  let mut pos = first.utf16_len()
  while pos < name.length() {
    match name.get_char(pos) {
      Some(ch) if selector_identifier_char(ch) => pos += ch.utf16_len()
      _ => return false
    }
  }
  true
}

///|
fn string_view_has_html_whitespace(value : StringView) -> Bool {
  for ch in value {
    if is_html_whitespace_char(ch) {
      return true
    }
  }
  false
}

///|
fn string_view_contains(haystack : StringView, needle : StringView) -> Bool {
  if needle.is_empty() || needle.length() > haystack.length() {
    return false
  }
  let mut pos = 0
  while pos + needle.length() <= haystack.length() {
    match haystack.get_view(start=pos, end=pos + needle.length()) {
      Some(part) if part == needle => return true
      _ => ()
    }
    pos += haystack.get_char(pos).unwrap().utf16_len()
  }
  false
}