///|
/// Insert escape markers into text content, honoring the escape mode (wraps
/// `@escape.escape_content`).
fn escape(ctx : RenderCtx, content : String) -> String {
  @escape.escape_content(content, enabled=ctx.options.escape_mode is Smart)
}

///|
/// Prefix used to encode literal occurrences of the two internal marker
/// characters. A literal prefix is doubled, making the encoding reversible.
const MARKER_LITERAL_PREFIX : Char = '\u{F000}'

///|
fn protect_internal_marker_literals(content : String) -> String {
  let out = StringBuilder(size_hint=content.length())
  for ch in content {
    match ch {
      MARKER_LITERAL_PREFIX => {
        out.write_char(MARKER_LITERAL_PREFIX)
        out.write_char(MARKER_LITERAL_PREFIX)
      }
      @escape.MARKER_ESCAPING => {
        out.write_char(MARKER_LITERAL_PREFIX)
        out.write_char('E')
      }
      @escape.MARKER_CODE_BLOCK_NEWLINE => {
        out.write_char(MARKER_LITERAL_PREFIX)
        out.write_char('N')
      }
      _ => out.write_char(ch)
    }
  }
  out.to_string()
}

///|
fn restore_internal_marker_literals(content : String) -> String {
  let chars = content.to_array()
  let out = StringBuilder(size_hint=content.length())
  let mut i = 0
  while i < chars.length() {
    if chars[i] == MARKER_LITERAL_PREFIX && i + 1 < chars.length() {
      match chars[i + 1] {
        MARKER_LITERAL_PREFIX => out.write_char(MARKER_LITERAL_PREFIX)
        'E' => out.write_char(@escape.MARKER_ESCAPING)
        'N' => out.write_char(@escape.MARKER_CODE_BLOCK_NEWLINE)
        _ => {
          out.write_char(MARKER_LITERAL_PREFIX)
          i += 1
          continue
        }
      }
      i += 2
    } else {
      out.write_char(chars[i])
      i += 1
    }
  }
  out.to_string()
}

///|
/// Resolve escape markers using the commonmark un-escaper rule set, honoring
/// the escape mode (wraps `@escape.unescape_content`).
fn unescape(ctx : RenderCtx, content : String) -> String {
  @escape.unescape_content(
    content,
    commonmark_unescapers(),
    enabled=ctx.options.escape_mode is Smart,
  )
}

///|
/// The commonmark un-escaper handlers, in the same registration order as the
/// Go commonmark plugin.
fn commonmark_unescapers() -> Array[@escape.UnEscaper] {
  [
    @escape.is_italic_or_bold, @escape.is_strikethrough, @escape.is_block_quote,
    @escape.is_atx_header, @escape.is_setext_header, @escape.is_divider, @escape.is_ordered_list,
    @escape.is_unordered_list, @escape.is_image_or_link, @escape.is_fenced_code,
    @escape.is_inline_code, @escape.is_backslash,
  ]
}