///|
/// Checkpoint metadata for resumable waveform processing.
pub struct ProcessingCheckpoint {
  job_id : String
  station : String
  channel : String
  next_sample : Int
  total_samples : Int
  stage_sequence : Int
  checksum : String
  complete : Bool
}

///|
/// Create the initial checkpoint for a trace.
pub fn initial_processing_checkpoint(
  job_id : String,
  trace : Trace,
) -> ProcessingCheckpoint {
  {
    job_id,
    station: trace.station,
    channel: trace.channel,
    next_sample: 0,
    total_samples: trace.npts(),
    stage_sequence: 0,
    checksum: waveform_checksum([]),
    complete: trace.npts() == 0,
  }
}

///|
/// Advance a checkpoint after processing one block.
pub fn advance_processing_checkpoint(
  checkpoint : ProcessingCheckpoint,
  processed_samples : Int,
  stage_sequence : Int,
  checksum : String,
) -> ProcessingCheckpoint {
  let next = (checkpoint.next_sample + processed_samples.max(0)).min(
    checkpoint.total_samples,
  )
  {
    job_id: checkpoint.job_id,
    station: checkpoint.station,
    channel: checkpoint.channel,
    next_sample: next,
    total_samples: checkpoint.total_samples,
    stage_sequence,
    checksum,
    complete: next >= checkpoint.total_samples,
  }
}

///|
/// Validate checkpoint compatibility with a trace.
pub fn validate_processing_checkpoint(
  checkpoint : ProcessingCheckpoint,
  trace : Trace,
) -> Result[Unit, String] {
  if checkpoint.station != trace.station || checkpoint.channel != trace.channel {
    return Err("checkpoint channel does not match trace")
  }
  if checkpoint.total_samples != trace.npts() {
    return Err("checkpoint sample count does not match trace")
  }
  if checkpoint.next_sample < 0 ||
    checkpoint.next_sample > checkpoint.total_samples {
    return Err("checkpoint cursor is outside trace bounds")
  }
  Ok(())
}

///|
/// Return the remaining half-open range for a checkpoint.
pub fn checkpoint_remaining_range(
  checkpoint : ProcessingCheckpoint,
) -> (Int, Int) {
  (checkpoint.next_sample, checkpoint.total_samples)
}

///|
/// Export checkpoints to a stable text record.
pub fn processing_checkpoint_text(checkpoint : ProcessingCheckpoint) -> String {
  "job=" +
  checkpoint.job_id +
  " station=" +
  checkpoint.station +
  " channel=" +
  checkpoint.channel +
  " next=" +
  checkpoint.next_sample.to_string() +
  " total=" +
  checkpoint.total_samples.to_string() +
  " stage=" +
  checkpoint.stage_sequence.to_string() +
  " checksum=" +
  checkpoint.checksum +
  " complete=" +
  checkpoint.complete.to_string()
}