// dispatch.mbt
//! Dispatch 统一编排门面(Commanded dispatch 同位素,更薄——无进程内
//! GenServer、无分布式):一个调用串完五步——
//!   ① exec 纯裁决产出事件(元数据自动包装)
//!   ② EventStore.append 持久化(乐观并发守门在 store 径)
//!   ③ EventBus.publish 通知订阅方(可空选;失败诚实——事件已落库,
//!      盲重试会双重作用)
//!   ④ 提交(清空未提交事件——已持久化即已提交)
//!   ⑤ Strong 一致性:轮询投影检查点追上目标版本(预算内未追上 =
//!      ConsistencyErr)。轮询等待由调用方注入(红线6:门面不读时钟)。
//! 高级编排(快照基态 / 重试机 / Reader 注入)仍走 Router;本门面是
//! 单聚合直连的默认路径。

/// 一致性级别 Consistency(Eventual/Strong)沿用 router.mbt 定义,此处不重复声明。

///|
/// Dispatch 统一编排门面。
/// projections = Strong 模式下需等待的投影注册表(Eventual 可空);
/// poll_wait = Strong 轮询间的等待注入(测试用假钟 / 生产接 async sleep);
/// poll_budget = 轮询次数上限(超过即 ConsistencyErr)。
pub fn[Id, S, C, E] dispatch(
  store : @store.EventStore[Id, E],
  bus : @event.EventBus[Id, E]?,
  aggregate : @aggregate.AggregateRoot[Id, S, C, E],
  cmd : C,
  projections~ : Array[@projection.LiveProjection[Id, E]] = [],
  consistency~ : Consistency = Eventual,
  poll_wait~ : (Int64) -> Unit = fn(_ms) { () },
  poll_budget~ : Int32 = 100,
) -> @aggregate.AggregateRoot[Id, S, C, E] raise EventSourcingErr {
  // ① 纯裁决(业务拒 → CommandErr,不产出事件)
  let committed = match (Ok(aggregate.exec(cmd)) catch { e => Err(e) }) {
    Ok(next) => next
    Err(e) => raise EventSourcingErr::CommandErr(e)
  }
  let events = committed.uncommitted_events()
  // ② 持久化(DuplicateVersion 等守门错误原样上浮——分类/重试归上层 Effect 协议)
  (store.append)(store, aggregate.id, events) catch {
    e => raise EventSourcingErr::EventStoreErr(e)
  }
  // ③ 通知(可空选)
  match bus {
    Some(bus_ref) =>
      (bus_ref.publish)(bus_ref, events) catch {
        e => raise EventSourcingErr::EventBusErr(e)
      }
    None => ()
  }
  // ④ 提交:清空未提交事件(连续 dispatch 同一根不再重发旧事件)
  let committed = committed.commit()
  // ⑤ Strong 一致性:轮询投影检查点追上目标版本
  if consistency is Strong && projections.length() > 0 {
    let target = committed.version()
    let mut polls = 0
    let mut caught_up = false
    while polls < poll_budget && !caught_up {
      caught_up = true
      for projection in projections {
        projection.refresh() catch {
          e => raise EventSourcingErr::EventStoreErr(e)
        }
        if projection.checkpoint() < target {
          caught_up = false
        }
      }
      if !caught_up {
        poll_wait(50L)
        polls += 1
      }
    }
    if !caught_up {
      raise EventSourcingErr::ConsistencyErr(
        "projection checkpoint behind version \{target} after \{poll_budget} polls",
      )
    }
  }
  committed
}