///|
/// Synchronous cooperative-verification checkpoint. All fields are value snapshots.
/// Checks count hash comparisons (two per hybrid piece), not physical byte ranges.
pub(all) struct VerificationProgress {
  phase : String
  total_checks : Int32
  checked_pieces : Int32
  failed_pieces : Int32
  unreadable_pieces : Int32
  read_calls : Int32
  validated_read_bytes : Int64
  complete_scope : Bool
  selected_file_count : Int32
  required_file_count : Int32
  file : Int32?
  offset : Int64?
  length : Int32?
  read_ok : Bool?
}

///|
priv struct VerifyControl {
  callback : (VerificationProgress) -> Bool raise CrateError
  total : Int32
  scope : VerificationScope
  mut checked : Int32
  mut failed : Int32
  mut unreadable : Int32
  mut read_calls : Int32
  mut read_bytes : Int64
  mut error : CrateError?
}

///|
fn new_verify_control(
  parsed : ParsedTorrent,
  plan : VerifyPlan,
  callback : (VerificationProgress) -> Bool raise CrateError,
) -> VerifyControl {
  let mut total = 0
  if parsed.summary.mode != V2 {
    for selected in plan.v1_pieces {
      if selected {
        total += 1
      }
    }
  }
  if parsed.summary.mode != V1 {
    let width = parsed.summary.piece_length.to_int64()
    for i in plan.scope.selected_files {
      total += ((parsed.summary.files[i].length + width - 1L) / width).to_int()
    }
  }
  {
    callback,
    total,
    scope: plan.scope,
    checked: 0,
    failed: 0,
    unreadable: 0,
    read_calls: 0,
    read_bytes: 0L,
    error: None,
  }
}

///|
fn check_verification_control(
  control : VerifyControl?,
) -> Unit raise CrateError {
  if control is Some(control) {
    if control.error is Some(error) {
      raise error
    }
  }
}

///|
fn emit_verification_progress(
  control : VerifyControl?,
  phase : String,
  file~ : Int32? = None,
  offset~ : Int64? = None,
  length~ : Int32? = None,
  read_ok~ : Bool? = None,
) -> Unit raise CrateError {
  check_verification_control(control)
  if control is Some(control) {
    let event : VerificationProgress = {
      phase,
      total_checks: control.total,
      checked_pieces: control.checked,
      failed_pieces: control.failed,
      unreadable_pieces: control.unreadable,
      read_calls: control.read_calls,
      validated_read_bytes: control.read_bytes,
      complete_scope: control.scope.complete,
      selected_file_count: control.scope.selected_files.length(),
      required_file_count: control.scope.required_files.length(),
      file,
      offset,
      length,
      read_ok,
    }
    let keep_going = (control.callback)(event) catch {
      CrateError(code, detail) => {
        let error = CrateError(code, detail)
        control.error = Some(error)
        raise error
      }
    }
    if !keep_going {
      let error = CrateError(
        "cancelled", "verification cancelled by progress callback",
      )
      control.error = Some(error)
      raise error
    }
  }
}

///|
fn controlled_read(
  control : VerifyControl,
  read : (Int32, Int64, Int32) -> Bytes raise CrateError,
  file : Int32,
  offset : Int64,
  size : Int32,
) -> Bytes raise CrateError {
  emit_verification_progress(
    Some(control),
    "before_read",
    file=Some(file),
    offset=Some(offset),
    length=Some(size),
  )
  control.read_calls += 1
  let data = read_exact(read, file, offset, size) catch {
    CrateError(code, detail) => {
      emit_verification_progress(
        Some(control),
        "after_read",
        file=Some(file),
        offset=Some(offset),
        length=Some(size),
        read_ok=Some(false),
      )
      raise CrateError(code, detail)
    }
  }
  control.read_bytes += size.to_int64()
  emit_verification_progress(
    Some(control),
    "after_read",
    file=Some(file),
    offset=Some(offset),
    length=Some(size),
    read_ok=Some(true),
  )
  data
}