//! Second pass part 5: inline link and wikilink scanning, math/code spans, emphasis.

///|
/// Returns next byte index, url and title.
fn Parser::scan_inline_link(
  self : Parser,
  underlying : BytesView,
  ix : Int,
  node : Int?,
) -> (Int, String, String)? {
  let mut ix = ix
  guard underlying.length() > ix && underlying.unsafe_get(ix) == b'(' else {
    return None
  }
  ix += 1

  let sep1 = self.scan_separator(underlying, ix)
  ix += sep1

  match scan_link_dest(underlying, ix, LINK_MAX_NESTED_PARENS) {
    Some((dest_length, dest_start, dest_end)) => {
      let dest = unescape(
        self.text,
        dest_start,
        dest_end,
        self.tree.is_in_table(),
      )
      ix += dest_length

      let title_sep = self.scan_separator(underlying, ix)
      ix += title_sep

      // scan title
      let title = match self.scan_link_title(underlying, ix, node) {
        Some((bytes_scanned, t)) => {
          guard title_sep != 0 else { return None }
          ix += bytes_scanned
          t
        }
        None => ""
      }

      // scan any remaining whitespace
      ix += self.scan_separator(underlying, ix)

      guard underlying.length() > ix && underlying.unsafe_get(ix) == b')' else {
        return None
      }
      ix += 1

      Some((ix, dest, title))
    }
    None => None
  }
}

///|
/// Scans whitespace (including a possible line break + container prefixes).
fn Parser::scan_separator(
  self : Parser,
  underlying : BytesView,
  ix0 : Int,
) -> Int {
  let start = ix0
  let mut i = ix0
  i += scan_while(underlying.view(start=i), fn(c) {
    c.is_ascii_whitespace_no_nl()
  })
  match scan_eol(underlying.view(start=i)) {
    Some(bl) => {
      i += bl
      i += skip_container_prefixes(
        self.tree,
        underlying.view(start=i),
        self.options,
      )
    }
    None => ()
  }
  i += scan_while(underlying.view(start=i), fn(c) {
    c.is_ascii_whitespace_no_nl()
  })
  i - start
}

// returns (bytes scanned, title cow)

///|
fn Parser::scan_link_title(
  self : Parser,
  text : BytesView,
  start_ix : Int,
  node : Int?,
) -> (Int, String)? {
  let bytes = text
  guard bytes.length() > start_ix else { return None }
  let open = bytes.unsafe_get(start_ix)
  guard open == b'\'' || open == b'"' || open == b'(' else { return None }
  let close = if open == b'(' { b')' } else { open }

  let title = StringBuilder::new()
  let mut mark = start_ix + 1
  let mut i = start_ix + 1

  while i < bytes.length() {
    let c = bytes.unsafe_get(i)

    if c == close {
      let cow = if mark == 1 {
        replace_nuls(@utf8.decode_lossy(text.view(start=mark, end=i)))
      } else {
        title.write_stringview(
          @utf8.decode_lossy(text.view(start=mark, end=i)).view(),
        )
        replace_nuls(title.to_string())
      }
      return Some((i - start_ix + 1, cow))
    }
    guard c != open else { return None }

    if c == b'\n' || c == b'\r' {
      match scan_nodes_to_ix(self.tree, node, i + 1) {
        Some(node_ix) =>
          if self.tree.nodes[node_ix].item.start > i {
            title.write_stringview(
              @utf8.decode_lossy(text.view(start=mark, end=i)).view(),
            )
            title.write_string("\n")
            i = self.tree.nodes[node_ix].item.start
            mark = i
            continue
          }
        None => ()
      }
    }
    if c == b'&' {
      match scan_entity(bytes.view(start=i)) {
        (n, Some(value)) => {
          title.write_stringview(
            @utf8.decode_lossy(text.view(start=mark, end=i)).view(),
          )
          title.write_string(value)
          i += n
          mark = i
          continue
        }
        _ => ()
      }
    }
    let is_table_pipe_escape = self.tree.is_in_table() &&
      c == b'\\' &&
      i + 2 < bytes.length() &&
      bytes.unsafe_get(i + 1) == b'\\' &&
      bytes.unsafe_get(i + 2) == b'|'
    if is_table_pipe_escape {
      // this runs if there are an even number of pipes in a table
      title.write_stringview(
        @utf8.decode_lossy(text.view(start=mark, end=i)).view(),
      )
      i += 2
      mark = i
    }
    let is_backslash_escape = c == b'\\' &&
      i + 1 < bytes.length() &&
      is_ascii_punctuation(bytes.unsafe_get(i + 1).to_int())
    if is_backslash_escape {
      title.write_stringview(
        @utf8.decode_lossy(text.view(start=mark, end=i)).view(),
      )
      i += 1
      mark = i
    }

    i += 1
  }

  None
}

///|
fn Parser::make_math_span(self : Parser, open : Int, close : Int) -> Unit {
  let mut close = close
  let start_is_display = self.tree.nodes[open].next.map_or(false, fn(next_ix) {
    next_ix != close && self.tree.nodes[next_ix].item.body is MaybeMath(_, _, _)
  })
  let end_is_display = self.tree.nodes[close].next.map_or(false, fn(next_ix) {
    self.tree.nodes[next_ix].item.body is MaybeMath(_, _, _)
  })
  let is_display = start_is_display && end_is_display
  if is_display {
    // This unwrap() can't panic, because if the next variable were None, end_is_display would be None
    let next = self.tree.nodes[close].next.unwrap()
    close = next
    self.tree.nodes[open].next = Some(close)
    self.tree.nodes[open].item.end += 1
    self.tree.nodes[close].item.start -= 1
  } else {
    if self.tree.nodes[open].item.end == self.tree.nodes[close].item.start {
      // inline math spans cannot be empty
      self.tree.nodes[open].item.body = Text(false)
      return
    }
    self.tree.nodes[open].next = Some(close)
  }
  let span_start = self.tree.nodes[open].item.end
  let span_end = self.tree.nodes[close].item.start

  let spanned_text = self.text.view(start=span_start, end=span_end)
  let spanned_bytes = spanned_text
  let buf2 : StringBuilder = StringBuilder::new()
  let mut has_buf = false

  let mut start_ix = 0
  let mut ix = 0
  while ix < spanned_bytes.length() {
    let c = spanned_bytes.unsafe_get(ix)
    if c == b'\r' || c == b'\n' {
      ix += 1
      if !has_buf {
        has_buf = true
      }
      buf2.write_stringview(
        @utf8.decode_lossy(spanned_text.view(start=start_ix, end=ix)).view(),
      )
      ix += skip_container_prefixes(
        self.tree,
        spanned_bytes.view(start=ix),
        self.options,
      )
      start_ix = ix
    } else {
      let is_escaped_pipe = c == b'\\' &&
        spanned_bytes.length() > ix + 1 &&
        spanned_bytes.unsafe_get(ix + 1) == b'|' &&
        self.tree.is_in_table()
      if is_escaped_pipe {
        if !has_buf {
          has_buf = true
        }
        buf2.write_stringview(
          @utf8.decode_lossy(spanned_text.view(start=start_ix, end=ix)).view(),
        )
        buf2.write_string("|")
        ix += 2
        start_ix = ix
      } else {
        ix += 1
      }
    }
  }

  let cow = if has_buf {
    buf2.write_stringview(
      @utf8.decode_lossy(
        spanned_text.view(start=start_ix, end=spanned_bytes.length()),
      ).view(),
    )
    replace_nuls(buf2.to_string())
  } else {
    replace_nuls(
      @utf8.decode_lossy(spanned_text.view(start=0, end=spanned_bytes.length())),
    )
  }

  self.tree.nodes[open].item.body = Math(
    self.allocs.allocate_cow(cow),
    is_display,
  )
  self.tree.nodes[open].item.end = self.tree.nodes[close].item.end
  self.tree.nodes[open].next = self.tree.nodes[close].next
}

///|
/// Make a code span.
fn Parser::make_code_span(
  self : Parser,
  open : Int,
  close : Int,
  preceding_backslash : Bool,
) -> Unit {
  let span_start = self.tree.nodes[open].item.end
  let span_end = self.tree.nodes[close].item.start
  let buf = StringBuilder::new()
  let mut has_buf = false

  let spanned_text = self.text.view(start=span_start, end=span_end)
  let spanned_bytes = spanned_text
  let mut start_ix = 0
  let mut ix = 0
  while ix < spanned_bytes.length() {
    let c = spanned_bytes.unsafe_get(ix)
    if c == b'\r' || c == b'\n' {
      if !has_buf {
        has_buf = true
      }
      buf.write_stringview(
        @utf8.decode_lossy(spanned_text.view(start=start_ix, end=ix)).view(),
      )
      buf.write_string(" ")
      ix += 1
      ix += skip_container_prefixes(
        self.tree,
        spanned_bytes.view(start=ix),
        self.options,
      )
      start_ix = ix
    } else {
      let is_escaped_pipe = c == b'\\' &&
        spanned_bytes.length() > ix + 1 &&
        spanned_bytes.unsafe_get(ix + 1) == b'|' &&
        self.tree.is_in_table()
      if is_escaped_pipe {
        if !has_buf {
          has_buf = true
        }
        buf.write_stringview(
          @utf8.decode_lossy(spanned_text.view(start=start_ix, end=ix)).view(),
        )
        buf.write_string("|")
        ix += 2
        start_ix = ix
      } else {
        ix += 1
      }
    }
  }

  let (opening, closing, all_spaces) = {
    let s = if has_buf {
      buf.write_stringview(
        @utf8.decode_lossy(
          spanned_text.view(start=start_ix, end=spanned_bytes.length()),
        ).view(),
      )
      buf.to_string()
    } else {
      @utf8.decode_lossy(spanned_text.view(start=0, end=spanned_bytes.length()))
    }
    let sb = @utf8.encode(s)
    (
      sb.length() > 0 && sb.unsafe_get(0) == b' ',
      sb.length() > 0 && sb.unsafe_get(sb.length() - 1) == b' ',
      s.split(" ").all(fn(v) { v.is_empty() }),
    )
  }

  let cow = if !all_spaces && opening && closing {
    let sb = @utf8.encode(buf.to_string())
    if has_buf {
      let inner = if sb.length() > 0 {
        @utf8.decode_lossy(sb.view(start=1, end=sb.length() - 1))
      } else {
        ""
      }
      replace_nuls(inner)
    } else {
      let slen = spanned_bytes.length()
      let inner = if slen > 1 {
        @utf8.decode_lossy(spanned_text.view(start=1, end=slen - 1))
      } else {
        ""
      }
      replace_nuls(inner)
    }
  } else if has_buf {
    replace_nuls(buf.to_string())
  } else {
    replace_nuls(
      @utf8.decode_lossy(spanned_text.view(start=0, end=spanned_bytes.length())),
    )
  }

  if preceding_backslash {
    self.tree.nodes[open].item.body = Text(true)
    self.tree.nodes[open].item.end = self.tree.nodes[open].item.start + 1
    self.tree.nodes[open].next = Some(close)
    self.tree.nodes[close].item.body = Code(self.allocs.allocate_cow(cow))
    self.tree.nodes[close].item.start = self.tree.nodes[open].item.start + 1
  } else {
    self.tree.nodes[open].item.body = Code(self.allocs.allocate_cow(cow))
    self.tree.nodes[open].item.end = self.tree.nodes[close].item.end
    self.tree.nodes[open].next = self.tree.nodes[close].next
  }
}

///|
/// On success, returns a buffer containing the inline html and byte offset.
fn Parser::scan_inline_html(
  self : Parser,
  bytes : BytesView,
  ix : Int,
) -> (Array[Byte], Int)? {
  guard bytes.length() > ix else { return None }
  let c = bytes.unsafe_get(ix)
  if c == b'!' {
    scan_inline_html_comment(bytes, ix + 1, self.html_scan_guard).map(n => {
      ([], n)
    })
  } else if c == b'?' {
    scan_inline_html_processing(bytes, ix + 1, self.html_scan_guard).map(n => {
      ([], n)
    })
  } else {
    match
      scan_html_block_inner(
        // Subtract 1 to include the < character
        bytes.view(start=ix - 1),
        Some(fn(bytes : BytesView) -> Int {
          skip_container_prefixes(self.tree, bytes, self.options)
        }),
      ) {
      Some((span, i)) => Some((span, i + ix - 1))
      None => None
    }
  }
}

///|
fn Parser::disable_all_links(self : Parser) -> Unit {
  self.link_stack.disable_all_links()
  self.wikilink_stack.disable_all_links()
}

///|
/// Use a link label to fetch a type, url, and title.
fn Parser::fetch_link_type_url_title(
  self : Parser,
  link_label : String,
  _span : (Int, Int),
  link_type : LinkType,
) -> (LinkType, String, String)? {
  guard self.link_ref_expansion_limit != 0 else { return None }

  let found = self.allocs.refdefs
    .get(unicase_fold(link_label))
    .map(fn(matching_def) {
      (link_type, matching_def.dest, matching_def.title.unwrap_or(""))
    })

  guard found is Some(found) else { return None }
  let (link_type, url, title) = found

  // Limit expansion from link references.
  self.link_ref_expansion_limit = (0).max(
    self.link_ref_expansion_limit - (url.length() + title.length()),
  )

  Some((link_type, url, title))
}

///|
/// Skips forward within a block to a node which spans (ends inclusive) the given
/// index into the source.
fn skip_container_prefixes(
  tree : Tree[Item],
  bytes : BytesView,
  options : Options,
) -> Int {
  let line_start = LineStart::new(bytes)
  ignore(line_start.scan_containers(tree, options))
  line_start.bytes_scanned()
}

///|
/// Handles a wikilink.
fn Parser::handle_wikilink(
  self : Parser,
  block_text : BytesView,
  cur_ix : Int,
  prev : Int?,
) -> Int? {
  guard self.tree.nodes[cur_ix].next is Some(next_ix) else { return None }
  match self.wikilink_stack.pop() {
    Some(tos) => {
      guard !(tos.ty is Disabled) else { return None }
      // fetches the beginning of the wikilink body
      let body_node = match self.tree.nodes[tos.node].next {
        Some(a) => self.tree.nodes[a].next
        None => None
      }
      guard body_node is Some(body_node) else { return None }
      let start_ix = self.tree.nodes[body_node].item.start
      let end_ix = self.tree.nodes[cur_ix].item.start
      // bail early in case the link is malformed
      guard end_ix > start_ix else { return None }
      let wikilink = match
        scan_wikilink_pipe(block_text, start_ix, end_ix - start_ix) {
        Some((rest, pipe_pos)) => {
          let wikitext = @utf8.decode_lossy(
            block_text.view(start=start_ix, end=pipe_pos),
          )
          // bail early if the wikiname would be empty
          guard !wikitext.is_empty() else { return None }
          // [[WikiName|rest]]
          if rest >= end_ix {
            // Empty display text: the `|` is immediately followed by `]]`.
            let body_node = self.tree.create_node(Item::{
              start: rest,
              end: rest,
              body: Text(false),
            })
            Some((true, body_node, wikitext))
          } else {
            match scan_nodes_to_ix(self.tree, Some(body_node), rest) {
              Some(body_node) => {
                // break node so passes can actually format the display text
                self.tree.nodes[body_node].item.start = rest
                Some((true, body_node, wikitext))
              }
              None => None
            }
          }
        }
        None => {
          let wikitext = @utf8.decode_lossy(
            block_text.view(start=start_ix, end=end_ix),
          )
          // bail early if the wikiname would be empty
          guard !wikitext.is_empty() else { return None }
          let body_node = self.tree.create_node(Item::{
            start: start_ix,
            end: end_ix,
            body: Text(false),
          })
          if wikitext.contains("\u0000") {
            self.tree.nodes[body_node].item.body = SynthesizeText(
              self.allocs.allocate_cow(replace_nuls(wikitext)),
            )
          }
          Some((false, body_node, wikitext))
        }
      }

      match wikilink {
        Some((has_pothole, body_node, wikiname)) => {
          let link_ix = self.allocs.allocate_link(
            WikiLink(has_pothole),
            replace_nuls(wikiname),
            "",
            "",
          )
          match prev {
            Some(prev_ix) => self.tree.nodes[prev_ix].next = None
            None => ()
          }
          self.tree.nodes[tos.node].item.body = if tos.ty is Image {
            Image(link_ix)
          } else {
            Link(link_ix)
          }
          self.tree.nodes[tos.node].child = Some(body_node)
          self.tree.nodes[tos.node].next = self.tree.nodes[next_ix].next
          self.tree.nodes[tos.node].item.end = end_ix + 2
          self.disable_all_links()
          return Some(tos.node)
        }
        None => ()
      }
    }
    None => ()
  }

  None
}