///|
fn text(value : String) -> String {
  value
}

///|
fn int_text(value : Int) -> String {
  "\{value}"
}

///|
fn amount_reducer(
  state : Int,
  event : @replay.JournalEvent[String],
) -> @replay.Transition[Int] {
  match event.payload {
    "add-5" => @replay.Transition::Accepted(state + 5)
    "subtract-4" => @replay.Transition::Accepted(state - 4)
    "double" => @replay.Transition::Accepted(state * 2)
    "cancel" => @replay.Transition::Accepted(0)
    _ => @replay.Transition::Rejected("unknown command")
  }
}

///|
fn upgraded_reducer(
  state : Int,
  event : @replay.JournalEvent[String],
) -> @replay.Transition[Int] {
  match event.payload {
    "double" => @replay.Transition::Accepted(state * 3)
    _ => amount_reducer(state, event)
  }
}

///|
fn state_json(value : Int) -> String {
  "\{value}"
}

///|
fn payload_json(value : String) -> String {
  "\"\{@replay.json_escape(value)}\""
}

///|
fn main {
  let journal : @replay.Journal[String] = @replay.Journal::new()
  ignore(journal.append("Balance", "add-5", "demo-1", text))
  ignore(journal.append("Balance", "double", "demo-1", text))
  ignore(journal.append("Balance", "subtract-4", "demo-1", text))

  println("journal=" + @replay.journal_to_json(journal, payload_json))
  let full = @replay.replay(journal, 0, text, int_text, amount_reducer)
  println("full_replay=" + @replay.replay_report_to_json(full, state_json))
  println(
    "deterministic=\{@replay.verify_determinism(journal, 0, text, int_text, amount_reducer)}",
  )
  match @replay.build_replay_evidence(journal, full, text) {
    Some(tree) => {
      println("evidence=" + @replay.replay_evidence_to_json(tree))
      match @replay.replay_inclusion_proof(journal, full, tree, 2) {
        Some(proof) =>
          println(
            "proof={\"sequence\":\{proof.sequence},\"depth\":\{proof.siblings.length()},\"verified\":\{@replay.verify_replay_inclusion(proof)}}",
          )
        None => println("proof=unavailable")
      }
    }
    None => println("evidence=unavailable")
  }

  let prefix_report = @replay.replay(
    journal.prefix(2),
    0,
    text,
    int_text,
    amount_reducer,
  )
  match @replay.checkpoint_from_report(prefix_report) {
    Some(checkpoint) => {
      let resumed = @replay.replay_from_checkpoint(
        journal, checkpoint, text, int_text, amount_reducer,
      )
      println(
        "checkpoint_replay=" +
        @replay.replay_report_to_json(resumed, state_json),
      )
    }
    None => println("checkpoint_replay=unavailable")
  }

  match @replay.ReplayBranch::fork("cancel-path", journal, 2, text) {
    Some(branch) => {
      ignore(branch.append("Balance", "cancel", "demo-1", text))
      match @replay.compare_histories(journal, branch.journal(), text) {
        Some(divergence) =>
          println(
            "branch_divergence=sequence:\{divergence.sequence},kind:\{@replay.divergence_kind_name(divergence.kind)}",
          )
        None => println("branch_divergence=none")
      }
    }
    None => println("branch=invalid")
  }

  let migration = @replay.compare_reducer_versions(
    journal, 0, text, int_text, amount_reducer, upgraded_reducer,
  )
  match migration.first_divergence {
    Some(divergence) =>
      println(
        "migration=changed_at:\{divergence.sequence},old:\{migration.old_report.final_state},new:\{migration.new_report.final_state}",
      )
    None => println("migration=compatible")
  }

  let failing : @replay.Journal[String] = @replay.Journal::new()
  ignore(failing.append("Balance", "add-5", "demo-2", text))
  ignore(failing.append("Balance", "subtract-4", "demo-2", text))
  ignore(failing.append("Balance", "subtract-4", "demo-2", text))
  let invariant = fn(value : Int) -> String? {
    if value < 0 {
      Some("balance became negative")
    } else {
      None
    }
  }
  match
    @replay.find_first_invariant_failure(
      failing, 0, text, int_text, amount_reducer, invariant,
    ) {
    @replay.InvariantResult::Violated(sequence, _, reason) =>
      println("invariant=failed_at:\{sequence},reason:\{reason}")
    _ => println("invariant=holds")
  }
}