///|
fn escape_text(value : StringView) -> String {
  let out = StringBuilder::new(size_hint=value.length())
  for ch in value {
    match ch {
      '&' => out.write_string("&")
      '<' => out.write_string("<")
      '>' => out.write_string(">")
      _ => out.write_char(ch)
    }
  }
  out.to_string()
}

///|
fn escape_attr_value(value : StringView, quote : Char) -> String {
  let out = StringBuilder::new(size_hint=value.length())
  for ch in value {
    match ch {
      '&' => out.write_string("&")
      '<' => out.write_string("<")
      '>' => out.write_string(">")
      '"' if quote == '"' => out.write_string(""")
      '\'' if quote == '\'' => out.write_string("'")
      _ => out.write_char(ch)
    }
  }
  out.to_string()
}

///|
fn escape_js_string(value : StringView, quote : Char) -> String {
  let out = StringBuilder::new(size_hint=value.length())
  for ch in value {
    match ch {
      '\\' => out.write_string("\\\\")
      '\n' => out.write_string("\\n")
      '\r' => out.write_string("\\r")
      '\t' => out.write_string("\\t")
      '\u{0008}' => out.write_string("\\b")
      '\u{000C}' => out.write_string("\\f")
      '"' if quote == '"' => out.write_string("\\\"")
      '\'' if quote == '\'' => out.write_string("\\'")
      '<' => out.write_string("\\u003c")
      '>' => out.write_string("\\u003e")
      '\u{2028}' => out.write_string("\\u2028")
      '\u{2029}' => out.write_string("\\u2029")
      _ => out.write_char(ch)
    }
  }
  out.to_string()
}

///|
fn percent_encode_url(value : StringView) -> String {
  let bytes = @utf8.encode(value)
  let out = StringBuilder::new(size_hint=bytes.length())
  for index in 0.. Bool {
  let value = byte.to_int()
  (value >= 0x41 && value <= 0x5A) ||
  (value >= 0x61 && value <= 0x7A) ||
  (value >= 0x30 && value <= 0x39) ||
  value == 0x2F ||
  value == 0x3A ||
  value == 0x40 ||
  value == 0x3F ||
  value == 0x26 ||
  value == 0x3D ||
  value == 0x23 ||
  value == 0x2B ||
  value == 0x2D ||
  value == 0x2E ||
  value == 0x5F ||
  value == 0x7E
}

///|
/// Append one byte as an uppercase percent-encoded triplet.
///
/// The output form is `%HH`, using hexadecimal digits `0`-`9` and `A`-`F`.
pub fn write_percent_encoded_byte(out : StringBuilder, byte : Byte) -> Unit {
  let value = byte.to_int()
  out.write_char('%')
  out.write_char(hex_digit_upper(value / 16))
  out.write_char(hex_digit_upper(value % 16))
}

///|
fn hex_digit_upper(value : Int) -> Char {
  match value {
    0 => '0'
    1 => '1'
    2 => '2'
    3 => '3'
    4 => '4'
    5 => '5'
    6 => '6'
    7 => '7'
    8 => '8'
    9 => '9'
    10 => 'A'
    11 => 'B'
    12 => 'C'
    13 => 'D'
    14 => 'E'
    15 => 'F'
    _ => '0'
  }
}

///|
/// Return whether `value` contains `needle`.
///
/// This scans by `Char`, so non-BMP characters are treated as single
/// characters even though MoonBit string offsets are UTF-16 code units.
pub fn string_contains_char(value : StringView, needle : Char) -> Bool {
  for ch in value {
    if ch == needle {
      return true
    }
  }
  false
}