///|
fn append_debuggee_output(
  events : Array[PresentationEvent],
  session : @debugger.DebugSession,
) -> Unit {
  let output = session.take_output()
  guard output is [_, ..] else { return }
  events.push(DebuggeeOutputPresentation(output))
}

///|
fn append_breakpoint_updates(
  events : Array[PresentationEvent],
  session : @debugger.DebugSession,
) -> Unit {
  let enabled : Map[Int, Bool] = Map([])
  let configuration = session.breakpoint_configuration()
  for breakpoint in configuration.sources() {
    enabled.set(breakpoint.id(), breakpoint.enabled())
  }
  for breakpoint in configuration.functions() {
    enabled.set(breakpoint.id(), breakpoint.enabled())
  }
  for breakpoint in session.source_breakpoints() {
    if enabled.get(breakpoint.id()) is Some(true) {
      events.push(BreakpointUpdatePresentation(breakpoint))
    }
  }
  for breakpoint in session.function_breakpoints() {
    if enabled.get(breakpoint.id()) is Some(true) {
      events.push(FunctionBreakpointUpdatePresentation(breakpoint))
    }
  }
}

///|
fn append_execution_outcome(
  events : Array[PresentationEvent],
  outcome : @debugger.RunOutcome,
) -> Unit {
  match outcome {
    @debugger.Stopped(snapshot) => {
      events.push(StopPresentation(snapshot))
      events.push(
        SourceViewPresentation(
          SourceView::from_stop(snapshot, line_count=DEFAULT_SOURCE_LINE_COUNT),
        ),
      )
    }
    @debugger.Exited(exit_code) =>
      events.push(ProcessExitPresentation(exit_code))
  }
}

///|
/// Runs one DebugSession action and converts its domain result to ordered
/// frontend events. The helper knows no concrete command type and contains no
/// adapter or protocol concepts.
async fn execute_session_action(
  context : ReplContext,
  operation : async (@debugger.DebugSession) -> @debugger.RunOutcome,
  restarting? : Bool = false,
  report_breakpoints? : Bool = false,
) -> CommandResult {
  let events : Array[PresentationEvent] = []
  if restarting {
    events.push(ProgramRestartingPresentation)
  }
  let outcome = operation(context.session) catch {
    @debugger.OperationNotAllowed(state~, action~) => {
      events.push(OperationNotAllowedPresentation(state, action))
      return CommandResult::continuing(events~)
    }
    @debugger.AdapterFailure(message) => {
      append_debuggee_output(events, context.session)
      events.push(AdapterFailurePresentation(message))
      return CommandResult::continuing(events~)
    }
    error => raise error
  }
  append_debuggee_output(events, context.session)
  if report_breakpoints {
    append_breakpoint_updates(events, context.session)
  }
  append_execution_outcome(events, outcome)
  CommandResult::continuing(events~)
}