///|
/// Replacement frames for missing slots, in stripe then shard order.
/// The caller decides where to persist them. Existing frames are never rewritten.
pub struct ObjectRepair {
  replacements : Array[ShardEnvelope]
  repaired_stripes : Int
}

///|
pub fn ObjectRepair::replacements(self : ObjectRepair) -> Array[ShardEnvelope] {
  let copy : Array[ShardEnvelope] = []
  for frame in self.replacements {
    copy.push(frame)
  }
  copy
}

///|
pub fn ObjectRepair::replacement_count(self : ObjectRepair) -> Int {
  self.replacements.length()
}

///|
pub fn ObjectRepair::repaired_stripes(self : ObjectRepair) -> Int {
  self.repaired_stripes
}

///|
/// Regenerate only absent frames. Every supplied frame is checked by the
/// stripe decoder; missing frames beyond parity capacity abort the operation.
/// This operation is all-or-error: callers receive no partial repair list.
pub fn repair_object(
  manifest : Manifest,
  frames : Array[ShardEnvelope],
  max_encoded_bytes? : Int = 16_777_216,
) -> ObjectRepair raise ErasureError {
  let codec = Codec::new(
    manifest.data_count(),
    manifest.parity_count(),
    max_encoded_bytes~,
  )
  if manifest.stripe_payload_limit() >
    codec.data_count() * codec.max_shard_bytes() {
    raise ResourceLimit("codec budget cannot hold manifest stripe")
  }
  if frames.length() > manifest.stripe_count() * codec.total_count() {
    raise ResourceLimit("too many frames for manifest")
  }
  let groups : Array[Array[ShardEnvelope]] = []
  for index = 0; index < manifest.stripe_count(); index = index + 1 {
    groups.push([])
  }
  for frame in frames {
    if frame.set_id() != manifest.set_id() {
      raise InvalidManifest("repair frame set id differs from manifest")
    }
    let stripe_index = frame.stripe_index()
    if stripe_index < 0 || stripe_index >= manifest.stripe_count() {
      raise InvalidIndex(stripe_index)
    }
    groups[stripe_index].push(frame)
  }
  let replacements : Array[ShardEnvelope] = []
  let mut repaired_stripes = 0
  for index = 0; index < manifest.stripe_count(); index = index + 1 {
    let result = recover_stripe(codec, groups[index], manifest.set_id(), index)
    if result.payload().length() != manifest.stripe_length(index) {
      raise InvalidManifest("repair stripe length differs from manifest")
    }
    let missing = result.missing_indices()
    if missing.length() > 0 {
      repaired_stripes = repaired_stripes + 1
      let rebuilt = result.shards()
      for shard_index in missing {
        replacements.push(rebuilt[shard_index])
      }
    }
  }
  { replacements, repaired_stripes, }
}