///|
priv struct Budget {
  limits : Limits
  mut used : Int32
  mut complete : Bool
  findings : Array[Finding]
}

///|
fn Budget::new(limits : Limits) -> Budget {
  { limits, used: 0, complete: true, findings: [], }
}

///|
fn Budget::stop(
  self : Budget,
  path : InstancePath,
  shape : @smithy.ShapeId,
  reason : String,
) -> Unit {
  if !self.complete {
    return
  }
  self.complete = false
  let finding : Finding = {
    code: LimitExceeded,
    severity: Error,
    path,
    shape,
    message: "I reached the " +
    reason +
    " validation limit before completing the value.",
  }
  if self.findings.length() < self.limits.findings {
    self.findings.push(finding)
  }
}

///|
fn Budget::charge(
  self : Budget,
  path : InstancePath,
  shape : @smithy.ShapeId,
  units~ : Int32 = 1,
) -> Bool {
  if !self.complete {
    return false
  }
  if units > self.limits.work - self.used {
    self.stop(path, shape, "work")
    return false
  }
  self.used += units
  true
}

///|
fn Budget::room(
  self : Budget,
  path : InstancePath,
  shape : @smithy.ShapeId,
  children : Int32,
  pending~ : Int32 = 0,
) -> Bool {
  if pending > self.limits.work - self.used ||
    children > self.limits.work - self.used - pending {
    self.stop(path, shape, "work")
    false
  } else {
    self.complete
  }
}

///|
fn Budget::emit(
  self : Budget,
  code : FindingCode,
  path : InstancePath,
  shape : @smithy.ShapeId,
  message : String,
) -> Unit {
  if !self.complete {
    return
  }
  if self.findings.length() >= self.limits.findings - 1 {
    self.stop(path, shape, "findings")
    return
  }
  self.findings.push({ code, severity: Error, path, shape, message, })
}