///|
/// Creation checkpoints describe real reads, not proof that metainfo is encoded or saved.
/// before_write is emitted only by the controlled CLI adapter, before atomic publication.
pub(all) struct CreationProgress {
  phase : String
  mode : TorrentMode
  file_count : Int32
  total_bytes : Int64
  read_calls : Int32
  validated_read_bytes : Int64
  file : Int32?
  offset : Int64?
  length : Int32?
  metainfo_bytes : Int32?
}

///|
priv struct CreateControl {
  callback : (CreationProgress) -> Bool raise CrateError
  plan : TorrentPlan
  mut reads : Int32
  mut bytes : Int64
}

///|
fn emit_creation_progress(
  control : CreateControl?,
  phase : String,
  file~ : Int32? = None,
  offset~ : Int64? = None,
  length~ : Int32? = None,
  metainfo_bytes~ : Int32? = None,
) -> Unit raise CrateError {
  if control is Some(control) {
    let event : CreationProgress = {
      phase,
      mode: control.plan.mode,
      file_count: control.plan.file_count,
      total_bytes: control.plan.real_bytes,
      read_calls: control.reads,
      validated_read_bytes: control.bytes,
      file,
      offset,
      length,
      metainfo_bytes,
    }
    if !(control.callback)(event) {
      reject("cancelled", "torrent creation cancelled by progress callback")
    }
  }
}

///|
/// Create complete, validated metainfo with synchronous cooperative cancellation.
/// Callback false or any error returns no metainfo. No filesystem writes occur here.
pub fn create_torrent_with_control(
  files : Array[SourceFile],
  options : CreateOptions,
  read : (Int32, Int64, Int32) -> Bytes raise CrateError,
  on_progress : (CreationProgress) -> Bool raise CrateError,
) -> Bytes raise CrateError {
  let plan = plan_torrent(files, options)
  // Freeze caller-owned path/endpoint arrays before callbacks can modify them.
  let files = files.map(fn(file) {
    SourceFile::{ path: file.path.copy(), length: file.length, }
  })
  let options : CreateOptions = {
    ..options,
    trackers: options.trackers.map(fn(tier) { tier.copy() }),
    web_seeds: options.web_seeds.copy(),
  }
  create_torrent_impl(files, options, read, Some(on_progress), plan)
}