///|
/// Creates a checkpoint from the final verified boundary of a replay report.
pub fn[S] checkpoint_from_report(report : ReplayReport[S]) -> Checkpoint[S]? {
  match report.status {
    Completed =>
      Some({
        sequence: report.final_sequence,
        state: report.final_state,
        state_hash: report.final_state_hash,
        journal_hash: report.journal_tail_hash,
      })
    _ => None
  }
}

///|
/// Continues a validated journal from an anchored checkpoint.
pub fn[P, S] replay_from_checkpoint(
  journal : Journal[P],
  checkpoint : Checkpoint[S],
  payload_fingerprint : (P) -> String,
  state_fingerprint : (S) -> String,
  reducer : (S, JournalEvent[P]) -> Transition[S],
) -> ReplayReport[S] {
  let actual_state_hash = fingerprint_text(state_fingerprint(checkpoint.state))
  let invalid_report = fn(reason : String) -> ReplayReport[S] {
    {
      status: InvalidCheckpoint(reason),
      initial_sequence: checkpoint.sequence,
      applied_events: 0,
      final_sequence: checkpoint.sequence,
      final_state: checkpoint.state,
      final_state_hash: actual_state_hash,
      journal_tail_hash: journal.tail_hash(),
      state_hashes: [actual_state_hash],
    }
  }
  if actual_state_hash != checkpoint.state_hash {
    return invalid_report("checkpoint state fingerprint does not match")
  }
  match journal.validate(payload_fingerprint) {
    Invalid(issue) =>
      return {
        status: InvalidJournal(issue),
        initial_sequence: checkpoint.sequence,
        applied_events: 0,
        final_sequence: checkpoint.sequence,
        final_state: checkpoint.state,
        final_state_hash: actual_state_hash,
        journal_tail_hash: journal.tail_hash(),
        state_hashes: [actual_state_hash],
      }
    Valid(_, _) => ()
  }
  match journal.hash_at(checkpoint.sequence) {
    None => return invalid_report("checkpoint sequence is outside the journal")
    Some(anchor) =>
      if anchor != checkpoint.journal_hash {
        return invalid_report("checkpoint journal anchor does not match")
      }
  }
  let state_hashes : Array[Int] = [actual_state_hash]
  let mut state = checkpoint.state
  let mut applied = 0
  for index = checkpoint.sequence; index < journal.length(); index = index + 1 {
    let event = match journal.get(index) {
      Some(event) => event
      None => abort("validated journal index disappeared")
    }
    match reducer(state, event) {
      Accepted(next_state) => {
        state = next_state
        applied = applied + 1
        state_hashes.push(fingerprint_text(state_fingerprint(state)))
      }
      Rejected(reason) =>
        return {
          status: Rejected(event.sequence, reason),
          initial_sequence: checkpoint.sequence,
          applied_events: applied,
          final_sequence: event.sequence - 1,
          final_state: state,
          final_state_hash: fingerprint_text(state_fingerprint(state)),
          journal_tail_hash: journal.tail_hash(),
          state_hashes,
        }
    }
  }
  {
    status: Completed,
    initial_sequence: checkpoint.sequence,
    applied_events: applied,
    final_sequence: checkpoint.sequence + applied,
    final_state: state,
    final_state_hash: fingerprint_text(state_fingerprint(state)),
    journal_tail_hash: journal.tail_hash(),
    state_hashes,
  }
}