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

///|
fn collapse_html_whitespace(value : StringView) -> String {
  let out = StringBuilder::new(size_hint=value.length())
  let mut pending_space = false
  let mut wrote = false
  for ch in value {
    if is_html_whitespace_char(ch) {
      if wrote {
        pending_space = true
      }
    } else {
      if pending_space {
        out.write_char(' ')
        pending_space = false
      }
      out.write_char(ch)
      wrote = true
    }
  }
  out.to_string()
}

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

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

///|
fn compact_pretty_whitespace_separator(value : StringView) -> Bool {
  string_view_has_formatting_whitespace(value) || value.length() > 2
}

///|
fn normalize_formatting_whitespace(value : StringView) -> String {
  let mut has_formatting = false
  for ch in value {
    if formatting_whitespace_char(ch) {
      has_formatting = true
      break
    }
  }
  if !has_formatting {
    return value.to_owned()
  }
  let out : Array[String] = []
  let mut pos = 0
  while pos < value.length() {
    guard value.get_char(pos) is Some(ch) else { break }
    if formatting_whitespace_char(ch) {
      while !out.is_empty() && out.last() == Some(" ") {
        ignore(out.pop())
      }
      let mut run_has_space = false
      while pos < value.length() {
        guard value.get_char(pos) is Some(run_ch) else { break }
        if run_ch == ' ' {
          run_has_space = true
          pos += run_ch.utf16_len()
        } else if formatting_whitespace_char(run_ch) {
          pos += run_ch.utf16_len()
        } else {
          break
        }
      }
      if !out.is_empty() && (pos < value.length() || run_has_space) {
        out.push(" ")
      }
    } else {
      out.push(ch.to_string())
      pos += ch.utf16_len()
    }
  }
  out.join("")
}

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