///|
fn markdown_editor_delete_reference_marker(
  source : String,
  chars : Array[Char],
  caret : Int,
) -> RichTextInputTransform? {
  if caret >= chars.length() {
    return None
  }
  if chars[caret] == '[' ||
    (
      chars[caret] == '!' &&
      caret + 1 < chars.length() &&
      chars[caret + 1] == '['
    ) {
    let content_start = if chars[caret] == '!' { caret + 2 } else { caret + 1 }
    match markdown_editor_reference_close_after(source, chars, content_start) {
      Some(bounds) =>
        return Some(
          markdown_editor_remove_reference_marker(
            chars,
            bounds.open_start,
            content_start,
            bounds.close_start,
            bounds.target_end,
          ),
        )
      None => ()
    }
  }
  if chars[caret] != ']' {
    return None
  }
  match markdown_editor_reference_open_before(source, chars, caret) {
    Some(bounds) => {
      let mut content_start = bounds.open_start + 1
      if chars[bounds.open_start] == '!' {
        content_start = bounds.open_start + 2
      }
      Some(
        markdown_editor_remove_reference_marker(
          chars,
          bounds.open_start,
          content_start,
          caret,
          bounds.target_end,
        ),
      )
    }
    None => None
  }
}

///|
fn markdown_editor_reference_open_before(
  source : String,
  chars : Array[Char],
  close_start : Int,
) -> MarkdownLinkBounds? {
  if close_start >= chars.length() || chars[close_start] != ']' {
    return None
  }
  let mut open_start = close_start
  while open_start > 0 &&
        chars[open_start - 1] != '[' &&
        chars[open_start - 1] != '\n' {
    open_start = open_start - 1
  }
  if open_start == 0 || chars[open_start - 1] != '[' {
    return None
  }
  let bracket_start = open_start - 1
  let open_start = if bracket_start > 0 && chars[bracket_start - 1] == '!' {
    bracket_start - 1
  } else {
    bracket_start
  }
  let content_start = bracket_start + 1
  let target = match
    markdown_editor_reference_marker_target_after(
      source, chars, close_start, content_start,
    ) {
    Some(target) => target
    None => return None
  }
  Some({
    open_start,
    close_start,
    target_start: target.start,
    target_end: target.end,
    definition_target_start: -1,
    definition_target_end: -1,
    reference_label: target.label,
  })
}

///|
fn markdown_editor_backspace_reference_marker(
  source : String,
  chars : Array[Char],
  caret : Int,
) -> RichTextInputTransform? {
  if caret > 0 {
    match
      markdown_editor_reference_bounds_full_end_before(source, chars, caret) {
      Some(bounds) => {
        let mut content_start = bounds.open_start + 1
        if chars[bounds.open_start] == '!' {
          content_start = bounds.open_start + 2
        }
        return Some(
          markdown_editor_remove_reference_marker(
            chars,
            bounds.open_start,
            content_start,
            bounds.close_start,
            bounds.target_end,
          ),
        )
      }
      None => ()
    }
  }
  if caret >= 2 && chars[caret - 2] == '!' && chars[caret - 1] == '[' {
    match markdown_editor_reference_close_after(source, chars, caret) {
      Some(bounds) =>
        return Some(
          markdown_editor_remove_reference_marker(
            chars,
            caret - 2,
            caret,
            bounds.close_start,
            bounds.target_end,
          ),
        )
      None => ()
    }
  }
  if caret >= 1 && chars[caret - 1] == '[' {
    match markdown_editor_reference_close_after(source, chars, caret) {
      Some(bounds) =>
        return Some(
          markdown_editor_remove_reference_marker(
            chars,
            caret - 1,
            caret,
            bounds.close_start,
            bounds.target_end,
          ),
        )
      None => ()
    }
  }
  None
}

///|
fn markdown_editor_reference_bounds_full_end_before(
  source : String,
  chars : Array[Char],
  caret : Int,
) -> MarkdownLinkBounds? {
  if caret <= 0 {
    return None
  }
  let end = caret - 1
  if chars[end] == ')' {
    let mut open = end
    while open > 0 && chars[open - 1] != '(' && chars[open - 1] != '\n' {
      open = open - 1
    }
    if open <= 0 || chars[open - 1] != '(' {
      return None
    }
    let close_start = open - 2
    if close_start < 0 || chars[close_start] != ']' {
      return None
    }
    match markdown_editor_reference_open_before(source, chars, close_start) {
      Some(bounds) => if bounds.target_end == end { Some(bounds) } else { None }
      None => None
    }
  } else if chars[end] == ']' {
    let mut label_open = end
    while label_open > 0 &&
          chars[label_open - 1] != '[' &&
          chars[label_open - 1] != '\n' {
      label_open = label_open - 1
    }
    if label_open <= 0 || chars[label_open - 1] != '[' {
      return None
    }
    let close_start = if label_open >= 2 && chars[label_open - 2] == ']' {
      label_open - 2
    } else {
      end
    }
    match markdown_editor_reference_open_before(source, chars, close_start) {
      Some(bounds) => if bounds.target_end == end { Some(bounds) } else { None }
      None => None
    }
  } else {
    None
  }
}

///|
fn markdown_editor_reference_close_after(
  source : String,
  chars : Array[Char],
  content_start : Int,
) -> MarkdownLinkBounds? {
  let mut close_start = content_start
  while close_start < chars.length() &&
        chars[close_start] != ']' &&
        chars[close_start] != '\n' {
    close_start = close_start + 1
  }
  if close_start >= chars.length() || chars[close_start] != ']' {
    return None
  }
  let open_start = if content_start >= 2 &&
    chars[content_start - 2] == '!' &&
    chars[content_start - 1] == '[' {
    content_start - 2
  } else {
    content_start - 1
  }
  let target = match
    markdown_editor_reference_marker_target_after(
      source, chars, close_start, content_start,
    ) {
    Some(target) => target
    None => return None
  }
  Some({
    open_start,
    close_start,
    target_start: target.start,
    target_end: target.end,
    definition_target_start: -1,
    definition_target_end: -1,
    reference_label: target.label,
  })
}

///|
pub(all) struct MarkdownEditorReferenceMarkerTarget {
  start : Int
  end : Int
  label : String
}

///|
fn markdown_editor_reference_marker_target_after(
  source : String,
  chars : Array[Char],
  close_start : Int,
  content_start : Int,
) -> MarkdownEditorReferenceMarkerTarget? {
  if close_start + 1 < chars.length() && chars[close_start + 1] == '(' {
    let mut target_end = close_start + 2
    while target_end < chars.length() &&
          chars[target_end] != ')' &&
          chars[target_end] != '\n' {
      target_end = target_end + 1
    }
    if target_end < chars.length() && chars[target_end] == ')' {
      Some({ start: close_start + 2, end: target_end, label: "" })
    } else {
      None
    }
  } else if close_start + 1 < chars.length() && chars[close_start + 1] == '[' {
    let mut target_end = close_start + 2
    while target_end < chars.length() &&
          chars[target_end] != ']' &&
          chars[target_end] != '\n' {
      target_end = target_end + 1
    }
    if target_end < chars.length() && chars[target_end] == ']' {
      Some({
        start: close_start + 2,
        end: target_end,
        label: String::from_array(chars[close_start + 2:target_end]),
      })
    } else {
      None
    }
  } else {
    let label = String::from_array(chars[content_start:close_start])
    if label == "" ||
      markdown_editor_reference_definition_target_range(source, label) is None {
      None
    } else {
      Some({ start: close_start, end: close_start, label })
    }
  }
}

///|
fn markdown_editor_remove_reference_marker(
  chars : Array[Char],
  open_start : Int,
  content_start : Int,
  close_start : Int,
  target_end : Int,
) -> RichTextInputTransform {
  let text = String::from_array(chars[:open_start]) +
    String::from_array(chars[content_start:close_start]) +
    String::from_array(chars[target_end + 1:])
  markdown_editor_transform_result(text~, caret=open_start)
}