///|
fn Engine::checkpoint_value(self : Engine) -> Json {
  {
    "format": "tideweave-checkpoint",
    "schema_version": 1,
    "config": self.cfg.to_json(),
    "commands": @json.to_json(self.journal),
    "version": self.revision,
    "watermark": self.clock,
  }
}

///|
/// A portable validated replay checkpoint, not a signed proof of history.
pub fn Engine::checkpoint(self : Engine) -> String {
  self.checkpoint_value().stringify()
}

///|
fn restore_value(value : Json) -> Engine raise TideError {
  let f = obj(value)
  fields_only(f, [
    "format", "schema_version", "config", "commands", "version", "watermark",
  ])
  if str(req(f, "format")) != "tideweave-checkpoint" ||
    integer(req(f, "schema_version")) != 1 {
    fail("unsupported checkpoint format or schema")
  }
  let c = read_config(req(f, "config"))
  let commands = read_commands(req(f, "commands"))
  let engine = Engine::new(c)
  for cmd in commands {
    if engine.apply(cmd).status != "accepted" {
      fail("checkpoint journal contains an uncommittable command")
    }
  }
  if engine.version() != integer(req(f, "version")) ||
    engine.watermark() != integer(req(f, "watermark")) {
    fail("checkpoint version or watermark disagrees with replayed journal")
  }
  engine
}

///|
pub fn restore(source : String) -> Engine raise TideError {
  restore_value(strict_json(source))
}

///|
/// Resume input has a checkpoint object and an array of new commands.
pub fn resume_json(source : String) -> String raise TideError {
  let f = obj(strict_json(source))
  fields_only(f, ["schema_version", "checkpoint", "commands"])
  if integer(req(f, "schema_version")) != 1 {
    fail("unsupported schema_version")
  }
  let engine = restore_value(req(f, "checkpoint"))
  let commands = read_commands(req(f, "commands"))
  engine.run(commands).to_json().stringify()
}