// The inline phase's main loop and the handlers for the constructs that are
// not emphasis: line breaks, code spans, backslashes, entities, raw HTML,
// autolinks, brackets and links.

///|
/// Parse the text of a leaf block into inlines.
fn parse_inlines(
  text : String,
  refs : Map[String, Reference],
  flavor : Flavor,
) -> Array[Inline] {
  let input = rtrim_string(text)
  let subject = Subject::new(input, refs, flavor)
  while !subject.eof() {
    subject.parse_inline()
  }
  subject.process_emphasis(0)
  while subject.last_delim >= 0 {
    subject.remove_delimiter(subject.last_delim)
  }
  subject.last_bracket = -1
  consolidate_text(subject.tree, subject.root)
  if flavor.autolinks {
    subject.postprocess_emails(subject.root)
  }
  materialize_inlines(subject.tree, subject.root)
}

///|
/// Drop the whitespace at the end, which belongs to no inline.
fn rtrim_string(text : String) -> String {
  let mut n = text.length()
  while n > 0 {
    let c = cu(text[:], n - 1)
    if c == 0x20 ||
      c == 0x09 ||
      c == 0x0A ||
      c == 0x0D ||
      c == 0x0B ||
      c == 0x0C {
      n = n - 1
    } else {
      break
    }
  }
  text.clamped_view(start=0, end=n).to_owned()
}

///|
/// Drop the spaces and tabs at the end of a run of text.
fn rtrim_view(text : StringView) -> StringView {
  let mut n = text.length()
  while n > 0 {
    let c = cu(text, n - 1)
    if c == 0x20 || c == 0x09 {
      n = n - 1
    } else {
      break
    }
  }
  text.clamped_view(start=0, end=n)
}

///|
/// One step of the loop: look at the character at the cursor and handle it.
fn Subject::parse_inline(self : Subject) -> Unit {
  let c = self.peek(self.pos)
  match c {
    0x0A | 0x0D => self.handle_newline()
    0x60 => self.handle_backticks()
    0x5C => self.handle_backslash()
    0x26 => self.handle_entity()
    0x3C => self.handle_pointy_brace()
    0x2A | 0x5F => self.handle_delim(c)
    0x5B => {
      self.pos = self.pos + 1
      let node = self.add_text("[")
      self.push_bracket(false, node)
    }
    0x5D => self.handle_close_bracket()
    0x21 => {
      self.pos = self.pos + 1
      // A `!` followed by `[^` opens a footnote reference, not an image: the
      // exclamation mark is then ordinary text.
      if self.peek(self.pos) == 0x5B &&
        !(self.flavor.footnotes && self.peek(self.pos + 1) == 0x5E) {
        self.pos = self.pos + 1
        let node = self.add_text("![")
        self.push_bracket(true, node)
      } else {
        let _ = self.add_text("!")
      }
    }
    0x7E =>
      if self.flavor.strikethrough {
        self.handle_strike()
      } else {
        self.handle_plain()
      }
    0x3A =>
      if self.flavor.autolinks &&
        self.last_bracket < 0 &&
        self.try_url_autolink() {
        ()
      } else {
        self.handle_plain()
      }
    0x77 =>
      if self.flavor.autolinks &&
        self.last_bracket < 0 &&
        self.try_www_autolink() {
        ()
      } else {
        self.handle_plain()
      }
    _ => self.handle_plain()
  }
}

///|
/// A run of text up to the next character that could start something.
fn Subject::handle_plain(self : Subject) -> Unit {
  let start = self.pos
  let mut end = start + 1
  while end < self.input.length() && !self.is_special(self.peek(end)) {
    end = end + 1
  }
  self.pos = end
  let mut text = self.input.clamped_view(start~, end~)
  if is_line_end(self.peek(self.pos)) {
    text = rtrim_view(text)
  }
  let _ = self.add_text(text)
}

///|
/// A soft or hard line break.
fn Subject::handle_newline(self : Subject) -> Unit {
  let nlpos = self.pos
  if self.peek(self.pos) == 0x0D {
    self.pos = self.pos + 1
  }
  if self.peek(self.pos) == 0x0A {
    self.pos = self.pos + 1
  }
  self.skip_spaces()
  if nlpos > 1 && self.peek(nlpos - 1) == 0x20 && self.peek(nlpos - 2) == 0x20 {
    let _ = self.add_break(HardBreakNode)
  } else {
    let _ = self.add_break(SoftBreakNode)
  }
}

///|
/// A code span, or the backticks themselves when no run closes it.
fn Subject::handle_backticks(self : Subject) -> Unit {
  let start = self.pos
  while self.peek(self.pos) == 0x60 {
    self.pos = self.pos + 1
  }
  let run = self.pos - start
  let content_start = self.pos
  let end = self.scan_to_closing_backticks(run)
  if end == 0 {
    self.pos = content_start
    let _ = self.add_text(self.input.clamped_view(start~, end=content_start))
    return
  }
  let raw = self.input.clamped_view(start=content_start, end=end - run)
  let code = normalize_code(raw)
  let node = self.tree.make(CodeNode, code, code.length(), "")
  self.tree.append_child(self.root, node)
  self.pos = end
}

///|
/// The position after a closing backtick run of the same width, or zero.
fn Subject::scan_to_closing_backticks(self : Subject, run : Int32) -> Int32 {
  if run > 80 {
    return 0
  }
  if self.scanned_for_backticks && self.backticks[run] <= self.pos {
    return 0
  }
  while true {
    while !self.eof() && self.peek(self.pos) != 0x60 {
      self.pos = self.pos + 1
    }
    if self.eof() {
      break
    }
    let mut count = 0
    while self.peek(self.pos) == 0x60 {
      self.pos = self.pos + 1
      count = count + 1
    }
    if count <= 80 {
      self.backticks[count] = self.pos - count
    }
    if count == run {
      return self.pos
    }
  }
  self.scanned_for_backticks = true
  0
}

///|
/// A backslash escape, a hard break, or a lone backslash.
fn Subject::handle_backslash(self : Subject) -> Unit {
  self.pos = self.pos + 1
  if is_ascii_punct(self.peek(self.pos)) {
    let start = self.pos - 1
    self.pos = self.pos + 1
    let _ = self.add_text(
      self.input.clamped_view(start=start + 1, end=self.pos),
    )
  } else if !self.eof() && self.skip_line_end() {
    let _ = self.add_break(HardBreakNode)
  } else {
    let _ = self.add_text("\\")
  }
}

///|
/// A character reference, or a lone ampersand.
fn Subject::handle_entity(self : Subject) -> Unit {
  let (value, len) = entity_at(self.input[:], self.pos)
  if len == 0 {
    self.pos = self.pos + 1
    let _ = self.add_text("&")
  } else {
    self.pos = self.pos + len
    let _ = self.add_text(value[:])
  }
}

///|
/// An autolink or a raw HTML tag, or a lone `<`.
fn Subject::handle_pointy_brace(self : Subject) -> Unit {
  let n = self.input.length()
  self.pos = self.pos + 1
  let text = self.input[:]
  let after = self.pos
  let uri = scan_autolink_uri(text, after)
  if uri > 0 {
    let raw = text.clamped_view(start=after, end=after + uri - 1)
    self.pos = self.pos + uri
    self.make_autolink(raw, false)
    return
  }
  let email = scan_autolink_email(text, after)
  if email > 0 {
    let raw = text.clamped_view(start=after, end=after + email - 1)
    self.pos = self.pos + email
    self.make_autolink(raw, true)
    return
  }
  let mut raw_len = 0
  let c = cu(text, after)
  if c == 0x21 {
    if cu(text, after + 1) == 0x2D && cu(text, after + 2) == 0x2D {
      if cu(text, after + 3) == 0x3E {
        raw_len = 5
      } else if cu(text, after + 3) == 0x2D && cu(text, after + 4) == 0x3E {
        raw_len = 6
      } else {
        let len = scan_html_comment(text, after + 1)
        if len > 0 {
          raw_len = len + 2
        }
      }
    } else if cu(text, after + 1) == 0x5B {
      let len = scan_html_cdata(text, after + 2)
      if len > 0 {
        // The construct is ` 0 {
        raw_len = len + 2
      }
    }
  } else if c == 0x3F {
    let len = scan_html_pi(text, after + 1)
    if len > 0 {
      raw_len = len + 2
    }
  } else {
    let len = scan_html_tag(text, after)
    if len > 0 {
      raw_len = len + 1
    }
  }
  if raw_len > 0 && after - 1 + raw_len <= n {
    let raw = text.clamped_view(start=after - 1, end=after - 1 + raw_len)
    let node = self.tree.make(HtmlNode, raw.to_owned(), raw.length(), "")
    self.tree.append_child(self.root, node)
    self.pos = after - 1 + raw_len
    return
  }
  let _ = self.add_text("<")
}

///|
/// A link made from an autolink, whose text is the destination as written.
fn Subject::make_autolink(
  self : Subject,
  raw : StringView,
  is_email : Bool,
) -> Unit {
  let destination = if is_email {
    "mailto:" + html_unescape(trim_view(raw))
  } else {
    html_unescape(trim_view(raw))
  }
  let link = self.tree.make(LinkNode, destination, destination.length(), "")
  self.tree.append_child(self.root, link)
  let text = self.tree.make(TextNode, raw.to_owned(), raw.length(), "")
  self.tree.append_child(link, text)
}

///|
/// A run of `*` or `_`, which may open or close emphasis.
fn Subject::handle_delim(self : Subject, c : Int32) -> Unit {
  let start = self.pos
  let (count, can_open, can_close) = self.scan_delims(c)
  let text = self.input.clamped_view(start~, end=self.pos)
  let node = self.add_text(text)
  if (can_open || can_close) && count > 0 {
    self.push_delimiter(c, can_open, can_close, node)
  }
}

///|
/// A run of tildes, which may open or close a strikethrough. Only runs of one
/// or two count.
fn Subject::handle_strike(self : Subject) -> Unit {
  let before = if self.pos == 0 {
    '\n'
  } else {
    flanking_before(self.input[:], self.pos, false)
  }
  let start = self.pos
  let mut count = 0
  while self.peek(self.pos) == 0x7E && count < 100 {
    count = count + 1
    self.pos = self.pos + 1
  }
  let after = if self.eof() {
    '\n'
  } else {
    flanking_after(self.input[:], self.pos, false)
  }
  let space_before = is_unicode_space(before)
  let space_after = is_unicode_space(after)
  let punct_before = is_unicode_punct(before)
  let punct_after = is_unicode_punct(after)
  let left = count > 0 &&
    !space_after &&
    !(punct_after && !space_before && !punct_before)
  let right = count > 0 &&
    !space_before &&
    !(punct_before && !space_after && !punct_after)
  let text = self.input.clamped_view(start~, end=self.pos)
  let node = self.add_text(text)
  if (left || right) && (count == 1 || count == 2) {
    self.push_delimiter(0x7E, left, right, node)
  }
}

///|
/// A link, an image, or a `]` that closes nothing.
fn Subject::handle_close_bracket(self : Subject) -> Unit {
  self.pos = self.pos + 1
  let initial_pos = self.pos
  let opener = self.last_bracket
  if opener < 0 {
    let _ = self.add_text("]")
    return
  }
  let is_image = self.brackets[opener].image
  if !is_image && self.no_link_openers {
    self.pop_bracket()
    let _ = self.add_text("]")
    return
  }
  let after_link_text = self.pos
  let mut matched = false
  let mut url = ""
  let mut title = ""
  let text = self.input[:]
  if self.peek(self.pos) == 0x28 {
    let spaces = scan_spacechars(text, self.pos + 1)
    let (raw_url, url_len) = scan_link_url(text, self.pos + 1 + spaces)
    if url_len >= 0 {
      let end_url = self.pos + 1 + spaces + url_len
      let start_title = end_url + scan_spacechars(text, end_url)
      let end_title = if start_title == end_url {
        start_title
      } else {
        start_title + scan_link_title(text, start_title)
      }
      let end_all = end_title + scan_spacechars(text, end_title)
      if self.peek(end_all) == 0x29 {
        self.pos = end_all + 1
        url = clean_url(raw_url[:])
        title = clean_title(text.clamped_view(start=start_title, end=end_title))
        matched = true
      } else {
        self.pos = after_link_text
      }
    } else {
      self.pos = after_link_text
    }
  }
  if !matched {
    let mut found_label = false
    let mut raw_label = ""
    let (label, label_len) = scan_link_label(text, self.pos)
    if label_len > 0 {
      raw_label = label
      found_label = true
      self.pos = self.pos + label_len
    }
    if !found_label {
      self.pos = initial_pos
    }
    if (!found_label || raw_label.is_empty()) &&
      !self.brackets[opener].bracket_after {
      raw_label = self.input
        .clamped_view(start=self.brackets[opener].pos, end=initial_pos - 1)
        .to_owned()
      found_label = true
    }
    if found_label {
      match self.refs.get(normalize_label(raw_label[:])) {
        Some(reference) => {
          url = reference.url
          title = reference.title
          matched = true
        }
        None => ()
      }
    }
  }
  if !matched &&
    self.flavor.footnotes &&
    !is_image &&
    self.peek(self.brackets[opener].body_start) == 0x5E {
    // `[^label]` is a footnote reference: it points at a definition by its
    // label, which is resolved to a number when the document is rendered. The
    // label is taken from the source between the caret and the close.
    self.pos = initial_pos
    let body_start = self.brackets[opener].body_start
    let label = self.input
      .clamped_view(start=body_start + 1, end=initial_pos - 1)
      .to_owned()
    if label.length() > 0 {
      let opener_inl = self.brackets[opener].inl
      let fnref = self.tree.make(FootnoteRefNode, label, label.length(), "")
      self.tree.insert_after(opener_inl, fnref)
      let mut node = self.tree.nodes[fnref].next
      while node >= 0 {
        let next = self.tree.nodes[node].next
        self.tree.detach(node)
        self.tree.append_child(fnref, node)
        node = next
      }
      self.tree.detach(opener_inl)
      self.process_emphasis(self.brackets[opener].pos)
      self.pop_bracket()
      return
    }
  }
  if !matched {
    self.pop_bracket()
    self.pos = initial_pos
    let _ = self.add_text("]")
    return
  }
  let opener_inl = self.brackets[opener].inl
  let link = self.tree.make(
    if is_image {
      ImageNode
    } else {
      LinkNode
    },
    url,
    url.length(),
    title,
  )
  self.tree.insert_after(opener_inl, link)
  let mut node = self.tree.nodes[link].next
  while node >= 0 {
    let next = self.tree.nodes[node].next
    self.tree.detach(node)
    self.tree.append_child(link, node)
    node = next
  }
  self.tree.detach(opener_inl)
  self.process_emphasis(self.brackets[opener].pos)
  self.pop_bracket()
  if !is_image {
    self.no_link_openers = true
  }
}