///|
/// A shared, sticky work allowance for one SDK call or a caller-owned workflow.
/// Units are deterministic accounting credits, not milliseconds or heap bytes.
/// The cancellation callback must be quick and must not reenter this control.
pub struct WorkControl {
  allowance : Int
  should_cancel : (String) -> Bool
  mut consumed : Int
  mut stopped : String?
}

///|
pub fn WorkControl::default() -> WorkControl {
  {
    allowance: 500000000,
    should_cancel: _ => false,
    consumed: 0,
    stopped: None,
  }
}

///|
/// The same control may be shared by prepare/rebuild/verify to bound their sum.
/// A cancelled or exhausted control cannot be reset; create a new one to retry.
pub fn WorkControl::new(
  work_units? : Int = 500000000,
  should_cancel? : (String) -> Bool = _ => false,
) -> WorkControl raise {
  guard work_units >= 0 && work_units <= 1000000000 else {
    raise Refused("work allowance must be between 0 and 1000000000")
  }
  { allowance: work_units, should_cancel, consumed: 0, stopped: None, }
}

///|
pub fn WorkControl::cancel(self : WorkControl) -> Unit {
  if self.stopped is None {
    self.stopped = Some("operation cancelled by caller")
  }
}

///|
pub fn WorkControl::used(self : WorkControl) -> Int {
  self.consumed
}

///|
pub fn WorkControl::remaining(self : WorkControl) -> Int {
  self.allowance - self.consumed
}

///|
pub fn WorkControl::stop_reason(self : WorkControl) -> String? {
  self.stopped
}

///|
fn WorkControl::poll(self : WorkControl, stage : String, units : Int) -> Bool {
  if self.stopped is Some(_) {
    return true
  }
  if (self.should_cancel)(stage) {
    self.cancel()
  }
  if self.stopped is Some(_) {
    return true
  }
  if units < 0 || units > self.remaining() {
    self.stopped = Some("work allowance exhausted at " + stage)
    return true
  }
  self.consumed += units
  false
}

///|
fn WorkControl::checkpoint(
  self : WorkControl,
  stage : String,
  units : Int,
) -> Unit raise {
  if self.poll(stage, units) {
    raise Incomplete(self.stopped.unwrap())
  }
}

///|
// Reserve bounded pairwise scans without multiplying untrusted counts first.
fn WorkControl::pairs(
  self : WorkControl,
  stage : String,
  left : Int,
  right : Int,
) -> Unit raise {
  self.checkpoint(stage, 0)
  if left > 0 && right > self.remaining() / left {
    self.stopped = Some("work allowance exhausted at " + stage)
    raise Incomplete(self.stopped.unwrap())
  }
  self.checkpoint(stage, left * right)
}

///|
fn work_digest(data : BytesView, control : WorkControl) -> String raise {
  control.checkpoint("hash", data.length())
  let hash = digest(data)
  control.checkpoint("hash-complete", 0)
  hash
}