///|
/// Defines the HTML escaping strategy.
///
/// Different contexts in HTML require different escaping rules.
/// For example, plain text only needs to escape `<` and `&`,
/// while quoted attributes must also escape quotation marks.
pub(all) enum EscapeMode {
  /// Escape `&` and `<`.
  TextMinimal
  /// Escape `&`, `<`, and `>`.
  Text
  /// Escape characters required inside double-quoted attributes:
  /// `&`, `<`, `>`, and `"`.
  DoubleQuoted
  /// Escape characters required inside single-quoted attributes:
  /// `&`, `<`, `>`, and `'`.
  SingleQuoted
  /// Escape all characters required inside quoted attributes:
  /// `&`, `<`, `>`, `"`, and `'`.
  Quoted
  /// Escape all HTML-sensitive characters including `/`.
  Safe
}

///|
/// Returns the HTML escape sequence corresponding to the given character under the specified escape mode.
fn escape_char(mode : EscapeMode, c : UInt16) -> Option[String] {
  match mode {
    EscapeMode::TextMinimal => match c {
      '&' => Some("&")
      '<' => Some("<")
      _ => None
    }
    EscapeMode::Text => match c {
      '&' => Some("&")
      '<' => Some("<")
      '>' => Some(">")
      _ => None
    }
    EscapeMode::DoubleQuoted => match c {
      '&' => Some("&")
      '<' => Some("<")
      '>' => Some(">")
      '"' => Some(""")
      _ => None
    }
    EscapeMode::SingleQuoted => match c {
      '&' => Some("&")
      '<' => Some("<")
      '>' => Some(">")
      '\'' => Some("'")
      _ => None
    }
    EscapeMode::Quoted => match c {
      '&' => Some("&")
      '<' => Some("<")
      '>' => Some(">")
      '"' => Some(""")
      '\'' => Some("'")
      _ => None
    }
    EscapeMode::Safe => match c {
      '&' => Some("&")
      '<' => Some("<")
      '>' => Some(">")
      '"' => Some(""")
      '\'' => Some("'")
      '/' => Some("/")
      _ => None
    }
  }
}

///| 
fn first_escape_pos(text : String, mode : EscapeMode) -> Int {
  let len = text.length()
  let mut i = 0
  while i < len {
    match escape_char(mode, text[i]) {
      Some(_) => return i
      None => ()
    }
    i += 1
  }
  -1
}

///|
/// Escapes a string according to the specified HTML escaping mode.
pub fn escape(text : String, mode : EscapeMode) -> String {
  let first = first_escape_pos(text, mode) 
  if first < 0 {
    return text
  }
  let len = text.length()
  let buf = StringBuilder::new(size_hint=len+16)
  
  buf.write_stringview(text[0:first])
  
  let mut start = first
  let mut i = first
  while i < len {
    let c = text[i]
    match escape_char(mode, c) {
      Some(en) => {
        if i > start {
          buf.write_stringview(text[start:i])
        }
        buf.write_stringview(en)
        start = i + 1
      }
      None => ()
    }
    i += 1
  }
  
  if i > start {
    buf.write_stringview(text[start:i])
  }
  
  buf.to_string()
}


///|
/// Escapes a string for use in HTML text content using the
/// minimal escaping rules.
///
/// The following characters are escaped:
///
/// - `&` → `&`
/// - `<` → `<`
pub fn escape_text_minimal(text : String) -> String {
  escape(text, EscapeMode::TextMinimal)
}

///|
/// Escapes a string for use in normal HTML text content.
///
/// The following characters are escaped:
///
/// - `&` → `&`
/// - `<` → `<`
/// - `>` → `>`
pub fn escape_text(text : String) -> String {
  escape(text, EscapeMode::Text)
}

///|
/// Escapes a string for use inside a double-quoted HTML attribute.
///
/// The following characters are escaped:
///
/// - `&` → `&`
/// - `<` → `<`
/// - `>` → `>`
/// - `"` → `"`
pub fn escape_double_quoted(text : String) -> String {
  escape(text, EscapeMode::DoubleQuoted)
}

///|
/// Escapes a string for use inside a single-quoted HTML attribute.
///
/// The following characters are escaped:
///
/// - `&` → `&`
/// - `<` → `<`
/// - `>` → `>`
/// - `'` → `'`
pub fn escape_single_quoted(text : String) -> String {
  escape(text, EscapeMode::SingleQuoted)
}

///|
/// Escapes a string for use inside a quoted HTML attribute.
///
/// The following characters are escaped:
///
/// - `&` → `&`
/// - `<` → `<`
/// - `>` → `>`
/// - `"` → `"`
/// - `'` → `'`
pub fn escape_quoted(text : String) -> String {
  escape(text, EscapeMode::Quoted)
}

///|
/// Escapes a string using the safest HTML escaping strategy.
///
/// The following characters are escaped:
///
/// - `&` → `&`
/// - `<` → `<`
/// - `>` → `>`
/// - `"` → `"`
/// - `'` → `'`
/// - `/` → `/
pub fn escape_safe(text : String) -> String {
  escape(text, EscapeMode::Safe)
}