///|
/// Transport the runtime uses to hand application model state across a
/// process restart (state-preserving dev restarts). `read` returns the
/// previously persisted payload, if any; `write` persists a fresh payload.
/// Both are pure closures: the I/O ownership stays with whoever constructs
/// the transport (typically the composition root), never with `core` or the
/// runtime itself.
pub struct ProgramStateTransport {
  priv read : () -> String?
  priv write : (String) -> Unit
}

///|
/// Application-supplied model serialization contract. `save` serializes the
/// current model; `load` recovers a model from a payload, returning `None`
/// when the payload is missing, stale, or unreadable so the runtime falls
/// back to the program's own init model.
pub struct ProgramStatePersistence[Model] {
  priv save : (Model) -> String
  priv load : (String) -> Model?
}

///|
pub fn ProgramStateTransport::new(
  read~ : () -> String?,
  write~ : (String) -> Unit,
) -> ProgramStateTransport {
  { read, write, }
}

///|
pub fn ProgramStateTransport::read(self : ProgramStateTransport) -> String? {
  (self.read)()
}

///|
pub fn ProgramStateTransport::write(
  self : ProgramStateTransport,
  payload : String,
) -> Unit {
  (self.write)(payload)
}

///|
pub fn[Model] ProgramStatePersistence::new(
  save~ : (Model) -> String,
  load~ : (String) -> Model?,
) -> ProgramStatePersistence[Model] {
  { save, load, }
}

///|
pub fn[Model] ProgramStatePersistence::save(
  self : ProgramStatePersistence[Model],
  model : Model,
) -> String {
  (self.save)(model)
}

///|
pub fn[Model] ProgramStatePersistence::load(
  self : ProgramStatePersistence[Model],
  payload : String,
) -> Model? {
  (self.load)(payload)
}