///|
/// A simple in-memory event log repository that simulates disk persistence.
pub struct EventJournal {
  events : Map[Int, Array[String]]
}

///|
/// Creates a new EventJournal.
pub fn EventJournal::new() -> EventJournal {
  { events: Map([]) }
}

///|
/// Message protocol for interacting with the EventJournal actor.
pub enum JournalMsg {
  WriteEvent(Int, String, ActorRef[Unit])
  ReadEvents(Int, ActorRef[Array[String]])
}

///|
/// Actor behavior for managing the persistent event journal.
pub async fn journal_behavior(
  _context : Context,
  state : EventJournal,
  msg : JournalMsg,
) -> EventJournal {
  @async.pause()
  match msg {
    WriteEvent(actor_id, event_str, ack_ref) => {
      match state.events.get(actor_id) {
        Some(arr) => arr.push(event_str)
        None => state.events.set(actor_id, [event_str])
      }
      ack_ref.send(())
      state
    }
    ReadEvents(actor_id, reply_ref) => {
      let events = match state.events.get(actor_id) {
        Some(arr) => arr
        None => []
      }
      reply_ref.send(events)
      state
    }
  }
}

///|
/// A helper representing a stateful Persistent Actor that implements Event Sourcing.
pub struct PersistentActor[State, Event, Command] {
  actor_id : Int
  mut state : State
  journal_ref : ActorRef[JournalMsg]
  command_handler : (State, Command) -> Array[Event]
  event_handler : (State, Event) -> State
  serializer : (Event) -> String
  deserializer : (String) -> Event
}

///|
/// Creates a new PersistentActor.
pub fn[State, Event, Command] PersistentActor::new(
  actor_id : Int,
  initial_state : State,
  journal_ref : ActorRef[JournalMsg],
  command_handler : (State, Command) -> Array[Event],
  event_handler : (State, Event) -> State,
  serializer : (Event) -> String,
  deserializer : (String) -> Event,
) -> PersistentActor[State, Event, Command] {
  {
    actor_id,
    state: initial_state,
    journal_ref,
    command_handler,
    event_handler,
    serializer,
    deserializer,
  }
}

///|
/// Returns the current state of the persistent actor.
pub fn[State, Event, Command] PersistentActor::state(
  self : PersistentActor[State, Event, Command],
) -> State {
  self.state
}

///|
/// Recovers the state of the actor by reading and replaying all past events from the journal.
pub async fn[State, Event, Command] PersistentActor::recover(
  self : PersistentActor[State, Event, Command],
  _context : Context,
) -> Unit {
  @async.pause()
  // Create a temporary mailbox queue to receive the events from the journal
  let reply_mailbox : @aqueue.Queue[ActorMsg[Array[String]]] = @aqueue.Queue(
    kind=Unbounded,
  )
  let reply_ref = { id: -1, mailbox: reply_mailbox }
  self.journal_ref.send(ReadEvents(self.actor_id, reply_ref))

  // Await the response using .get()
  guard reply_mailbox.get() is User(events) else { return }

  // Replay all events to reconstruct state
  for event_str in events {
    let event = (self.deserializer)(event_str)
    self.state = (self.event_handler)(self.state, event)
  }
}

///|
/// Processes a command: generates events, persists them to the journal, and updates state.
pub async fn[State, Event, Command] PersistentActor::process_command(
  self : PersistentActor[State, Event, Command],
  _context : Context,
  command : Command,
) -> Unit {
  @async.pause()
  let events = (self.command_handler)(self.state, command)

  // Persist all generated events
  for event in events {
    let event_str = (self.serializer)(event)
    let ack_mailbox : @aqueue.Queue[ActorMsg[Unit]] = @aqueue.Queue(
      kind=Unbounded,
    )
    let ack_ref = { id: -2, mailbox: ack_mailbox }
    self.journal_ref.send(WriteEvent(self.actor_id, event_str, ack_ref))

    // Await persistence confirmation using .get()
    guard ack_mailbox.get() is User(_) else { return }

    // Apply event to update local state
    self.state = (self.event_handler)(self.state, event)
  }
}

///|
fn _silence_persistence_warnings() -> Unit {
  let _ = WriteEvent(0, "", { id: 0, mailbox: @aqueue.Queue(kind=Unbounded) })
  let _ = ReadEvents(0, { id: 0, mailbox: @aqueue.Queue(kind=Unbounded) })
}