//! Second pass part 6: emphasis matching, hard breaks, inline stack, code/math delims.

///|
priv struct InlineEl {
  /// offset of tree node
  start : Int
  /// number of delimiters available for matching
  count : Int
  /// length of the run that these delimiters came from
  run_length : Int
  /// b'*', b'_', or b'~'
  c : Byte
  /// can both open and close
  both : Bool
}

///|
struct InlineStack {
  stack : Array[InlineEl]
  // Lower bounds for matching indices in the stack.
  lower_bounds : Array[Int]
}

// indices into the lower bounds array

///|
const UNDERSCORE_NOT_BOTH : Int = 0

///|
const ASTERISK_NOT_BOTH : Int = 1

///|
const ASTERISK_BASE : Int = 2

///|
const TILDES : Int = 5

///|
const UNDERSCORE_BASE : Int = 6

///|
const CIRCUMFLEXES : Int = 9

///|
const EQUALS : Int = 10

///|
fn has_valid_delim_rule(
  run_length : Int,
  el_run_length : Int,
  both : Bool,
  el_both : Bool,
) -> Bool {
  (!both && !el_both) ||
  (run_length + el_run_length) % 3 != 0 ||
  run_length % 3 == 0
}

///|
fn inline_stack_new() -> InlineStack {
  { stack: [], lower_bounds: Array::make(11, 0) }
}

///|
fn InlineStack::pop_all(self : InlineStack, tree : Tree[Item]) -> Unit {
  for el in self.stack {
    for i in 0.. Int {
  if c == b'_' {
    let mod3_lower = self.lower_bounds[UNDERSCORE_BASE + count % 3]
    if both {
      mod3_lower
    } else {
      mod3_lower.min(self.lower_bounds[UNDERSCORE_NOT_BOTH])
    }
  } else if c == b'*' {
    let mod3_lower = self.lower_bounds[ASTERISK_BASE + count % 3]
    if both {
      mod3_lower
    } else {
      mod3_lower.min(self.lower_bounds[ASTERISK_NOT_BOTH])
    }
  } else if c == b'^' {
    self.lower_bounds[CIRCUMFLEXES]
  } else if c == b'=' {
    self.lower_bounds[EQUALS]
  } else {
    self.lower_bounds[TILDES]
  }
}

///|
fn InlineStack::set_lowerbound(
  self : InlineStack,
  c : Byte,
  count : Int,
  both : Bool,
  new_bound : Int,
) -> Unit {
  if c == b'_' {
    if both {
      self.lower_bounds[UNDERSCORE_BASE + count % 3] = new_bound
    } else {
      self.lower_bounds[UNDERSCORE_NOT_BOTH] = new_bound
    }
  } else if c == b'*' {
    self.lower_bounds[ASTERISK_BASE + count % 3] = new_bound
    if !both {
      self.lower_bounds[ASTERISK_NOT_BOTH] = new_bound
    }
  } else if c == b'^' {
    self.lower_bounds[CIRCUMFLEXES] = new_bound
  } else if c == b'=' {
    self.lower_bounds[EQUALS] = new_bound
  } else {
    self.lower_bounds[TILDES] = new_bound
  }
}

///|
fn InlineStack::truncate(self : InlineStack, new_bound : Int) -> Unit {
  self.stack.truncate(new_bound)
  for i in 0..<11 {
    if self.lower_bounds[i] > new_bound {
      self.lower_bounds[i] = new_bound
    }
  }
}

///|
fn InlineStack::find_match(
  self : InlineStack,
  tree : Tree[Item],
  c : Byte,
  run_length : Int,
  both : Bool,
) -> InlineEl? {
  let lowerbound = self.stack
    .length()
    .min(self.get_lowerbound(c, run_length, both))
  // search from the end
  let mut i = self.stack.length() - 1
  while i >= lowerbound {
    let el = self.stack[i]
    let run_matches = if c == b'~' || c == b'^' || c == b'=' {
      run_length == el.run_length
    } else {
      true
    }
    let type_matches = el.c == c &&
      has_valid_delim_rule(run_length, el.run_length, both, el.both)
    if run_matches && type_matches {
      let matching_el = self.stack[i]
      // truncate the stack after the match, converting popped delims to text
      for j in (i + 1).. Unit {
  self.lower_bounds[ix] = self.lower_bounds[ix].min(self.stack.length())
}

///|
fn InlineStack::push(self : InlineStack, el : InlineEl) -> Unit {
  if el.c == b'~' {
    self.trim_lower_bound(TILDES)
  } else if el.c == b'^' {
    self.trim_lower_bound(CIRCUMFLEXES)
  } else if el.c == b'=' {
    self.trim_lower_bound(EQUALS)
  }
  self.stack.push(el)
}

///|
/// Tracks tree indices of code span delimiters of each length.
struct CodeDelims {
  inner : @hashmap.HashMap[Int, @deque.Deque[Int]]
  mut seen_first : Bool
}

///|
fn code_delims_new() -> CodeDelims {
  { inner: @hashmap.HashMap::HashMap([]), seen_first: false }
}

///|
fn CodeDelims::insert(self : CodeDelims, count : Int, ix : Int) -> Unit {
  if self.seen_first {
    if self.inner.get(count) is Some(q) {
      q.push_back(ix)
    } else {
      let q : @deque.Deque[Int] = @deque.Deque([], capacity=0)
      q.push_back(ix)
      self.inner.set(count, q)
    }
  } else {
    // Skip the first insert, since that delimiter will always
    // be an opener and not a closer.
    self.seen_first = true
  }
}

///|
fn CodeDelims::is_populated(self : CodeDelims) -> Bool {
  !self.inner.is_empty()
}

///|
fn CodeDelims::find(self : CodeDelims, open_ix : Int, count : Int) -> Int? {
  while true {
    guard self.inner.get(count) is Some(q) else { return None }
    match q.pop_front() {
      Some(ix) => if ix > open_ix { return Some(ix) }
      None => return None
    }
  }
  None
}

///|
fn CodeDelims::clear(self : CodeDelims) -> Unit {
  self.inner.clear()
  self.seen_first = false
}

///|
/// Tracks brace contexts and delimiter length for math delimiters.
struct MathDelims {
  inner : @hashmap.HashMap[Int, @deque.Deque[(Int, Bool, Bool)]]
}

///|
fn math_delims_new() -> MathDelims {
  { inner: @hashmap.HashMap::HashMap([]) }
}

///|
fn MathDelims::insert(
  self : MathDelims,
  delim_is_display : Bool,
  brace_context : Int,
  ix : Int,
  can_close : Bool,
) -> Unit {
  if self.inner.get(brace_context) is Some(q) {
    q.push_back((ix, can_close, delim_is_display))
  } else {
    let q : @deque.Deque[(Int, Bool, Bool)] = @deque.Deque([], capacity=0)
    q.push_back((ix, can_close, delim_is_display))
    self.inner.set(brace_context, q)
  }
}

///|
fn MathDelims::is_populated(self : MathDelims) -> Bool {
  !self.inner.is_empty()
}

///|
fn MathDelims::find(
  self : MathDelims,
  tree : Tree[Item],
  open_ix : Int,
  is_display : Bool,
  brace_context : Int,
) -> Int? {
  while true {
    guard self.inner.get(brace_context) is Some(q) else { return None }
    match q.pop_front() {
      Some((ix, can_close, delim_is_display)) => {
        if ix <= open_ix || (is_display && tree.nodes[open_ix].next == Some(ix)) {
          continue
        }
        let can_close = can_close &&
          tree.nodes[open_ix].item.end != tree.nodes[ix].item.start
        guard !((!is_display && can_close) || (is_display && delim_is_display)) else {
          return Some(ix)
        }
        // if we can't use it, leave it in the queue as a tombstone
        q.push_front((ix, can_close, delim_is_display))
        break
      }
      None => return None
    }
  }
  None
}

///|
fn MathDelims::clear(self : MathDelims) -> Unit {
  self.inner.clear()
}

///|
/// Applies the inner "work from the inside out" loop of emphasis matching.
/// Returns `true` if the opener matched successfully (caller finishes the
/// outer iteration), or `false` if the opener should be turned into text and
/// the outer search retried.
fn Parser::apply_emphasis_inner(
  self : Parser,
  el : InlineEl,
  start : Int,
  end : Int,
  match_count : Int,
  prev : Int?,
) -> Bool {
  let mut start = start
  let mut end = end
  let c = el.c
  while start > el.start + el.count - match_count {
    let inc = if start > el.start + el.count - match_count + 1 { 2 } else { 1 }
    let ty = if c == b'~' {
      if inc == 2 {
        if self.options.contains(enable_strikethrough()) {
          Some(ItemBody::Strikethrough)
        } else {
          None
        }
      } else if self.options.contains(enable_subscript()) {
        Some(ItemBody::Subscript)
      } else if self.options.contains(enable_strikethrough()) {
        Some(ItemBody::Strikethrough)
      } else {
        None
      }
    } else if c == b'^' {
      if self.options.contains(enable_superscript()) {
        Some(ItemBody::Superscript)
      } else {
        None
      }
    } else if c == b'=' {
      if inc == 2 && self.options.contains(enable_highlight()) {
        Some(ItemBody::Highlight)
      } else {
        None
      }
    } else if inc == 2 {
      Some(ItemBody::Strong)
    } else {
      Some(ItemBody::Emphasis)
    }

    match ty {
      Some(ty) => {
        // have a match!
        let root = start - inc
        end = end + inc
        self.tree.nodes[root].item.body = ty
        self.tree.nodes[root].item.end = self.tree.nodes[end].item.end
        self.tree.nodes[root].child = Some(start)
        self.tree.nodes[root].next = None
        match prev {
          Some(prev_ix) => self.tree.nodes[prev_ix].next = None
          None => ()
        }
        start = root
      }
      None => {
        self.tree.nodes[el.start].item.body = Text(false)
        return false
      }
    }
  }
  true
}

///|
fn Parser::handle_emphasis_and_hard_break(self : Parser) -> Unit {
  let mut prev : Int? = None
  let mut cur = self.tree.cur()

  let mut single_quote_open : Int? = None
  let mut double_quote_open = false

  while true {
    match cur {
      Some(cur_ix) => {
        let mut cur_ix = cur_ix
        match self.tree.nodes[cur_ix].item.body {
          MaybeEmphasis(count, can_open, can_close) => {
            let mut count = count
            let run_length = count
            let c = self.text.unsafe_get(self.tree.nodes[cur_ix].item.start)
            let both = can_open && can_close
            if can_close {
              // try to find a matching opener; `continue 'outer` retries
              let mut searching = true
              while searching {
                match
                  self.inline_stack.find_match(self.tree, c, run_length, both) {
                  Some(el) => {
                    let match_count = count.min(el.count)
                    // start, end are tree node indices
                    let end = cur_ix - 1
                    let start = el.start + el.count

                    let ok = self.apply_emphasis_inner(
                      el, start, end, match_count, prev,
                    )
                    guard ok else {
                      // `continue 'outer`
                      continue
                    }

                    // set next for top most emph level
                    let prev_ix = el.start + el.count - match_count
                    prev = Some(prev_ix)
                    cur = self.tree.nodes[cur_ix + match_count - 1].next
                    self.tree.nodes[prev_ix].next = cur

                    if el.count > match_count {
                      self.inline_stack.push(InlineEl::{
                        start: el.start,
                        count: el.count - match_count,
                        run_length: el.run_length,
                        c: el.c,
                        both: el.both,
                      })
                    }
                    count -= match_count
                    if count > 0 {
                      cur_ix = cur.unwrap()
                      // retry to find another match
                      continue
                    } else {
                      searching = false
                    }
                  }
                  None => searching = false
                }
              }
            }
            if count > 0 {
              if can_open {
                self.inline_stack.push(InlineEl::{
                  start: cur_ix,
                  run_length,
                  count,
                  c,
                  both,
                })
              } else {
                for i in 0.. {
            let body = if c == b'\'' {
              if single_quote_open is Some(_) && can_close {
                let open_ix = single_quote_open.unwrap()
                self.tree.nodes[open_ix].item.body = SynthesizeChar('‘')
                single_quote_open = None
                SynthesizeChar('’')
              } else {
                if can_open {
                  single_quote_open = Some(cur_ix)
                }
                SynthesizeChar('’')
              }
              // double quote
            } else if can_close && double_quote_open {
              double_quote_open = false
              SynthesizeChar('”')
            } else {
              if can_open && !double_quote_open {
                double_quote_open = true
              }
              SynthesizeChar('“')
            }
            self.tree.nodes[cur_ix].item.body = body
            prev = cur
            cur = self.tree.nodes[cur_ix].next
          }
          HardBreak(true) => {
            if self.tree.nodes[cur_ix].next is None {
              self.tree.nodes[cur_ix].item.body = SynthesizeChar('\\')
            }
            prev = cur
            cur = self.tree.nodes[cur_ix].next
          }
          _ => {
            prev = cur
            cur = self.tree.nodes[cur_ix].next
          }
        }
      }
      None => break
    }
  }
  self.inline_stack.pop_all(self.tree)
}