///|
fn markdown_editor_wrap_selection_input(
  source : String,
  caret : Int,
  selection : @core.TextRange?,
  input : String,
) -> RichTextInputTransform? {
  if markdown_editor_any_code_block_at_content_offset(source, caret, selection) {
    return None
  }
  let marker = match input {
    "**" => "**"
    "__" => "__"
    "~~" => "~~"
    "`" => "`"
    "*" => "*"
    "_" => "_"
    _ => return None
  }
  match markdown_editor_active_selection(selection) {
    Some(range) => {
      let result = markdown_editor_toggle_inline_range(source, range, marker)
      Some(
        markdown_editor_transform_result(
          text=result.source,
          caret=result.caret,
          selection=result.selection,
        ),
      )
    }
    None => markdown_editor_pair_inline_marker_input(source, marker, caret)
  }
}

///|
fn markdown_editor_pair_inline_marker_input(
  source : String,
  marker : String,
  caret : Int,
) -> RichTextInputTransform? {
  if marker == "" || marker == "*" || marker == "_" {
    return None
  }
  let source_length = markdown_editor_text_length(source)
  let caret = markdown_editor_clamp_int(caret, 0, source_length)
  Some(
    markdown_editor_transform_result(
      text=markdown_editor_substring(source, 0, caret) +
        marker +
        marker +
        markdown_editor_substring(source, caret, source_length),
      caret=caret + markdown_editor_text_length(marker),
    ),
  )
}

///|
fn markdown_editor_pair_delimiter_input(
  source : String,
  caret : Int,
  selection : @core.TextRange?,
  input : String,
) -> RichTextInputTransform? {
  if markdown_editor_any_code_block_at_content_offset(source, caret, selection) {
    return None
  }
  let close = match input {
    "(" => ")"
    "[" => "]"
    "{" => "}"
    "\"" => "\""
    "'" =>
      if markdown_editor_apostrophe_in_word(source, caret, selection) {
        return None
      } else {
        "'"
      }
    "`" => "`"
    _ => return None
  }
  let source_length = markdown_editor_text_length(source)
  match markdown_editor_active_selection(selection) {
    Some(range) => {
      let normalized = range.normalized()
      let start = markdown_editor_clamp_int(normalized.start, 0, source_length)
      let end = markdown_editor_clamp_int(normalized.end, start, source_length)
      let input_length = markdown_editor_text_length(input)
      let text = markdown_editor_substring(source, 0, start) +
        input +
        markdown_editor_substring(source, start, end) +
        close +
        markdown_editor_substring(source, end, source_length)
      Some(
        markdown_editor_transform_result(
          text~,
          caret=end + input_length,
          selection=Some(
            @core.TextRange::new(
              start=start + input_length,
              end=end + input_length,
            ),
          ),
        ),
      )
    }
    None => {
      let caret = markdown_editor_clamp_int(caret, 0, source_length)
      let insert = input + close
      Some(
        markdown_editor_transform_result(
          text=markdown_editor_substring(source, 0, caret) +
            insert +
            markdown_editor_substring(source, caret, source_length),
          caret=caret + markdown_editor_text_length(input),
        ),
      )
    }
  }
}

///|
fn markdown_editor_skip_closing_delimiter(
  source : String,
  caret : Int,
  selection : @core.TextRange?,
  input : String,
) -> RichTextInputTransform? {
  if markdown_editor_active_selection(selection) is Some(_) {
    return None
  }
  let close = match input {
    "**" => "**"
    "__" => "__"
    "~~" => "~~"
    "_" => "_"
    ")" => ")"
    "]" => "]"
    "}" => "}"
    "\"" => "\""
    "'" => "'"
    "`" => "`"
    _ => return None
  }
  let source_length = markdown_editor_text_length(source)
  let caret = markdown_editor_clamp_int(caret, 0, source_length)
  if close == "_" {
    let chars = source.to_array()
    match markdown_editor_find_inline_opening_marker(chars, caret, close) {
      Some(open_start) =>
        if !markdown_editor_inline_marker_pair_supported(
            chars,
            open_start,
            markdown_editor_text_length(close),
            caret,
            close,
          ) {
          return None
        }
      None => return None
    }
    if markdown_editor_source_has_marker(chars, caret, close) {
      return Some(
        markdown_editor_transform_result(
          text=source,
          caret=caret + markdown_editor_text_length(close),
        ),
      )
    }
    return None
  }
  if markdown_editor_source_has_marker_text(source, source_length, caret, close) {
    Some(
      markdown_editor_transform_result(
        text=source,
        caret=caret + markdown_editor_text_length(close),
      ),
    )
  } else {
    None
  }
}

///|
fn markdown_editor_apostrophe_in_word(
  source : String,
  caret : Int,
  selection : @core.TextRange?,
) -> Bool {
  if markdown_editor_active_selection(selection) is Some(_) {
    return false
  }
  let source_length = markdown_editor_text_length(source)
  let caret = markdown_editor_clamp_int(caret, 0, source_length)
  if caret <= 0 || caret >= source_length {
    return false
  }
  match
    (
      markdown_editor_source_char_at(source, source_length, caret - 1),
      markdown_editor_source_char_at(source, source_length, caret),
    ) {
    (Some(previous), Some(next)) =>
      markdown_editor_ascii_word_char(previous) &&
      markdown_editor_ascii_word_char(next)
    _ => false
  }
}

///|
pub fn markdown_editor_ascii_word_char(ch : Char) -> Bool {
  (ch >= 'a' && ch <= 'z') ||
  (ch >= 'A' && ch <= 'Z') ||
  (ch >= '0' && ch <= '9') ||
  ch == '_'
}

///|
fn markdown_editor_source_char_at(
  source : String,
  source_length : Int,
  index : Int,
) -> Char? {
  if index < 0 || index >= source_length {
    return None
  }
  match source.offset_of_nth_char(index) {
    Some(offset) => source.get_char(offset)
    None => None
  }
}

///|
fn markdown_editor_source_has_marker_text(
  source : String,
  source_length : Int,
  start : Int,
  marker : String,
) -> Bool {
  let marker_length = markdown_editor_text_length(marker)
  start >= 0 &&
  start + marker_length <= source_length &&
  markdown_editor_substring(source, start, start + marker_length) == marker
}

///|
fn markdown_editor_backspace_empty_delimiter_pair(
  source : String,
  caret : Int,
  selection : @core.TextRange?,
) -> RichTextInputTransform? {
  if markdown_editor_active_selection(selection) is Some(_) {
    return None
  }
  let source_length = markdown_editor_text_length(source)
  let caret = markdown_editor_clamp_int(caret, 0, source_length)
  if caret <= 0 || caret >= source_length {
    return None
  }
  if !markdown_editor_empty_delimiter_pair_at_source(
      source, source_length, caret,
    ) {
    return None
  }
  Some(
    markdown_editor_transform_result(
      text=markdown_editor_substring(source, 0, caret - 1) +
        markdown_editor_substring(source, caret + 1, source_length),
      caret=caret - 1,
    ),
  )
}

///|
fn markdown_editor_empty_delimiter_pair_at_source(
  source : String,
  source_length : Int,
  caret : Int,
) -> Bool {
  if caret <= 0 || caret >= source_length {
    return false
  }
  match
    (
      markdown_editor_source_char_at(source, source_length, caret - 1),
      markdown_editor_source_char_at(source, source_length, caret),
    ) {
    (Some('('), Some(')')) => true
    (Some('['), Some(']')) => true
    (Some('{'), Some('}')) => true
    (Some('"'), Some('"')) => true
    (Some('\''), Some('\'')) => true
    (Some('`'), Some('`')) => true
    _ => false
  }
}

///|
fn markdown_editor_delete_empty_delimiter_pair(
  source : String,
  caret : Int,
  selection : @core.TextRange?,
) -> RichTextInputTransform? {
  if markdown_editor_active_selection(selection) is Some(_) {
    return None
  }
  let source_length = markdown_editor_text_length(source)
  let caret = markdown_editor_clamp_int(caret, 0, source_length)
  if caret + 1 >= source_length {
    return None
  }
  if !markdown_editor_empty_delimiter_pair_at_source(
      source,
      source_length,
      caret + 1,
    ) {
    return None
  }
  Some(
    markdown_editor_transform_result(
      text=markdown_editor_substring(source, 0, caret) +
        markdown_editor_substring(source, caret + 2, source_length),
      caret~,
    ),
  )
}

///|
fn markdown_editor_backspace_empty_inline_marker_pair(
  source : String,
  caret : Int,
  selection : @core.TextRange?,
) -> RichTextInputTransform? {
  if markdown_editor_active_selection(selection) is Some(_) {
    return None
  }
  let source_length = markdown_editor_text_length(source)
  let caret = markdown_editor_clamp_int(caret, 0, source_length)
  for marker in ["**", "__", "~~", "`"] {
    let marker_length = markdown_editor_text_length(marker)
    if caret >= marker_length &&
      markdown_editor_empty_inline_marker_pair_at_source(
        source,
        source_length,
        caret - marker_length,
        marker,
      ) {
      return Some(
        markdown_editor_remove_empty_inline_marker_pair(
          source,
          source_length,
          caret - marker_length,
          marker_length,
        ),
      )
    }
    if caret >= marker_length * 2 &&
      markdown_editor_empty_inline_marker_pair_at_source(
        source,
        source_length,
        caret - marker_length * 2,
        marker,
      ) {
      return Some(
        markdown_editor_remove_empty_inline_marker_pair(
          source,
          source_length,
          caret - marker_length * 2,
          marker_length,
        ),
      )
    }
  }
  None
}

///|
fn markdown_editor_delete_empty_inline_marker_pair(
  source : String,
  caret : Int,
  selection : @core.TextRange?,
) -> RichTextInputTransform? {
  if markdown_editor_active_selection(selection) is Some(_) {
    return None
  }
  let source_length = markdown_editor_text_length(source)
  let caret = markdown_editor_clamp_int(caret, 0, source_length)
  for marker in ["**", "__", "~~", "`"] {
    let marker_length = markdown_editor_text_length(marker)
    if markdown_editor_empty_inline_marker_pair_at_source(
        source, source_length, caret, marker,
      ) {
      return Some(
        markdown_editor_remove_empty_inline_marker_pair(
          source, source_length, caret, marker_length,
        ),
      )
    }
    if caret >= marker_length &&
      markdown_editor_empty_inline_marker_pair_at_source(
        source,
        source_length,
        caret - marker_length,
        marker,
      ) {
      return Some(
        markdown_editor_remove_empty_inline_marker_pair(
          source,
          source_length,
          caret - marker_length,
          marker_length,
        ),
      )
    }
  }
  None
}

///|
fn markdown_editor_empty_inline_marker_pair_at_source(
  source : String,
  source_length : Int,
  open_start : Int,
  marker : String,
) -> Bool {
  let marker_length = markdown_editor_text_length(marker)
  open_start >= 0 &&
  open_start + marker_length * 2 <= source_length &&
  markdown_editor_source_has_marker_text(
    source, source_length, open_start, marker,
  ) &&
  markdown_editor_source_has_marker_text(
    source,
    source_length,
    open_start + marker_length,
    marker,
  )
}

///|
fn markdown_editor_remove_empty_inline_marker_pair(
  source : String,
  source_length : Int,
  open_start : Int,
  marker_length : Int,
) -> RichTextInputTransform {
  markdown_editor_transform_result(
    text=markdown_editor_substring(source, 0, open_start) +
      markdown_editor_substring(
        source,
        open_start + marker_length * 2,
        source_length,
      ),
    caret=open_start,
  )
}

///|
fn markdown_editor_backspace_inline_marker(
  source : String,
  caret : Int,
) -> RichTextInputTransform? {
  let chars = source.to_array()
  let source_length = markdown_editor_text_length(source)
  let caret = markdown_editor_clamp_int(caret, 0, chars.length())
  match markdown_editor_backspace_footnote_marker(chars, caret) {
    Some(transform) => return Some(transform)
    None => ()
  }
  match markdown_editor_backspace_autolink_marker(chars, caret) {
    Some(transform) => return Some(transform)
    None => ()
  }
  match markdown_editor_backspace_reference_marker(source, chars, caret) {
    Some(transform) => return Some(transform)
    None => ()
  }
  for marker in ["**", "__", "~~", "`", "*", "_"] {
    let marker_length = markdown_editor_text_length(marker)
    if caret >= marker_length &&
      markdown_editor_source_has_marker(chars, caret - marker_length, marker) {
      match
        markdown_editor_find_inline_opening_marker(
          chars,
          caret - marker_length,
          marker,
        ) {
        Some(open_start) =>
          if markdown_editor_inline_marker_pair_supported(
              chars,
              open_start,
              marker_length,
              caret - marker_length,
              marker,
            ) {
            return Some(
              markdown_editor_remove_inline_marker_pair(
                source,
                source_length,
                open_start,
                marker_length,
                caret - marker_length,
              ),
            )
          }
        None => ()
      }
      match markdown_editor_find_inline_closing_marker(chars, caret, marker) {
        Some(close_start) =>
          if markdown_editor_inline_marker_pair_supported(
              chars,
              caret - marker_length,
              marker_length,
              close_start,
              marker,
            ) {
            return Some(
              markdown_editor_remove_inline_marker_pair(
                source,
                source_length,
                caret - marker_length,
                marker_length,
                close_start,
              ),
            )
          }
        None => ()
      }
    }
  }
  None
}

///|
fn markdown_editor_delete_inline_marker(
  source : String,
  chars : Array[Char],
  caret : Int,
) -> RichTextInputTransform? {
  let source_length = markdown_editor_text_length(source)
  match markdown_editor_delete_footnote_marker(chars, caret) {
    Some(transform) => return Some(transform)
    None => ()
  }
  match markdown_editor_delete_autolink_marker(chars, caret) {
    Some(transform) => return Some(transform)
    None => ()
  }
  match markdown_editor_delete_reference_marker(source, chars, caret) {
    Some(transform) => return Some(transform)
    None => ()
  }
  for marker in ["**", "__", "~~", "`", "*", "_"] {
    let marker_length = markdown_editor_text_length(marker)
    if markdown_editor_source_has_marker(chars, caret, marker) {
      match
        markdown_editor_find_inline_closing_marker(
          chars,
          caret + marker_length,
          marker,
        ) {
        Some(close_start) =>
          if markdown_editor_inline_marker_pair_supported(
              chars, caret, marker_length, close_start, marker,
            ) {
            return Some(
              markdown_editor_remove_inline_marker_pair(
                source, source_length, caret, marker_length, close_start,
              ),
            )
          }
        None => ()
      }
      match markdown_editor_find_inline_opening_marker(chars, caret, marker) {
        Some(open_start) =>
          if markdown_editor_inline_marker_pair_supported(
              chars, open_start, marker_length, caret, marker,
            ) {
            return Some(
              markdown_editor_remove_inline_marker_pair(
                source, source_length, open_start, marker_length, caret,
              ),
            )
          }
        None => ()
      }
    }
  }
  None
}

///|
fn markdown_editor_source_has_marker(
  chars : Array[Char],
  start : Int,
  marker : String,
) -> Bool {
  let marker_chars = marker.to_array()
  if start < 0 || start + marker_chars.length() > chars.length() {
    return false
  }
  for index in 0.. Int? {
  let marker_length = markdown_editor_text_length(marker)
  let mut index = start
  while index + marker_length <= chars.length() && chars[index] != '\n' {
    if markdown_editor_source_has_marker(chars, index, marker) {
      return Some(index)
    }
    index = index + 1
  }
  None
}

///|
fn markdown_editor_find_inline_opening_marker(
  chars : Array[Char],
  close_start : Int,
  marker : String,
) -> Int? {
  let marker_length = markdown_editor_text_length(marker)
  let mut index = close_start - marker_length
  while index >= 0 {
    if chars[index] == '\n' {
      return None
    }
    if markdown_editor_source_has_marker(chars, index, marker) {
      return Some(index)
    }
    index = index - 1
  }
  None
}

///|
fn markdown_editor_remove_inline_marker_pair(
  source : String,
  source_length : Int,
  open_start : Int,
  marker_length : Int,
  close_start : Int,
) -> RichTextInputTransform {
  let text = markdown_editor_substring(source, 0, open_start) +
    markdown_editor_substring(source, open_start + marker_length, close_start) +
    markdown_editor_substring(
      source,
      close_start + marker_length,
      source_length,
    )
  markdown_editor_transform_result(text~, caret=open_start)
}

///|
fn markdown_editor_inline_marker_pair_supported(
  chars : Array[Char],
  open_start : Int,
  marker_length : Int,
  close_start : Int,
  marker : String,
) -> Bool {
  if marker != "_" {
    return true
  }
  let before_open_is_word = open_start > 0 &&
    markdown_editor_ascii_word_char(chars[open_start - 1])
  let after_close = close_start + marker_length
  let after_close_is_word = after_close < chars.length() &&
    markdown_editor_ascii_word_char(chars[after_close])
  !before_open_is_word && !after_close_is_word
}

///|