// projection.mbt
//! 投影:从事件构建读模型(FR-MC-3 投影契约)
//!
//! 契约(吸收 commanded 检查点语义,MoonBit 形):
//! 1. **拉取非推送**:投影是 EventStore 的订阅方——只认
//!    `load(aggregate_id, after_checkpoint)`;EventBus 推送与投影无关
//!    (推送 = 通知,投影 = 重放,两法源)。
//! 2. **检查点即版本**:`last_version` = checkpoint;`refresh` 拉取
//!    checkpoint 之后的事件,apply_batch 折入视图,推进检查点——幂等
//!    (同 checkpoint 重拉重放,视图不变)。
//! 3. **apply 纯函数**:投影函数禁副作用、**禁取当前时钟**(时间源唯一,
//!    const 红线6);视图演化只由事件驱动。
//! 4. **视图持久化可选**:`PersistedProjection` = 视图 + checkpoint 的
//!    SQLite 落点(save 即 ack;load 即恢复),与 `LiveProjection` 组合
//!    = 重启续跑。
//! MVP 口径:拉取形(poll);推送订阅随消费需求另役。

///|
/// 视图(MVP 口径 = Json;强类型视图随消费需求扩形)
type View = Json

///|
/// 投影器(记录型函数面,ADR-MC-003 同族):init 出初始视图,
/// apply 单事件演化,apply_batch 批量折叠(缺省 = 逐事件 apply)。
pub(all) struct Projection[AggregateId, E] {
  init : () -> View
  apply : (View, @event.EventMessage[AggregateId, E]) -> View
  apply_batch : (View, Array[@event.EventMessage[AggregateId, E]]) -> View
}

///|
pub fn[AggregateId, E] Projection::new(
  init : () -> View,
  apply : (View, @event.EventMessage[AggregateId, E]) -> View,
  apply_batch : (View, Array[@event.EventMessage[AggregateId, E]]) -> View,
) -> Projection[AggregateId, E] {
  { init, apply, apply_batch, }
}

///|
/// 缺省批量折叠(逐事件 apply——多数投影够用;性能敏感可自定义)。
pub fn[AggregateId, E] default_batch(
  apply : (View, @event.EventMessage[AggregateId, E]) -> View,
) -> (View, Array[@event.EventMessage[AggregateId, E]]) -> View {
  fn(view, events) {
    events.fold(init=view, fn(acc, event) { apply(acc, event) })
  }
}

///|
/// 订阅位置(刷新起点语义)
pub(all) enum SubscriptionPosition {
  Origin // 从头开始
  Current // 只订阅新事件
  FromVersion(Int32)
}

///|
/// 解析订阅位置为起始版本(Current 需 store 探版本)。
fn[AggregateId, E] position_version(
  position : SubscriptionPosition,
  event_store : @store.EventStore[AggregateId, E],
  aggregate_id : AggregateId,
) -> Int32 raise @store.EventStoreError {
  match position {
    Origin => 0
    Current => (event_store.current_version)(event_store, aggregate_id)
    FromVersion(v) => v
  }
}

///|
/// 实时投影(内存视图 + 检查点;拉取刷新形——FR-MC-3 参考实现)。
pub(all) struct LiveProjection[AggregateId, E] {
  mut view : View
  mut last_version : Int32
  projection : Projection[AggregateId, E]
  event_store : @store.EventStore[AggregateId, E]
  aggregate_id : AggregateId
}

///|
/// 构造:按订阅位置装载——拉取起始版本之后的事件,批量折入初始视图,
/// 检查点 = 已折末事件版本(流空 = 起始版本)。
pub fn[AggregateId, E] LiveProjection::new(
  aggregate_id : AggregateId,
  event_store : @store.EventStore[AggregateId, E],
  projection : Projection[AggregateId, E],
  position : SubscriptionPosition,
) -> LiveProjection[AggregateId, E] raise @store.EventStoreError {
  let start_version = position_version(position, event_store, aggregate_id)
  let events = (event_store.load)(event_store, aggregate_id, start_version) catch {
    e => raise event_store_error_of(e)
  }
  let view = (projection.apply_batch)((projection.init)(), events)
  let last_version = match events.last() {
    Some(last) => last.aggregate_version
    None => start_version
  }
  { view, last_version, projection, event_store, aggregate_id, }
}

///|
/// 刷新:拉取检查点之后的事件,折入视图,推进检查点;流空 = no-op。
pub fn[AggregateId, E] LiveProjection::refresh(
  self : LiveProjection[AggregateId, E],
) -> Unit raise @store.EventStoreError {
  let events = (self.event_store.load)(
    self.event_store,
    self.aggregate_id,
    self.last_version,
  ) catch {
    e => raise event_store_error_of(e)
  }
  if !events.is_empty() {
    self.view = (self.projection.apply_batch)(self.view, events)
    self.last_version = match events.last() {
      Some(last) => last.aggregate_version
      None => self.last_version
    }
  }
}

///|
/// 读视图
pub fn[AggregateId, E] LiveProjection::get_view(
  self : LiveProjection[AggregateId, E],
) -> View {
  self.view
}

///|
/// 读检查点
pub fn[AggregateId, E] LiveProjection::checkpoint(
  self : LiveProjection[AggregateId, E],
) -> Int32 {
  self.last_version
}

///|
/// 错误回原族:EventStoreError 变体原样上浮
/// (DuplicateVersion 可分辨),非本族错才降维 StorageError 文本。
fn event_store_error_of(e : Error) -> @store.EventStoreError {
  match e {
    @store.EventStoreError::DuplicateVersion(v) =>
      @store.EventStoreError::DuplicateVersion(v)
    @store.EventStoreError::SerializationError(m) =>
      @store.EventStoreError::SerializationError(m)
    _ => @store.EventStoreError::StorageError(e.to_string())
  }
}

///|
/// SQLite 持久化投影落点(视图 + checkpoint;save 即 ack,load 即恢复——
/// 与 LiveProjection 组合 = 重启续跑)。
pub(all) struct PersistedProjection {
  conn : @sqlite3.Connection
  table_name : String
}

///|
pub fn PersistedProjection::new(
  conn : @sqlite3.Connection,
  table_name : String,
) -> PersistedProjection raise {
  let proj = { conn, table_name, }
  proj.init_schema()
  proj
}

///|
fn PersistedProjection::init_schema(self : PersistedProjection) -> Unit raise {
  let sql = "CREATE TABLE IF NOT EXISTS \{self.table_name} (id TEXT PRIMARY KEY, view_data TEXT NOT NULL, last_version INTEGER NOT NULL, updated_at INTEGER DEFAULT (unixepoch()))"
  let stmt = self.conn.prepare(sql)
  let _ = stmt.step()
  let _ = stmt.finalize() catch { _ => () }
}

///|
pub fn PersistedProjection::save(
  self : PersistedProjection,
  id : String,
  view : View,
  version : Int32,
) -> Unit raise {
  let sql = "INSERT OR REPLACE INTO \{self.table_name} (id, view_data, last_version) VALUES (?, ?, ?)"
  let stmt = self.conn.prepare(sql)
  errdefer {
    let _ = stmt.finalize() catch { _ => () }
  }
  stmt.bind(index=1, id)
  stmt.bind(index=2, @json.to_json(view).stringify())
  stmt.bind(index=3, version)
  let _ = stmt.step()
}

///|
pub fn PersistedProjection::load(
  self : PersistedProjection,
  id : String,
) -> (View, Int32)? raise {
  let sql = "SELECT view_data, last_version FROM \{self.table_name} WHERE id = ?"
  let stmt = self.conn.prepare(sql)
  errdefer {
    let _ = stmt.finalize() catch { _ => () }
  }
  stmt.bind(index=1, id)
  if stmt.step() {
    let data : String = stmt.column(index=0)
    let version : Int64 = stmt.column(index=1)
    let view = @json.parse(data)
    Some((view, version.to_int()))
  } else {
    None
  }
}