///|
/// A half-open byte range that has enough admitted frames to attempt recovery.
/// Presence is not authentication or a new content checksum verification.
pub struct RecoverableRange {
  start : Int
  end : Int
}

///|
pub fn RecoverableRange::start(self : RecoverableRange) -> Int {
  self.start
}

///|
pub fn RecoverableRange::end(self : RecoverableRange) -> Int {
  self.end
}

///|
pub fn RecoverableRange::length(self : RecoverableRange) -> Int {
  self.end - self.start
}

///|
/// Enumerate maximal contiguous ranges whose stripes each have at least k
/// admitted frames. Supports partial media reads and resume scheduling without
/// decoding the object. A result budget is checked before every emitted run.
pub fn FrameCatalog::recoverable_ranges(
  self : FrameCatalog,
  max_ranges? : Int = 4096,
) -> Array[RecoverableRange] raise ErasureError {
  if max_ranges < 0 {
    raise ResourceLimit("recoverable range count")
  }
  let width = self.manifest.data_count() + self.manifest.parity_count()
  let result : Array[RecoverableRange] = []
  let mut run_start : Int? = None
  for stripe = 0; stripe < self.manifest.stripe_count(); stripe = stripe + 1 {
    let mut present = 0
    for shard = 0; shard < width; shard = shard + 1 {
      if self.slots[stripe * width + shard] is Some(_) {
        present += 1
      }
    }
    let start = stripe * self.manifest.stripe_payload_limit()
    if present >= self.manifest.data_count() {
      if run_start is None {
        run_start = Some(start)
      }
    } else if run_start is Some(first) {
      if result.length() >= max_ranges {
        raise ResourceLimit("recoverable range count")
      }
      result.push({ start: first, end: start, })
      run_start = None
    }
  }
  if run_start is Some(first) {
    if result.length() >= max_ranges {
      raise ResourceLimit("recoverable range count")
    }
    result.push({ start: first, end: self.manifest.total_length(), })
  }
  result
}