///|
/// Checks an invariant at the initial state and after every accepted event.
///
/// The invariant returns `None` when the state is valid or a stable diagnostic
/// message when it is violated. The first violation is therefore the shortest
/// replay prefix that reproduces the failure.
pub fn[P, S] find_first_invariant_failure(
  journal : Journal[P],
  initial_state : S,
  payload_fingerprint : (P) -> String,
  state_fingerprint : (S) -> String,
  reducer : (S, JournalEvent[P]) -> Transition[S],
  invariant : (S) -> String?,
) -> InvariantResult {
  match journal.validate(payload_fingerprint) {
    Invalid(issue) => return ReplayFailed(InvalidJournal(issue))
    Valid(_, _) => ()
  }
  match invariant(initial_state) {
    Some(reason) =>
      return Violated(
        0,
        fingerprint_text(state_fingerprint(initial_state)),
        reason,
      )
    None => ()
  }
  let mut state = initial_state
  for index = 0; 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) {
      Rejected(reason) => return ReplayFailed(Rejected(event.sequence, reason))
      Accepted(next_state) => {
        state = next_state
        match invariant(state) {
          Some(reason) =>
            return Violated(
              event.sequence,
              fingerprint_text(state_fingerprint(state)),
              reason,
            )
          None => ()
        }
      }
    }
  }
  Holds(journal.length() + 1)
}

///|
/// Returns the shortest journal prefix that reproduces an invariant failure.
pub fn[P, S] minimal_failing_prefix(
  journal : Journal[P],
  initial_state : S,
  payload_fingerprint : (P) -> String,
  state_fingerprint : (S) -> String,
  reducer : (S, JournalEvent[P]) -> Transition[S],
  invariant : (S) -> String?,
) -> Journal[P]? {
  match
    find_first_invariant_failure(
      journal, initial_state, payload_fingerprint, state_fingerprint, reducer, invariant,
    ) {
    Violated(sequence, _, _) => Some(journal.prefix(sequence))
    _ => None
  }
}