// effect.mbt
//! Effect 重试状态机库缝(spec §2.1):
//! exec(命令) --raise--> 状态机裁决(重试/放行/熔断)--> 调用方。
//! 状态机自身 = gen 产物(词表 TOML 经骨架生成器产骨架 +
//! 手写钩子);本件只定协议面与 runner,不含任何策略裁决。

///|
/// 框架错误分类封闭集(结构知识非裁决——分类固定;动作归词表+钩子)
pub(all) enum EffectErrorKind {
  Duplicate // 乐观并发冲突族:DuplicateVersion / Conflict——可重试性质
  Validation // 业务拒绝族:InvalidCommand 等——重放同结果,永不重试
  Storage // 存储层族:StorageError / SerializationError
  Other
}

///|
/// 重试动作封闭集(词表 on_error 值域)
pub(all) enum RetryAction {
  ActionRetry
  ActionExhausted
}

///|
/// 状态机裁决输出
pub(all) enum RetryVerdict {
  WaitRetry(wait_ms~ : Int64)
  GiveUpRaise
}

///|
/// Effect 重试状态机协议(记录面,宪法4;gen 产物实现)
pub(all) struct RetryMachine {
  /// 词表默认分派表(on_error 四类 → 动作)
  default_action : (EffectErrorKind) -> RetryAction
  /// 纯裁决(无时钟无副作用,红线6):attempt = 本次将为第几次重试(1 起);
  /// consecutive_failures = 含本次失败的连续失败数。退避时长与熔断判定由此内算。
  decide : (Int32, Int32, EffectErrorKind) -> RetryVerdict
}

///|
/// 策略钩子(红线7:业务裁决归钩子;None = 沿用词表默认分派)
pub(all) struct RetryHooks {
  override_action : (EffectErrorKind) -> RetryAction?
}

///|
/// 纯透钩子(零覆写)
pub fn RetryHooks::none() -> RetryHooks {
  { override_action: fn(_kind) { None }, }
}

///|
/// 命令错误分类(结构知识:Conflict → Duplicate——命令面乐观并发镜像,可重试性质)
pub fn effect_kind_of_command_error(
  err : @command.CommandError,
) -> EffectErrorKind {
  match err {
    Conflict(_, _) => Duplicate
    InvalidCommand(_) => Validation
    UnregisteredCommand(_) => Validation
    Unauthorized(_) => Validation
    PreconditionFailed(_) => Validation
    NotFound(_) => Validation
  }
}

///|
/// 存储错误分类(dispatch 层 with_config 挂钩用)
pub fn effect_kind_of_store_error(
  err : @store.EventStoreError,
) -> EffectErrorKind {
  match err {
    DuplicateVersion(_) => Duplicate
    StorageError(_) => Storage
    SerializationError(_) => Storage
  }
}

///|
/// Effect 形态执行(spec §2.1 分层):exec 上抛先过状态机——
/// 钩子覆写 > 词表默认 → decide → WaitRetry(ms) 则 wait(ms)(注入时钟,红线6 同源)
/// 后重执行;ActionExhausted / GiveUpRaise(含熔断)→ 原错上抛。
/// 幂等前提:execute Err 不改态 ⇒ 同命令重放同结果。
pub fn[AggregateId, S, C, E] AggregateRoot::exec_with_effect(
  self : AggregateRoot[AggregateId, S, C, E],
  cmd : C,
  machine : RetryMachine,
  hooks : RetryHooks,
  wait : (Int64) -> Unit,
) -> AggregateRoot[AggregateId, S, C, E] raise @command.CommandError {
  let mut attempt = 0
  let mut consecutive_failures = 0
  let mut root = self
  let mut running = true
  while running {
    let outcome : Result[
      AggregateRoot[AggregateId, S, C, E],
      @command.CommandError,
    ] = Ok(root.exec(cmd)) catch { e => Err(e) }
    match outcome {
      Ok(next) => {
        root = next
        running = false
      }
      Err(e) => {
        consecutive_failures = consecutive_failures + 1
        let kind = effect_kind_of_command_error(e)
        let action = match (hooks.override_action)(kind) {
          Some(overridden) => overridden
          None => (machine.default_action)(kind)
        }
        match action {
          ActionExhausted => raise e
          ActionRetry => {
            attempt = attempt + 1
            match (machine.decide)(attempt, consecutive_failures, kind) {
              WaitRetry(wait_ms~) => wait(wait_ms)
              GiveUpRaise => raise e
            }
          }
        }
      }
    }
  }
  root
}