///|
/// Structured rejection diagnostics; token indices and character offsets are distinct.
pub(all) struct ParseDiagnostic {
  token_index : Int32
  char_start : Int32
  char_end : Int32
  actual_kind : String?
  expected_kinds : Array[String]
  at_eof : Bool
} derive(Eq, Debug)

///|
pub extend ParseDiagnostic with Eq::{equal, not_equal}

///|
pub extend ParseDiagnostic with @moonbitlang/core/debug.Debug::{to_repr}

///|
/// Accepted prefixes have no failure, even when further tokens could be accepted.
pub fn Report::diagnostic(self : Report) -> ParseDiagnostic? {
  if self.accepted() {
    return None
  }
  let tokens = self.forest.tokens
  let index = self.furthest
  if index < tokens.length() {
    let t = tokens[index]
    Some({
      token_index: index,
      char_start: t.start,
      char_end: t.end,
      actual_kind: Some(t.kind),
      expected_kinds: self.expected.copy(),
      at_eof: false,
    })
  } else {
    let end = if tokens.is_empty() {
      0
    } else {
      tokens[tokens.length() - 1].end
    }
    Some({
      token_index: index,
      char_start: end,
      char_end: end,
      actual_kind: None,
      expected_kinds: self.expected.copy(),
      at_eof: true,
    })
  }
}

///|
/// A bounded token window around the failure, or the end for an accepted report.
pub fn Report::context(
  self : Report,
  radius : Int32,
) -> Result[Array[Token], Problem] {
  if radius < 0 || radius > 4096 {
    return Err(InvalidLimit("context radius"))
  }
  let tokens = self.forest.tokens
  let center = if self.accepted() { tokens.length() } else { self.furthest }
  let lo = if center > radius { center - radius } else { 0 }
  let hi = if center + radius + 1 < tokens.length() {
    center + radius + 1
  } else {
    tokens.length()
  }
  let result : Array[Token] = []
  for i = lo; i < hi; i = i + 1 {
    result.push(tokens[i])
  }
  Ok(result)
}

///|
/// Total tokens includes unmatched input; forest leaves contain only scanned tokens.
pub fn Forest::token_count(self : Forest) -> Int32 {
  self.tokens.length()
}

///|
pub fn Forest::rule_count(self : Forest) -> Int32 {
  self.rules.length()
}