///|
/// Result of advancing a captured bytecode execution trace.
pub(all) enum BytecodeDebugResult {
  DebuggerPaused(BytecodeTraceEvent)
  DebuggerFinished(BytecodeRunResult)
}

///|
/// Deterministic debugger cursor over a bytecode trace. It is intentionally a
/// replay debugger: execution is captured once, then frontends can single-step
/// or continue between source-line breakpoints without changing program state.
pub struct BytecodeTraceDebugger {
  trace : BytecodeTrace
  breakpoints : Array[Int]
  mut cursor : Int
}

///|
pub fn BytecodeTraceDebugger::new(
  trace : BytecodeTrace,
  breakpoints : Array[Int],
) -> BytecodeTraceDebugger {
  { trace, breakpoints, cursor: 0, }
}

///|
pub fn BytecodeTraceDebugger::position(self : BytecodeTraceDebugger) -> Int {
  self.cursor
}

///|
pub fn BytecodeTraceDebugger::event_count(self : BytecodeTraceDebugger) -> Int {
  self.trace.events.length()
}

///|
pub fn BytecodeTraceDebugger::reset(self : BytecodeTraceDebugger) -> Unit {
  self.cursor = 0
}

///|
pub fn BytecodeTraceDebugger::step(
  self : BytecodeTraceDebugger,
) -> BytecodeDebugResult {
  if self.cursor >= self.trace.events.length() {
    return DebuggerFinished(self.trace.result)
  }
  let event = self.trace.events[self.cursor]
  self.cursor = self.cursor + 1
  DebuggerPaused(event)
}

///|
fn BytecodeTraceDebugger::is_breakpoint(
  self : BytecodeTraceDebugger,
  line : Int,
) -> Bool {
  for breakpoint in self.breakpoints {
    if breakpoint == line {
      return true
    }
  }
  false
}

///|
/// Advance until the next event whose source line is a configured breakpoint.
/// If no later breakpoint is hit, return the program's final result.
pub fn BytecodeTraceDebugger::continue_execution(
  self : BytecodeTraceDebugger,
) -> BytecodeDebugResult {
  while self.cursor < self.trace.events.length() {
    let event = self.trace.events[self.cursor]
    self.cursor = self.cursor + 1
    if self.is_breakpoint(event.instruction.span.line()) {
      return DebuggerPaused(event)
    }
  }
  DebuggerFinished(self.trace.result)
}