///|
pub(all) struct VerificationScope {
  complete : Bool
  selected_files : Array[Int32]
  required_files : Array[Int32]
}

///|
priv struct VerifyPlan {
  scope : VerificationScope
  selected : Array[Bool]
  v1_pieces : Array[Bool]
}

///|
fn marked_indices(mask : Array[Bool]) -> Array[Int32] {
  let indices : Array[Int32] = []
  for i, marked in mask {
    if marked {
      indices.push(i)
    }
  }
  indices
}

///|
fn plan_verification(
  parsed : ParsedTorrent,
  requested : Array[Int32]?,
) -> VerifyPlan raise CrateError {
  let selected = Array::make(parsed.summary.files.length(), false)
  match requested {
    None => selected.fill(true)
    Some(indices) => {
      if indices.is_empty() {
        reject("selection", "select at least one file")
      }
      for index in indices {
        if index < 0 || index >= selected.length() {
          reject("selection", "file index is outside inspected file list")
        }
        selected[index] = true
      }
    }
  }
  let v1_pieces = Array::make(parsed.pieces.length() / 20, false)
  let piece = parsed.summary.piece_length.to_int64()
  let indices = marked_indices(selected)
  let complete = indices.length() == selected.length()
  let mut cursor = 0L
  if complete {
    v1_pieces.fill(true)
  } else if parsed.summary.mode != V2 {
    for entry in parsed.entries {
      if !entry.padding && entry.length > 0L && selected[entry.source] {
        let first = (cursor / piece).to_int()
        let end = ((cursor + entry.length - 1L) / piece).to_int() + 1
        for p in first.. 0L {
        let first = (cursor / piece).to_int()
        let end = ((cursor + entry.length - 1L) / piece).to_int() + 1
        for p in first.. VerificationScope raise CrateError {
  plan_verification(parse_torrent(torrent), selected_files).scope
}