// tx.mbt — FEC 发送组状态机(K8 刀3)
//
// 语义(具名效应,K9 策略机可拾):收片 → 攒组 → 组满同突发发冗余 → 闭组
// 推进组号。发侧零等待(D-卷 §四):数据帧到达即出,冗余帧只搭组满那拍
// 的顺风车;尾组不满 k 不闭(D-FEC4),ARQ 兜底。

///|
/// FEC 发送机:纯形零 IO,帧由宿主搬运。
pub struct FecTx {
  k : Int32
  m : Int32
  /// 线帧整片字节数(12B 头 + 体);载荷上限 = capacity - 14。
  capacity : Int32
  codec : &MdsCodec
  mut group_id : Int32
  mut slots : Array[FixedArray[Byte]]
  mut closed_groups : Int32
  mut frames_out : Int32
}

///|
/// RS 缺省适配器版。
pub fn FecTx::new(k : Int32, m : Int32, capacity : Int32) -> FecTx {
  FecTx::with_codec(RsCodec::new(k, m), k, m, capacity)
}

///|
/// 端口注入版(换码 = 换适配器,层机器零改动)。
/// 判据:k ≥ 1,m ≥ 1,k+m ≤ 255(shard_total 单字节线域;越域即断言死,
/// k+m=256 的组形线头写不出,发出来只会全数被对端弃置——谎报不如急停)。
pub fn FecTx::with_codec(
  codec : &MdsCodec,
  k : Int32,
  m : Int32,
  capacity : Int32,
) -> FecTx {
  if k < 1 || m < 1 || k + m > 255 {
    abort(
      "FecTx::with_codec: 组形越线域 k=\{k} m=\{m}(判据:k≥1, m≥1, k+m≤255)",
    )
  }
  {
    k,
    m,
    capacity,
    codec,
    group_id: 0,
    slots: [],
    closed_groups: 0,
    frames_out: 0,
  }
}

///|
/// 发送快照。
pub struct FecTxStats {
  closed_groups : Int32
  frames_out : Int32
}

///|
pub fn FecTx::stats(self : FecTx) -> FecTxStats {
  { closed_groups: self.closed_groups, frames_out: self.frames_out, }
}

///|
/// 下一片数据:载荷进组,出线上帧——数据帧即出;组满同突发追加冗余帧。
/// 判据:载荷 ≤ capacity - 14(12B 头 + 2B 体长前缀),超限即断言死
/// (刀4 以 KCP mtu 让位保证;静默截断是谎报)。
pub fn FecTx::feed(
  self : FecTx,
  payload : FixedArray[Byte],
) -> Array[FixedArray[Byte]] {
  if payload.length() > self.capacity - 14 {
    abort(
      "FecTx::feed 载荷超片容量:\{payload.length()} > \{self.capacity - 14}",
    )
  }
  let payload_len = payload.length()
  let shard = FixedArray::make(self.capacity, (0).to_byte())
  shard[FEC_HEADER_SIZE] = ((payload_len >> 8) & 0xFF).to_byte()
  shard[FEC_HEADER_SIZE + 1] = (payload_len & 0xFF).to_byte()
  FixedArray::unsafe_blit(shard, FEC_HEADER_SIZE + 2, payload, 0, payload_len)
  fec_write_header(
    shard,
    self.group_id,
    self.slots.length(),
    self.k,
    self.m,
    payload_len,
  )
  self.slots.push(shard)
  let out : Array[FixedArray[Byte]] = [shard]
  self.frames_out += 1
  if self.slots.length() == self.k {
    // 闭组:同突发发冗余帧(头编码后重盖)
    let data : Array[FixedArray[Byte]] = []
    for s in self.slots {
      data.push(s)
    }
    let parity = self.codec.encode_parity(data)
    for r in 0..