//! Second pass part 2: inline handling (HTML, code spans, links, emphasis) and
//! supporting stacks and link-label scanning.

///|
/// Handle inline markup.
fn Parser::handle_inline(self : Parser) -> Unit {
  self.handle_inline_pass1()
  self.handle_emphasis_and_hard_break()
}

///|
/// Handle inline HTML, code spans, and links.
fn Parser::handle_inline_pass1(self : Parser) -> Unit {
  let mut cur : Int? = self.tree.cur()
  let mut prev : Int? = None

  let block_end = self.tree.nodes[self.tree.peek_up().unwrap()].item.end
  let block_text = self.text.view(start=0, end=block_end)

  while true {
    match cur {
      Some(cur_ix) => {
        let mut cur_ix = cur_ix
        match self.tree.nodes[cur_ix].item.body {
          MaybeHtml => {
            let next = self.tree.nodes[cur_ix].next
            let autolink = match next {
              Some(next_ix) =>
                scan_autolink(block_text, self.tree.nodes[next_ix].item.start)
              None => None
            }

            match autolink {
              Some((ix, uri, link_type)) => {
                let node = scan_nodes_to_ix(self.tree, next, ix)
                let text_node = self.tree.create_node(Item::{
                  start: self.tree.nodes[cur_ix].item.start + 1,
                  end: ix - 1,
                  body: Text(false),
                })
                let link_ix = self.allocs.allocate_link(link_type, uri, "", "")
                self.tree.nodes[cur_ix].item.body = Link(link_ix)
                self.tree.nodes[cur_ix].item.end = ix
                self.tree.nodes[cur_ix].next = node
                self.tree.nodes[cur_ix].child = Some(text_node)
                prev = cur
                cur = node
                match cur {
                  Some(node_ix) =>
                    self.tree.nodes[node_ix].item.start = self.tree.nodes[node_ix].item.start.max(
                      ix,
                    )
                  None => ()
                }
                continue
              }
              None => {
                let inline_html = match next {
                  Some(next_ix) =>
                    self.scan_inline_html(
                      block_text,
                      self.tree.nodes[next_ix].item.start,
                    )
                  None => None
                }
                match inline_html {
                  Some((span, ix)) => {
                    let node = scan_nodes_to_ix(self.tree, next, ix)
                    self.tree.nodes[cur_ix].item.body = if !span.is_empty() {
                      let converted_string = @utf8.decode_lossy(
                        Bytes::from_array(span).view(),
                      )
                      OwnedInlineHtml(
                        self.allocs.allocate_cow(replace_nuls(converted_string)),
                      )
                    } else {
                      InlineHtml
                    }
                    self.tree.nodes[cur_ix].item.end = ix
                    self.tree.nodes[cur_ix].next = node
                    prev = cur
                    cur = node
                    match cur {
                      Some(node_ix) =>
                        self.tree.nodes[node_ix].item.start = self.tree.nodes[node_ix].item.start.max(
                          ix,
                        )
                      None => ()
                    }
                    continue
                  }
                  None => ()
                }
              }
            }
            self.tree.nodes[cur_ix].item.body = Text(false)
          }
          MaybeMath(can_open, _can_close, brace_context) => {
            guard can_open else {
              self.tree.nodes[cur_ix].item.body = Text(false)
              prev = cur
              cur = self.tree.nodes[cur_ix].next
              continue
            }
            let is_display = self.tree.nodes[cur_ix].next.map_or(false, fn(
              next_ix,
            ) {
              self.tree.nodes[next_ix].item.body is MaybeMath(_, _, _)
            })
            let result = if self.math_delims.is_populated() {
              // we have previously scanned all math environment delimiters
              self.math_delims.find(
                self.tree,
                cur_ix,
                is_display,
                brace_context,
              )
            } else {
              // we haven't previously scanned all math delimiters
              let mut scan = self.tree.nodes[cur_ix].next
              if is_display {
                // a display delimiter, `$$`, is actually two delimiters
                match scan {
                  Some(s) => scan = self.tree.nodes[s].next
                  None => ()
                }
              }
              let mut invalid = false
              while true {
                match scan {
                  Some(scan_ix) => {
                    match self.tree.nodes[scan_ix].item.body {
                      MaybeMath(_can_open, can_close, delim_brace_context) => {
                        let delim_is_display = self.tree.nodes[scan_ix].next.map_or(
                          false,
                          fn(next_ix) {
                            self.tree.nodes[next_ix].item.body
                            is MaybeMath(_, _, _)
                          },
                        )
                        let use_it = !invalid &&
                          delim_brace_context == brace_context
                        if use_it {
                          if (!is_display && can_close) ||
                            (is_display && delim_is_display) {
                            // This will skip ahead past everything we just inserted.
                            self.math_delims.clear()
                            break
                          } else {
                            // Math cannot contain $, so the current item is invalid.
                            invalid = true
                          }
                        }
                        self.math_delims.insert(
                          delim_is_display, delim_brace_context, scan_ix, can_close,
                        )
                      }
                      _ => ()
                    }
                    scan = self.tree.nodes[scan_ix].next
                  }
                  None => break
                }
              }
              scan
            }

            match result {
              Some(scan_ix) => self.make_math_span(cur_ix, scan_ix)
              None => self.tree.nodes[cur_ix].item.body = Text(false)
            }
          }
          MaybeCode(search_count, preceded_by_backslash) => {
            let mut search_count = search_count
            if preceded_by_backslash {
              search_count -= 1
              guard search_count != 0 else {
                self.tree.nodes[cur_ix].item.body = Text(false)
                prev = cur
                cur = self.tree.nodes[cur_ix].next
                continue
              }
            }

            if self.code_delims.is_populated() {
              // we have previously scanned all codeblock delimiters
              match self.code_delims.find(cur_ix, search_count) {
                Some(scan_ix) =>
                  self.make_code_span(cur_ix, scan_ix, preceded_by_backslash)
                None => self.tree.nodes[cur_ix].item.body = Text(false)
              }
            } else {
              // we haven't previously scanned all codeblock delimiters
              let mut scan = if search_count > 0 {
                self.tree.nodes[cur_ix].next
              } else {
                None
              }
              while true {
                match scan {
                  Some(scan_ix) => {
                    match self.tree.nodes[scan_ix].item.body {
                      MaybeCode(delim_count, _) =>
                        if search_count == delim_count {
                          self.make_code_span(
                            cur_ix, scan_ix, preceded_by_backslash,
                          )
                          self.code_delims.clear()
                          break
                        } else {
                          self.code_delims.insert(delim_count, scan_ix)
                        }
                      _ => ()
                    }
                    scan = self.tree.nodes[scan_ix].next
                  }
                  None => break
                }
              }
              if scan is None {
                self.tree.nodes[cur_ix].item.body = Text(false)
              }
            }
          }
          MaybeLinkOpen => {
            self.tree.nodes[cur_ix].item.body = Text(false)
            let link_open_doubled = self.tree.nodes[cur_ix].next.map_or(false, fn(
              ix,
            ) {
              self.tree.nodes[ix].item.body is MaybeLinkOpen
            })
            if self.options.contains(enable_wikilinks()) && link_open_doubled {
              self.wikilink_stack.push(LinkStackEl::{ node: cur_ix, ty: Link })
            }
            self.link_stack.push(LinkStackEl::{ node: cur_ix, ty: Link })
          }
          MaybeImage => {
            self.tree.nodes[cur_ix].item.body = Text(false)
            let link_open_doubled = self.tree.nodes[cur_ix].next.map_or(false, fn(
              ix,
            ) {
              self.tree.nodes[ix].item.body is MaybeLinkOpen
            })
            if self.options.contains(enable_wikilinks()) && link_open_doubled {
              self.wikilink_stack.push(LinkStackEl::{ node: cur_ix, ty: Image })
            }
            self.link_stack.push(LinkStackEl::{ node: cur_ix, ty: Image })
          }
          MaybeLinkClose(could_be_ref) => {
            self.tree.nodes[cur_ix].item.body = Text(false)
            let tos_link = self.link_stack.pop()
            let next_is_link_close = self.tree.nodes[cur_ix].next.map_or(
              false,
              fn(ix) { self.tree.nodes[ix].item.body is MaybeLinkClose(_) },
            )
            let is_wikilink_close = self.options.contains(enable_wikilinks()) &&
              next_is_link_close
            if is_wikilink_close {
              match self.handle_wikilink(block_text, cur_ix, prev) {
                Some(node) => {
                  prev = Some(node)
                  cur = self.tree.nodes[node].next
                  continue
                }
                None => ()
              }
            }
            match tos_link {
              Some(tos) => {
                // skip rendering if already in a link, unless its an image
                let parent_is_link = self.tree.nodes[self.tree
                  .peek_up()
                  .unwrap()].item.body
                  is Link(_)
                let in_link = !(tos.ty is Image) && parent_is_link
                guard !in_link else { continue }
                guard !(tos.ty is Disabled) else { continue }
                let next = self.tree.nodes[cur_ix].next
                match
                  self.scan_inline_link(
                    block_text,
                    self.tree.nodes[cur_ix].item.end,
                    next,
                  ) {
                  Some((next_ix, url, title)) => {
                    let next_node = scan_nodes_to_ix(self.tree, next, next_ix)
                    match prev {
                      Some(prev_ix) => self.tree.nodes[prev_ix].next = None
                      None => ()
                    }
                    cur = Some(tos.node)
                    cur_ix = tos.node
                    let link_ix = self.allocs.allocate_link(
                      Inline,
                      url,
                      title,
                      "",
                    )
                    self.tree.nodes[cur_ix].item.body = if tos.ty is Image {
                      Image(link_ix)
                    } else {
                      Link(link_ix)
                    }
                    self.tree.nodes[cur_ix].child = self.tree.nodes[cur_ix].next
                    self.tree.nodes[cur_ix].next = next_node
                    self.tree.nodes[cur_ix].item.end = next_ix
                    match next_node {
                      Some(next_node_ix) =>
                        self.tree.nodes[next_node_ix].item.start = self.tree.nodes[next_node_ix].item.start.max(
                          next_ix,
                        )
                      None => ()
                    }

                    if tos.ty is Link {
                      self.disable_all_links()
                    }
                  }
                  None => {
                    // ok, so its not an inline link. maybe it is a reference
                    let scan_result = scan_reference(
                      self.tree,
                      block_text,
                      cur_ix,
                      self.options,
                    )
                    let (node_after_link, link_type) = match scan_result {
                      // [label][reference]
                      RefScan::LinkLabel(_, end_ix) => {
                        guard scan_nodes_to_ix(self.tree, next, end_ix - 1)
                          is Some(reference_close_node) else {
                          continue
                        }
                        self.tree.nodes[reference_close_node].item.body = MaybeLinkClose(
                          false,
                        )
                        let next_node = self.tree.nodes[reference_close_node].next

                        (next_node, Reference)
                      }
                      // [reference][]
                      RefScan::Collapsed(next_node) => {
                        guard could_be_ref else { continue }
                        (next_node, Collapsed)
                      }
                      // [shortcut]
                      RefScan::Failed | RefScan::UnexpectedFootnote => {
                        guard could_be_ref else { continue }
                        (next, Shortcut)
                      }
                    }

                    let label : (ReferenceLabel, Int)? = match scan_result {
                      RefScan::LinkLabel(l, end_ix) =>
                        Some((ReferenceLabel::Link(l), end_ix))
                      RefScan::Collapsed(_)
                      | RefScan::Failed
                      | RefScan::UnexpectedFootnote => {
                        // No label? maybe it is a shortcut reference
                        let label_start = self.tree.nodes[tos.node].item.end - 1
                        let label_end = self.tree.nodes[cur_ix].item.end
                        match
                          scan_link_label(
                            self.tree,
                            self.text.view(start=label_start, end=label_end),
                            self.options,
                          ) {
                          Some((ix, label)) =>
                            if label_start + ix == label_end {
                              Some((label, label_start + ix))
                            } else {
                              None
                            }
                          None => None
                        }
                      }
                    }

                    let id = match label {
                      Some(
                        (ReferenceLabel::Link(l), _)
                        | (ReferenceLabel::Footnote(l), _)
                      ) => l
                      None => ""
                    }

                    // see if it's a footnote reference
                    match label {
                      Some((ReferenceLabel::Footnote(l), end)) => {
                        let footref = self.allocs.allocate_cow(l)
                        let folded_label = unicase_fold(l)
                        if self.allocs.footdefs.contains(folded_label) {
                          let def = self.allocs.footdefs[folded_label]
                          def._use_count += 1
                          self.allocs.footdefs[folded_label] = def
                        }
                        let should_emit = !self.options.options_has_gfm_footnotes() ||
                          self.allocs.footdefs.contains(folded_label)
                        if should_emit {
                          let footnote_ix = if tos.ty is Image {
                            self.tree.nodes[tos.node].next = Some(cur_ix)
                            self.tree.nodes[tos.node].child = None
                            self.tree.nodes[tos.node].item.body = SynthesizeChar(
                              '!',
                            )
                            self.tree.nodes[cur_ix].item.start = self.tree.nodes[tos.node].item.start +
                              1
                            self.tree.nodes[tos.node].item.end = self.tree.nodes[tos.node].item.start +
                              1
                            cur_ix
                          } else {
                            tos.node
                          }
                          self.tree.nodes[footnote_ix].next = next
                          self.tree.nodes[footnote_ix].child = None
                          self.tree.nodes[footnote_ix].item.body = FootnoteReference(
                            footref,
                          )
                          self.tree.nodes[footnote_ix].item.end = end
                          prev = Some(footnote_ix)
                          cur = next
                          self.link_stack.clear()
                          continue
                        }
                      }
                      Some((ReferenceLabel::Link(link_label), end)) =>
                        match
                          self.fetch_link_type_url_title(
                            link_label,
                            (self.tree.nodes[tos.node].item.start, end),
                            link_type,
                          ) {
                          Some((def_link_type, url, title)) => {
                            let link_ix = self.allocs.allocate_link(
                              def_link_type, url, title, id,
                            )
                            self.tree.nodes[tos.node].item.body = if tos.ty
                              is Image {
                              Image(link_ix)
                            } else {
                              Link(link_ix)
                            }
                            let label_node = self.tree.nodes[tos.node].next

                            // lets do some tree surgery to add the link to the tree
                            self.tree.nodes[tos.node].next = node_after_link

                            if label_node != cur {
                              self.tree.nodes[tos.node].child = label_node

                              // finally: disconnect list of children
                              match prev {
                                Some(prev_ix) =>
                                  self.tree.nodes[prev_ix].next = None
                                None => ()
                              }
                            }

                            self.tree.nodes[tos.node].item.end = end

                            // set up cur so next node will be node_after_link
                            cur = Some(tos.node)
                            cur_ix = tos.node

                            if tos.ty is Link {
                              self.disable_all_links()
                            }
                          }
                          None => ()
                        }
                      None => ()
                    }
                  }
                }
              }
              None => ()
            }
          }
          _ => ()
        }
        prev = cur
        cur = self.tree.nodes[cur_ix].next
      }
      None => break
    }
  }
  self.link_stack.clear()
  self.wikilink_stack.clear()
  self.code_delims.clear()
  self.math_delims.clear()
}

///|
priv enum ReferenceLabel {
  Link(String)
  Footnote(String)
}

///|
priv enum RefScan {
  // label, source ix of label end
  LinkLabel(String, Int)
  // contains next node index
  Collapsed(Int?)
  UnexpectedFootnote
  Failed
}

///|
/// Skips forward within a block to a node which spans (ends inclusive) the given
/// index into the source.
fn scan_nodes_to_ix(tree : Tree[Item], node : Int?, ix : Int) -> Int? {
  let mut node = node
  while true {
    match node {
      Some(node_ix) =>
        if tree.nodes[node_ix].item.end <= ix {
          node = tree.nodes[node_ix].next
        } else {
          break
        }
      None => break
    }
  }
  node
}

///|
/// Scans an inline link label, which cannot be interrupted.
fn scan_link_label(
  tree : Tree[Item],
  text : BytesView,
  options : Options,
) -> (Int, ReferenceLabel)? {
  let bytes = text
  guard bytes.length() >= 2 && bytes.unsafe_get(0) == b'[' else { return None }
  let is_footnote = options.contains(enable_footnotes()) &&
    bytes.length() > 2 &&
    bytes.unsafe_get(1) == b'^' &&
    bytes.unsafe_get(2) != b']'
  if is_footnote {
    let handler : ((BytesView) -> Int?)? = if options.options_has_gfm_footnotes() {
      None
    } else {
      Some(fn(bytes : BytesView) -> Int? {
        Some(skip_container_prefixes(tree, bytes, options))
      })
    }
    match scan_link_label_rest(text, 2, handler, tree.is_in_table()) {
      Some((byte_index, cow)) =>
        return Some((byte_index + 2, ReferenceLabel::Footnote(cow)))
      None => ()
    }
  }
  let handler : ((BytesView) -> Int?)? = Some(fn(bytes : BytesView) -> Int? {
    Some(skip_container_prefixes(tree, bytes, options))
  })
  match scan_link_label_rest(text, 1, handler, tree.is_in_table()) {
    Some((byte_index, cow)) => Some((byte_index + 1, ReferenceLabel::Link(cow)))
    None => None
  }
}

///|
fn scan_reference(
  tree : Tree[Item],
  text : BytesView,
  cur_ix : Int,
  options : Options,
) -> RefScan {
  let start = tree.nodes[cur_ix].item.end
  let tail = text.view(start~)

  if tail.has_prefix(b"[]".view()) {
    let next_ix = match tree.nodes[cur_ix].next {
      Some(next_ix) if tree.nodes[next_ix].item.start ==
        tree.nodes[cur_ix].item.end => next_ix
      _ => return RefScan::Failed
    }
    guard tree.nodes[next_ix].next is Some(closing_node) else {
      return RefScan::Failed
    }
    RefScan::Collapsed(tree.nodes[closing_node].next)
  } else {
    match scan_link_label(tree, text.view(start~), options) {
      Some((ix, ReferenceLabel::Link(label))) =>
        RefScan::LinkLabel(label, start + ix)
      Some((_ix, ReferenceLabel::Footnote(_label))) =>
        RefScan::UnexpectedFootnote
      None => RefScan::Failed
    }
  }
}

///|
fn replace_nuls(s : String) -> String {
  s.replace_all(old="\u{0}", new="\u{fffd}")
}

///|
priv struct LinkStackEl {
  node : Int
  mut ty : LinkStackTy
}

///|
priv enum LinkStackTy {
  Link
  Image
  Disabled
}

///|
struct LinkStack {
  inner : Array[LinkStackEl]
  mut disabled_ix : Int
}

///|
fn link_stack_new() -> LinkStack {
  { inner: [], disabled_ix: 0 }
}

///|
fn LinkStack::push(self : LinkStack, el : LinkStackEl) -> Unit {
  self.inner.push(el)
}

///|
fn LinkStack::pop(self : LinkStack) -> LinkStackEl? {
  let el = self.inner.pop()
  self.disabled_ix = self.disabled_ix.min(self.inner.length())
  el
}

///|
fn LinkStack::clear(self : LinkStack) -> Unit {
  self.inner.clear()
  self.disabled_ix = 0
}

///|
fn LinkStack::disable_all_links(self : LinkStack) -> Unit {
  for i in self.disabled_ix..