///| Delimiter Stack Algorithm for strict CommonMark emphasis handling

///| (split from inline_parser.mbt).

///|
/// Delimiter entry for the stack algorithm
priv struct Delimiter {
  pos : Int // Position in text
  marker : Char // '*' or '_'
  length : Int // Number of consecutive markers
  mut remaining : Int // Remaining markers not yet matched
  can_open : Bool // Can this be an opener?
  can_close : Bool // Can this be a closer?
  mut active : Bool // Is this delimiter still active?
}

///|
fn build_utf16_offsets(chars : Array[Char]) -> Array[Int] {
  let offsets : Array[Int] = Array::make(chars.length() + 1, 0)
  let mut utf16_pos = 0
  for i = 0; i < chars.length(); i = i + 1 {
    offsets[i] = utf16_pos
    if chars[i].to_int() > 0xFFFF {
      utf16_pos += 2
    } else {
      utf16_pos += 1
    }
  }
  offsets[chars.length()] = utf16_pos
  offsets
}

///|
fn substring_by_codepoint(
  text : String,
  utf16_offsets : Array[Int],
  start : Int,
  end : Int,
) -> String {
  if utf16_offsets.is_empty() {
    return text.unsafe_substring(start~, end~)
  }
  text.unsafe_substring(start=utf16_offsets[start], end=utf16_offsets[end])
}

///|
fn collect_delimiters(len : Int, char_at : (Int) -> Char) -> Array[Delimiter] {
  let delimiters : Array[Delimiter] = []
  let mut i = 0
  while i < len {
    let c = char_at(i)
    if c == '*' || c == '_' {
      // Count consecutive markers
      let start = i
      let marker = c
      let mut count = 0
      while i < len && char_at(i) == marker {
        count += 1
        i += 1
      }

      // Determine flanking status (CommonMark rules)
      let before = if start > 0 { Some(char_at(start - 1)) } else { None }
      let after = if i < len { Some(char_at(i)) } else { None }
      let before_is_whitespace = match before {
        None => true
        Some(ch) => is_unicode_whitespace(ch)
      }
      let before_is_punctuation = match before {
        None => false
        Some(ch) => is_unicode_punctuation(ch)
      }
      let after_is_whitespace = match after {
        None => true
        Some(ch) => is_unicode_whitespace(ch)
      }
      let after_is_punctuation = match after {
        None => false
        Some(ch) => is_unicode_punctuation(ch)
      }

      // Left-flanking: not followed by whitespace, and either
      // (a) not followed by punctuation, or (b) preceded by whitespace or punctuation
      let left_flanking = !after_is_whitespace &&
        (!after_is_punctuation || before_is_whitespace || before_is_punctuation)

      // Right-flanking: not preceded by whitespace, and either
      // (a) not preceded by punctuation, or (b) followed by whitespace or punctuation
      let right_flanking = !before_is_whitespace &&
        (!before_is_punctuation || after_is_whitespace || after_is_punctuation)

      // For underscore, additional rules apply
      let (can_open, can_close) = if marker == '_' {
        // _ can open if left-flanking and (not right-flanking or preceded by punctuation)
        // _ can close if right-flanking and (not left-flanking or followed by punctuation)
        (
          left_flanking && (!right_flanking || before_is_punctuation),
          right_flanking && (!left_flanking || after_is_punctuation),
        )
      } else {
        // * can open if left-flanking
        // * can close if right-flanking
        (left_flanking, right_flanking)
      }
      if can_open || can_close {
        delimiters.push(Delimiter::{
          pos: start,
          marker,
          length: count,
          remaining: count,
          can_open,
          can_close,
          active: true,
        })
      }
    } else {
      i += 1
    }
  }
  delimiters
}

///|
/// Parse inlines using delimiter stack algorithm (CommonMark spec)
fn parse_inlines_with_delimiter_stack(
  text : String,
  wikilinks? : Bool = false,
  known_bmp? : Bool = false,
) -> Array[Inline] {
  let (len, utf16_offsets, delimiters) = if known_bmp {
    (
      text.length(),
      ([] : Array[Int]),
      collect_delimiters(text.length(), fn(i) {
        text.unsafe_get(i).unsafe_to_char()
      }),
    )
  } else {
    let chars : Array[Char] = text.to_array()
    (
      chars.length(),
      build_utf16_offsets(chars),
      collect_delimiters(chars.length(), fn(i) { chars[i] }),
    )
  }

  // Phase 2: Process delimiters to find matching pairs
  // Result: list of (opener_pos, closer_pos, marker, is_strong)
  let matches : Array[(Int, Int, Char, Bool)] = []

  // Process closers from left to right
  for closer_idx = 0
      closer_idx < delimiters.length()
      closer_idx = closer_idx + 1 {
    let closer = delimiters[closer_idx]
    if !closer.can_close || closer.remaining == 0 {
      continue
    }

    // Look back for a matching opener
    for opener_idx = closer_idx - 1
        opener_idx >= 0
        opener_idx = opener_idx - 1 {
      let opener = delimiters[opener_idx]
      if !opener.can_open || opener.remaining == 0 || !opener.active {
        continue
      }
      if opener.marker != closer.marker {
        continue
      }

      // Rule: if sum of lengths is multiple of 3 and both can_open and can_close,
      // then opener length and closer length must both not be multiples of 3
      // (This prevents *foo**bar* from matching incorrectly)
      if (opener.can_open && opener.can_close) ||
        (closer.can_open && closer.can_close) {
        if (opener.length + closer.length) % 3 == 0 {
          if opener.length % 3 != 0 || closer.length % 3 != 0 {
            continue
          }
        }
      }

      // Found a match! Determine if strong or emphasis
      let use_count = if opener.remaining >= 2 && closer.remaining >= 2 {
        2
      } else {
        1
      }
      let is_strong = use_count == 2

      // Calculate positions
      let opener_end = opener.pos + opener.length - opener.remaining + use_count
      let closer_start = closer.pos + (closer.length - closer.remaining)
      matches.push(
        (opener_end - use_count, closer_start, opener.marker, is_strong),
      )

      // Update remaining counts
      delimiters[opener_idx].remaining = opener.remaining - use_count
      delimiters[closer_idx].remaining = closer.remaining - use_count

      // Deactivate delimiters between opener and closer
      for j = opener_idx + 1; j < closer_idx; j = j + 1 {
        delimiters[j].active = false
      }

      // If more remaining in closer, continue matching
      if closer.remaining > 0 {
        // Reset closer_idx to re-process this closer
        // (Actually we need to decrement to counteract the loop increment)
        // But since we modified remaining, the loop will handle it
      }
      break
    }
  }

  // Phase 3: Build inline elements from matches
  // Sort matches by opener position (they should already be mostly sorted)

  // Build result by processing text segments (even if no emphasis matches)
  let result : Array[Inline] = []
  if matches.is_empty() {
    // No emphasis matches, but still need to process other inlines correctly
    let segment_inlines = parse_segment_simple(text, 0, wikilinks~, known_bmp~)
    for inline in segment_inlines {
      result.push(inline)
    }
  } else {
    build_inlines_from_matches(
      text,
      utf16_offsets,
      len,
      known_bmp,
      matches,
      result,
      wikilinks~,
    )
  }
  result
}

///|
/// Build inline elements from delimiter matches
fn build_inlines_from_matches(
  text : String,
  utf16_offsets : Array[Int],
  len : Int,
  known_bmp : Bool,
  matches : Array[(Int, Int, Char, Bool)],
  result : Array[Inline],
  wikilinks? : Bool = false,
) -> Unit {
  // Sort matches by opener position
  let sorted = matches.copy()
  sorted.sort_by(fn(a, b) { a.0.compare(b.0) })

  // Build nested structure
  build_inlines_recursive(
    text,
    utf16_offsets,
    known_bmp,
    0,
    len,
    sorted,
    0,
    sorted.length(),
    result,
    wikilinks~,
  )
}

///|
/// Recursively build inline elements
fn build_inlines_recursive(
  text : String,
  utf16_offsets : Array[Int],
  known_bmp : Bool,
  start : Int,
  end : Int,
  matches : Array[(Int, Int, Char, Bool)],
  match_start : Int,
  match_end : Int,
  result : Array[Inline],
  wikilinks? : Bool = false,
) -> Unit {
  let mut pos = start
  for i = match_start; i < match_end; i = i + 1 {
    let (opener_pos, closer_pos, marker, is_strong) = matches[i]

    // Skip if outside our range or already processed
    if opener_pos < start || closer_pos > end || opener_pos < pos {
      continue
    }
    let marker_len = if is_strong { 2 } else { 1 }

    // Add text before this emphasis
    if opener_pos > pos {
      let segment = substring_by_codepoint(text, utf16_offsets, pos, opener_pos)
      // Parse segment for other inline elements (code, links, etc.)
      let segment_inlines = parse_segment_simple(
        segment,
        pos,
        wikilinks~,
        known_bmp~,
      )
      for inline in segment_inlines {
        result.push(inline)
      }
    }

    // Build children for this emphasis
    let children : Array[Inline] = []
    let content_start = opener_pos + marker_len
    let content_end = closer_pos

    // Find nested matches within this emphasis
    let nested_start = i + 1
    let mut nested_end = i + 1
    while nested_end < match_end {
      let (np, nc, _, _) = matches[nested_end]
      if np >= content_start && nc <= content_end {
        nested_end += 1
      } else {
        break
      }
    }
    if nested_start < nested_end {
      // Has nested emphasis
      build_inlines_recursive(
        text,
        utf16_offsets,
        known_bmp,
        content_start,
        content_end,
        matches,
        nested_start,
        nested_end,
        children,
        wikilinks~,
      )
      // Note: nested matches will be skipped naturally by position check
    } else {
      // No nested emphasis, parse content simply
      let content = substring_by_codepoint(
        text, utf16_offsets, content_start, content_end,
      )
      let content_inlines = parse_segment_simple(
        content,
        content_start,
        wikilinks~,
        known_bmp~,
      )
      for inline in content_inlines {
        children.push(inline)
      }
    }

    // Create emphasis or strong node
    let em_marker = if marker == '*' {
      EmphasisMarker::Asterisk
    } else {
      EmphasisMarker::Underscore
    }
    let span = Span::new(opener_pos, closer_pos + marker_len)
    if is_strong {
      result.push(Inline::Strong(marker=em_marker, children~, span~))
    } else {
      result.push(Inline::Emphasis(marker=em_marker, children~, span~))
    }
    pos = closer_pos + marker_len
  }

  // Add remaining text
  if pos < end {
    let segment = substring_by_codepoint(text, utf16_offsets, pos, end)
    let segment_inlines = parse_segment_simple(
      segment,
      pos,
      wikilinks~,
      known_bmp~,
    )
    for inline in segment_inlines {
      result.push(inline)
    }
  }
}

///|
/// Parse a text segment for non-emphasis inlines (code spans, links, etc.)
fn parse_segment_simple(
  text : String,
  offset : Int,
  wikilinks? : Bool = false,
  known_bmp? : Bool = false,
) -> Array[Inline] {
  // For now, just return as text. Full implementation would parse
  // code spans, links, etc. here.
  let scanner = if known_bmp {
    Scanner::new_bmp(text)
  } else {
    Scanner::new(text)
  }
  let result : Array[Inline] = []
  let text_buf = StringBuilder::new()
  let mut text_start = 0
  while !scanner.is_eof() {
    let pos = scanner.pos
    match scanner.peek() {
      Some('`') => {
        // Try to parse code span
        let backtick_count = scanner.count_char('`')
        scanner.advance(backtick_count)

        // Find closing backticks
        let content_buf = StringBuilder::new()
        let mut found_closing = false
        while !scanner.is_eof() {
          let closing_count = scanner.count_char('`')
          if closing_count == backtick_count {
            found_closing = true
            let content = content_buf.to_string()
            scanner.advance(closing_count)

            // Flush text buffer
            if !text_buf.is_empty() {
              result.push(
                Inline::Text(
                  content=text_buf.to_string(),
                  span=Span::new(offset + text_start, offset + pos),
                ),
              )
              text_buf.reset()
            }

            // Trim single leading/trailing space if present
            let trimmed = if content.length() >= 2 {
              let chars = content.to_array()
              if chars[0] == ' ' &&
                chars[chars.length() - 1] == ' ' &&
                !is_all_spaces(content) {
                content.unsafe_substring(start=1, end=content.length() - 1)
              } else {
                content
              }
            } else {
              content
            }
            result.push(
              Inline::Code(
                content=trimmed,
                backtick_count~,
                span=Span::new(offset + pos, offset + scanner.pos),
              ),
            )
            text_start = scanner.pos
            break
          } else if closing_count > 0 {
            for j = 0; j < closing_count; j = j + 1 {
              content_buf.write_char('`')
            }
            scanner.advance(closing_count)
          } else {
            match scanner.consume() {
              Some(c) => content_buf.write_char(c)
              None => break
            }
          }
        }
        if !found_closing {
          // No closing backticks, include opening backticks in text
          for j = 0; j < backtick_count; j = j + 1 {
            text_buf.write_char('`')
          }
          // Content was consumed, add it to text
          text_buf.write_string(content_buf.to_string())
        }
      }
      Some('[') => {
        // Try to parse wikilink before regular markdown links.
        let inline_parser = InlineParser::new(scanner, wikilinks~)
        let inline = if wikilinks && char_is(scanner.peek_at(1), '[') {
          match inline_parser.try_parse_wikilink(pos) {
            Some(wikilink) => Some(wikilink)
            None => inline_parser.try_parse_link(pos)
          }
        } else {
          inline_parser.try_parse_link(pos)
        }
        match inline {
          Some(parsed) => {
            // Flush text buffer
            if !text_buf.is_empty() {
              result.push(
                Inline::Text(
                  content=text_buf.to_string(),
                  span=Span::new(offset + text_start, offset + pos),
                ),
              )
              text_buf.reset()
            }
            result.push(parsed)
            text_start = scanner.pos
          }
          None => {
            text_buf.write_char('[')
            scanner.advance(1)
          }
        }
      }
      Some('!') =>
        // Try to parse image
        if char_is(scanner.peek_at(1), '[') {
          let img_parser = InlineParser::new(scanner, wikilinks~)
          match img_parser.try_parse_image(pos) {
            Some(img) => {
              // Flush text buffer
              if !text_buf.is_empty() {
                result.push(
                  Inline::Text(
                    content=text_buf.to_string(),
                    span=Span::new(offset + text_start, offset + pos),
                  ),
                )
                text_buf.reset()
              }
              result.push(img)
              text_start = scanner.pos
            }
            None => {
              text_buf.write_char('!')
              scanner.advance(1)
            }
          }
        } else {
          text_buf.write_char('!')
          scanner.advance(1)
        }
      Some('<') => {
        // Try to parse raw inline HTML before autolink.
        let html_parser = InlineParser::new(scanner, wikilinks~)
        let inline = match html_parser.try_parse_html_comment(pos) {
          Some(comment) => Some(comment)
          None => {
            let auto_parser = InlineParser::new(scanner, wikilinks~)
            auto_parser.try_parse_autolink(pos)
          }
        }
        match inline {
          Some(parsed) => {
            // Flush text buffer
            if !text_buf.is_empty() {
              result.push(
                Inline::Text(
                  content=text_buf.to_string(),
                  span=Span::new(offset + text_start, offset + pos),
                ),
              )
              text_buf.reset()
            }
            result.push(parsed)
            text_start = scanner.pos
          }
          None => {
            text_buf.write_char('<')
            scanner.advance(1)
          }
        }
      }
      Some('\n') => {
        // Check for hard break (2+ trailing spaces before newline)
        let content = text_buf.to_string()
        let trimmed = content.trim_end(chars=" ").to_owned()
        let trailing_spaces = content.length() - trimmed.length()
        let is_hard_break = trailing_spaces >= 2
        if !trimmed.is_empty() {
          result.push(
            Inline::Text(
              content=trimmed,
              span=Span::new(
                offset + text_start,
                offset + pos - trailing_spaces,
              ),
            ),
          )
        }
        text_buf.reset()
        scanner.advance(1)
        // Skip leading spaces after newline
        while char_is(scanner.peek(), ' ') {
          scanner.advance(1)
        }
        if is_hard_break {
          result.push(
            Inline::HardBreak(
              style=HardBreakStyle::TwoSpaces,
              span=Span::new(
                offset + pos - trailing_spaces,
                offset + scanner.pos,
              ),
            ),
          )
        } else {
          result.push(
            Inline::SoftBreak(
              span=Span::new(offset + pos, offset + scanner.pos),
            ),
          )
        }
        text_start = scanner.pos
      }
      Some(c) => {
        text_buf.write_char(c)
        scanner.advance(1)
      }
      None => break
    }
  }

  // Flush remaining text
  if !text_buf.is_empty() {
    result.push(
      Inline::Text(
        content=text_buf.to_string(),
        span=Span::new(offset + text_start, offset + scanner.pos),
      ),
    )
  }
  result
}