///| Bullet and ordered list parsing (split from block_parser.mbt).

///|
/// Try to parse list
fn BlockParser::try_parse_list(self : BlockParser, start : Int) -> Block? {
  let leading = self.read_leading_trivia()

  // Check for bullet or ordered list marker
  match self.detect_list_marker() {
    Some((marker_kind, marker_width)) =>
      match marker_kind {
        ListMarkerKind::Bullet(marker) =>
          self.parse_bullet_list(start, leading, marker, marker_width)
        ListMarkerKind::Ordered(start_num, delim) =>
          self.parse_ordered_list(
            start, leading, start_num, delim, marker_width,
          )
      }
    None => None
  }
}

///|
/// List marker kind
priv enum ListMarkerKind {
  Bullet(BulletMarker)
  Ordered(Int, OrderedDelimiter)
}

///|
/// Detect list marker at current position
fn BlockParser::detect_list_marker(
  self : BlockParser,
) -> (ListMarkerKind, Int)? {
  let saved = self.scanner.pos

  // Check for bullet marker
  match self.scanner.peek() {
    Some('-') => {
      self.scanner.advance(1)
      match self.scanner.peek() {
        Some(' ') | Some('\t') => {
          self.scanner.restore(saved)
          return Some((ListMarkerKind::Bullet(BulletMarker::Dash), 2))
        }
        _ => ()
      }
    }
    Some('*') => {
      self.scanner.advance(1)
      match self.scanner.peek() {
        Some(' ') | Some('\t') => {
          self.scanner.restore(saved)
          return Some((ListMarkerKind::Bullet(BulletMarker::Asterisk), 2))
        }
        _ => ()
      }
    }
    Some('+') => {
      self.scanner.advance(1)
      match self.scanner.peek() {
        Some(' ') | Some('\t') => {
          self.scanner.restore(saved)
          return Some((ListMarkerKind::Bullet(BulletMarker::Plus), 2))
        }
        _ => ()
      }
    }
    _ => ()
  }
  self.scanner.restore(saved)

  // Check for ordered list marker (1. or 1))
  if char_is_digit(self.scanner.peek()) {
    let mut num = 0
    let mut digits = 0
    while char_is_digit(self.scanner.peek()) {
      match self.scanner.peek() {
        Some(c) => {
          num = num * 10 + (c.to_int() - '0'.to_int())
          self.scanner.advance(1)
          digits += 1
          if digits > 9 {
            self.scanner.restore(saved)
            return None
          }
        }
        None => break
      }
    }
    match self.scanner.peek() {
      Some('.') => {
        self.scanner.advance(1)
        match self.scanner.peek() {
          Some(' ') | Some('\t') => {
            self.scanner.restore(saved)
            return Some(
              (ListMarkerKind::Ordered(num, OrderedDelimiter::Dot), digits + 2),
            )
          }
          _ => ()
        }
      }
      Some(')') => {
        self.scanner.advance(1)
        match self.scanner.peek() {
          Some(' ') | Some('\t') => {
            self.scanner.restore(saved)
            return Some(
              (
                ListMarkerKind::Ordered(num, OrderedDelimiter::Paren),
                digits + 2,
              ),
            )
          }
          _ => ()
        }
      }
      _ => ()
    }
  }
  self.scanner.restore(saved)
  None
}

///|
fn BlockParser::current_bullet_item_matches(
  self : BlockParser,
  marker : BulletMarker,
) -> Bool {
  match self.detect_list_marker() {
    Some((ListMarkerKind::Bullet(current), _)) => current == marker
    _ => false
  }
}

///|
/// Parse bullet list
fn BlockParser::parse_bullet_list(
  self : BlockParser,
  start : Int,
  leading : Trivia,
  marker : BulletMarker,
  _marker_width : Int,
) -> Block? {
  let items : Array[ListItem] = []
  let mut tight = true
  while !self.scanner.is_eof() {
    let item_start = self.scanner.pos
    let marker_offset = self.scanner.skip_spaces()

    // Check marker matches and is followed by required whitespace.
    if !self.current_bullet_item_matches(marker) {
      self.scanner.restore(item_start)
      break
    }
    self.scanner.advance(1) // Skip marker
    let content_offset = self.scanner.skip_spaces()
    let actual_offset = if content_offset == 0 { 1 } else { content_offset }

    // Parse item content
    let item = self.parse_list_item(item_start, marker_offset, actual_offset)
    items.push(item)

    // Check for blank lines (affects tight/loose)
    if !self.scanner.is_eof() && self.scanner.is_blank_line() {
      let blank_start = self.scanner.pos
      while !self.scanner.is_eof() && self.scanner.is_blank_line() {
        self.scanner.skip_line()
      }
      if self.next_bullet_item_matches(marker) {
        tight = false
      } else {
        self.scanner.restore(blank_start)
        break
      }
    }
  }
  if items.is_empty() {
    return None
  }
  Some(
    Block::BulletList(
      marker~,
      tight~,
      items~,
      span=Span::new(start, self.scanner.pos),
      leading_trivia=leading,
      trailing_trivia=Trivia::empty(),
    ),
  )
}

///|
/// Parse ordered list
fn BlockParser::parse_ordered_list(
  self : BlockParser,
  start : Int,
  leading : Trivia,
  start_num : Int,
  delimiter : OrderedDelimiter,
  _marker_width : Int,
) -> Block? {
  let items : Array[ListItem] = []
  let mut tight = true
  while !self.scanner.is_eof() {
    let item_start = self.scanner.pos
    let marker_offset = self.scanner.skip_spaces()

    // Check for ordered marker with same delimiter
    match self.detect_list_marker() {
      Some((ListMarkerKind::Ordered(_, d), _)) if d == delimiter => ()
      _ => {
        self.scanner.restore(item_start)
        break
      }
    }

    // Skip number and delimiter
    while char_is_digit(self.scanner.peek()) {
      self.scanner.advance(1)
    }
    self.scanner.advance(1) // Skip . or )
    let content_offset = self.scanner.skip_spaces()
    let actual_offset = if content_offset == 0 { 1 } else { content_offset }

    // Parse item content
    let item = self.parse_list_item(item_start, marker_offset, actual_offset)
    items.push(item)

    // Check for blank lines
    if !self.scanner.is_eof() && self.scanner.is_blank_line() {
      let blank_start = self.scanner.pos
      while !self.scanner.is_eof() && self.scanner.is_blank_line() {
        self.scanner.skip_line()
      }
      if self.next_ordered_item_matches(delimiter) {
        tight = false
      } else {
        self.scanner.restore(blank_start)
        break
      }
    }
  }
  if items.is_empty() {
    return None
  }
  Some(
    Block::OrderedList(
      start=start_num,
      delimiter~,
      tight~,
      items~,
      span=Span::new(start, self.scanner.pos),
      leading_trivia=leading,
      trailing_trivia=Trivia::empty(),
    ),
  )
}

///|
fn BlockParser::next_bullet_item_matches(
  self : BlockParser,
  marker : BulletMarker,
) -> Bool {
  let saved = self.scanner.pos
  let _ = self.scanner.skip_spaces()
  let matches = self.current_bullet_item_matches(marker)
  self.scanner.restore(saved)
  matches
}

///|
fn BlockParser::next_ordered_item_matches(
  self : BlockParser,
  delimiter : OrderedDelimiter,
) -> Bool {
  let saved = self.scanner.pos
  let _ = self.scanner.skip_spaces()
  let matches = match self.detect_list_marker() {
    Some((ListMarkerKind::Ordered(_, d), _)) => d == delimiter
    _ => false
  }
  self.scanner.restore(saved)
  matches
}

///|
/// Parse list item content
fn BlockParser::parse_list_item(
  self : BlockParser,
  start : Int,
  marker_offset : Int,
  content_offset : Int,
) -> ListItem {
  // Parse first line content
  let content_buf = StringBuilder::new()
  self.scanner.append_line_to(content_buf)
  if char_is(self.scanner.peek(), '\n') {
    self.scanner.advance(1)
  }

  // Parse content as blocks
  let content = content_buf.to_string()
  let children : Array[Block] = []

  // Check for task list checkbox: [ ], [x], or [X]
  let (checked, actual_content) = parse_task_checkbox(content)
  if !actual_content.is_empty() {
    let inline_content = self.parse_inline_content(actual_content)
    children.push(
      Block::Paragraph(
        children=inline_content,
        span=Span::new(start, self.scanner.pos),
        leading_trivia=Trivia::empty(),
        trailing_trivia=Trivia::empty(),
      ),
    )
  }

  // Check for nested content (indented continuation)
  // Nested list requires at least 2 spaces of indentation
  let min_indent = marker_offset + 1 + content_offset // marker position + marker char + spaces after
  while !self.scanner.is_eof() && !self.scanner.is_blank_line() {
    let saved = self.scanner.pos
    let indent = self.scanner.skip_spaces()

    // Check if indented enough to be continuation
    if indent < min_indent {
      self.scanner.restore(saved)
      break
    }

    // Check if this is a nested list
    match self.detect_list_marker() {
      Some((ListMarkerKind::Bullet(nested_marker), _)) => {
        self.scanner.restore(saved)
        match self.parse_nested_bullet_list(nested_marker, min_indent) {
          Some(nested_list) => children.push(nested_list)
          None => break
        }
      }
      _ => {
        // Not a nested list, restore and break
        self.scanner.restore(saved)
        break
      }
    }
  }
  {
    children,
    checked,
    marker_offset,
    content_offset,
    span: Span::new(start, self.scanner.pos),
  }
}

///|
/// Parse nested bullet list (indented list)
fn BlockParser::parse_nested_bullet_list(
  self : BlockParser,
  marker : BulletMarker,
  min_indent : Int,
) -> Block? {
  let items : Array[ListItem] = []
  let mut tight = true
  while !self.scanner.is_eof() {
    let item_start = self.scanner.pos
    let marker_offset = self.scanner.skip_spaces()

    // Must have at least min_indent spaces
    if marker_offset < min_indent {
      self.scanner.restore(item_start)
      break
    }

    // Check marker matches and is followed by required whitespace.
    if !self.current_bullet_item_matches(marker) {
      self.scanner.restore(item_start)
      break
    }
    self.scanner.advance(1) // Skip marker
    let content_offset = self.scanner.skip_spaces()
    let actual_offset = if content_offset == 0 { 1 } else { content_offset }

    // Parse item content (non-recursive for now to avoid deep nesting issues)
    let item = self.parse_nested_list_item(
      item_start, marker_offset, actual_offset,
    )
    items.push(item)

    // Check for blank lines
    if !self.scanner.is_eof() && self.scanner.is_blank_line() {
      tight = false
      while !self.scanner.is_eof() && self.scanner.is_blank_line() {
        self.scanner.skip_line()
      }
    }
  }
  if items.is_empty() {
    return None
  }
  Some(
    Block::BulletList(
      marker~,
      tight~,
      items~,
      span=Span::new(items[0].span.from, self.scanner.pos),
      leading_trivia=Trivia::empty(),
      trailing_trivia=Trivia::empty(),
    ),
  )
}

///|
/// Parse nested list item (simplified, no recursion)
fn BlockParser::parse_nested_list_item(
  self : BlockParser,
  start : Int,
  marker_offset : Int,
  content_offset : Int,
) -> ListItem {
  let content_buf = StringBuilder::new()
  self.scanner.append_line_to(content_buf)
  if char_is(self.scanner.peek(), '\n') {
    self.scanner.advance(1)
  }
  let content = content_buf.to_string()
  let children : Array[Block] = []
  let (checked, actual_content) = parse_task_checkbox(content)
  if !actual_content.is_empty() {
    let inline_content = self.parse_inline_content(actual_content)
    children.push(
      Block::Paragraph(
        children=inline_content,
        span=Span::new(start, self.scanner.pos),
        leading_trivia=Trivia::empty(),
        trailing_trivia=Trivia::empty(),
      ),
    )
  }
  {
    children,
    checked,
    marker_offset,
    content_offset,
    span: Span::new(start, self.scanner.pos),
  }
}

///| Parse task list checkbox from content

///|
/// Returns (checked status, remaining content)
fn parse_task_checkbox(content : String) -> (Bool?, String) {
  // Check for [ ] (unchecked) or [x]/[X] (checked)
  if content.length() >= 4 {
    if content.has_prefix("[ ] ") {
      return (Some(false), skip_n_chars(content, 4))
    }
    if content.has_prefix("[x] ") || content.has_prefix("[X] ") {
      return (Some(true), skip_n_chars(content, 4))
    }
  }
  // Handle case without trailing space (end of line)
  if content == "[ ]" {
    return (Some(false), "")
  }
  if content == "[x]" || content == "[X]" {
    return (Some(true), "")
  }
  (None, content)
}

///|
/// Skip first n characters of a string
fn skip_n_chars(s : String, n : Int) -> String {
  let buf = StringBuilder::new()
  let mut i = 0
  for c in s {
    if i >= n {
      buf.write_char(c)
    }
    i += 1
  }
  buf.to_string()
}