///| Link reference definitions and GFM footnote definitions.

///| Split out from block_parser.mbt to keep that file focused on the container

///| algorithm.

///|
/// Strip the link reference definitions a paragraph starts with, registering
/// each one, and return what is left of the paragraph.
fn BlockParser::strip_link_definitions(
  self : BlockParser,
  node : Node,
) -> String {
  self.take_link_definitions(node, true)
}

///|
/// Same, but without registering anything: used to decide whether a paragraph
/// would still hold text.
fn BlockParser::strip_link_definitions_dry(
  self : BlockParser,
  node : Node,
) -> String {
  self.take_link_definitions(node, false)
}

///|
fn BlockParser::take_link_definitions(
  self : BlockParser,
  node : Node,
  register : Bool,
) -> String {
  let content = joined_lines(node)
  let len = content.length()
  let mut pos = 0
  while pos < len && content.unsafe_get(pos) == '[' {
    match self.parse_link_reference(node, content, pos, register) {
      Some(next) => pos = next
      None => break
    }
  }
  if pos == 0 {
    return content
  }
  content.unsafe_substring(start=pos, end=len)
}

///|
fn skip_spaces_tabs(text : String, start : Int) -> Int {
  let len = text.length()
  let mut i = start
  while i < len && is_space_or_tab(text.unsafe_get(i)) {
    i = i + 1
  }
  i
}

///|
/// Skip whitespace, allowing at most one line ending. Returns the new position
/// and how many line endings were crossed.
fn skip_ws_one_newline(text : String, start : Int) -> (Int, Int) {
  let len = text.length()
  let mut i = skip_spaces_tabs(text, start)
  let mut newlines = 0
  if i < len && text.unsafe_get(i) == '\n' {
    newlines = 1
    i = skip_spaces_tabs(text, i + 1)
  }
  (i, newlines)
}

///|
/// Parse `[label]: destination "title"` at `pos`, returning the position just
/// past it.
fn BlockParser::parse_link_reference(
  self : BlockParser,
  node : Node,
  text : String,
  pos : Int,
  register : Bool,
) -> Int? {
  let len = text.length()
  guard pos < len && text.unsafe_get(pos) == '[' else { return None }
  let label_start = pos + 1
  let mut i = label_start
  let mut label_end = -1
  while i < len {
    let c = text.unsafe_get(i)
    if c == '\\' && i + 1 < len {
      i = i + 2
      continue
    }
    if c == '[' {
      return None
    }
    if c == ']' {
      label_end = i
      break
    }
    i = i + 1
  }
  guard label_end >= 0 else { return None }
  let label = text.unsafe_substring(start=label_start, end=label_end)
  guard label.length() <= 999 && !is_blank_text(label) else { return None }
  i = label_end + 1
  guard i < len && text.unsafe_get(i) == ':' else { return None }
  i = i + 1
  let (after_colon, _) = skip_ws_one_newline(text, i)
  i = after_colon
  guard parse_link_destination(text, i) is Some((raw_dest, after_dest)) else {
    return None
  }
  guard after_dest > i else { return None }
  let mut end = -1
  let mut title = ""
  let (title_start, newlines) = skip_ws_one_newline(text, after_dest)
  if title_start > after_dest || newlines > 0 {
    match parse_link_title(text, title_start) {
      Some((raw_title, after_title)) => {
        let line_end = skip_spaces_tabs(text, after_title)
        if line_end >= len || text.unsafe_get(line_end) == '\n' {
          title = unescape_string(raw_title)
          end = if line_end < len { line_end + 1 } else { line_end }
        }
      }
      None => ()
    }
  }
  if end < 0 {
    let line_end = skip_spaces_tabs(text, after_dest)
    guard line_end >= len || text.unsafe_get(line_end) == '\n' else {
      return None
    }
    end = if line_end < len { line_end + 1 } else { line_end }
  }
  if register {
    self.definitions.push({
      label,
      url: normalize_destination(raw_dest),
      title,
      span: Span::new(source_offset_in(node, pos), source_offset_in(node, end)),
    })
  }
  Some(end)
}

///|
/// GFM footnote definition: `[^label]: content`.
fn BlockParser::try_footnote_definition(
  self : BlockParser,
  container : Node,
) -> Node? {
  guard self.peek_line(self.next_nonspace) == '[' else { return None }
  guard self.peek_line(self.next_nonspace + 1) == '^' else { return None }
  let mut i = self.next_nonspace + 2
  while i < self.line_len {
    let c = self.line.unsafe_get(i)
    if c == ']' {
      break
    }
    if c == '[' || c == '\n' || c == '\r' || c == 0 {
      return None
    }
    i = i + 1
  }
  guard i < self.line_len && i > self.next_nonspace + 2 else { return None }
  guard i + 1 < self.line_len && self.line.unsafe_get(i + 1) == ':' else {
    return None
  }
  let label = self.line
    .unsafe_substring(start=self.next_nonspace + 2, end=i)
    .trim(chars=" \t")
    .to_owned()
  guard !label.is_empty() else { return None }
  let node = self.add_child(
    container,
    NodeKind::FootnoteDefNode,
    self.line_start + self.next_nonspace,
  )
  node.label = label
  node.end = self.line_end
  self.advance_offset(i + 2 - self.offset, false)
  if is_space_or_tab(self.peek_line(self.offset)) {
    self.advance_offset(1, true)
  }
  Some(node)
}