///|
/// Optional work limits for a single high-level decode call. max_input_bytes
/// also caps each assembled item/sample payload, including repeated extents.
/// Pixel counts are
/// luma-grid areas, not bytes or a process-memory guarantee. Every reconstructed
/// or show-existing picture counts, including hidden pictures and alpha tracks.
/// Grid canvases also consume pixels. Reference state persists between video
/// calls, but the work budget restarts for each call.
/// All fields must be positive; max_frame_pixels cannot exceed 100,000,000.
pub(all) struct DecodeLimits {
  max_input_bytes : Int
  max_frame_pixels : Int
  max_total_pixels : Int
  max_frames : Int
}

///|
/// An opt-in starting policy: 64 MiB input, 16 Mi pixels per padded picture,
/// 128 Mi cumulative pixels and 256 pictures. Ordinary free functions preserve
/// their original policy. Applications can construct stricter limits.
pub fn DecodeLimits::default() -> DecodeLimits {
  {
    max_input_bytes: 64 * 1024 * 1024,
    max_frame_pixels: 16 * 1024 * 1024,
    max_total_pixels: 128 * 1024 * 1024,
    max_frames: 256,
  }
}

///|
priv struct DecodeBudget {
  limits : DecodeLimits
  mut frames_left : Int
  mut pixels_left : Int
  diagnostic : DecodeDiagnostic?
}

///|
fn decode_input_allowed(length : Int, limits : DecodeLimits?) -> Bool {
  match limits {
    None => true
    Some(value) =>
      value.max_input_bytes > 0 &&
      value.max_frame_pixels > 0 &&
      value.max_frame_pixels <= MAX_IMAGE_PIXELS &&
      value.max_total_pixels > 0 &&
      value.max_frames > 0 &&
      length >= 0 &&
      length <= value.max_input_bytes
  }
}

///|
fn decode_budget(limits : DecodeLimits?) -> DecodeBudget? {
  limits.map(value => {
    limits: value,
    frames_left: value.max_frames,
    pixels_left: value.max_total_pixels,
    diagnostic: None,
  })
}

///|
fn decode_samples_allowed(
  samples : Array[Array[Byte]],
  limits : DecodeLimits?,
) -> Bool {
  if !decode_input_allowed(0, limits) {
    return false
  }
  match limits {
    None => true
    Some(value) => {
      let mut remaining = value.max_input_bytes
      for sample in samples {
        if sample.length() > remaining {
          return false
        }
        remaining -= sample.length()
      }
      true
    }
  }
}

///|
fn decode_payload_allowed(
  length : Int,
  added : Int,
  limits : DecodeLimits?,
) -> Bool {
  if length < 0 || added < 0 || added > 0x7FFFFFFF - length {
    return false
  }
  match limits {
    None => true
    Some(value) => added <= value.max_input_bytes - length
  }
}

///|
/// Reserve before allocating a reconstructed picture or grid canvas. AV1
/// reconstruction counts MI padding and the larger super-resolution extent.
fn decode_reserve(
  budget : DecodeBudget?,
  width : Int,
  height : Int,
  picture? : Bool = true,
  padded? : Bool = true,
) -> Bool {
  let value = match budget {
    None => return true
    Some(value) => value
  }
  if width <= 0 ||
    height <= 0 ||
    width > MAX_IMAGE_PIXELS ||
    height > MAX_IMAGE_PIXELS {
    return false
  }
  let w = if padded { (width + 7) / 8 * 8 } else { width }
  let h = if padded { (height + 7) / 8 * 8 } else { height }
  if w > value.limits.max_frame_pixels / h ||
    w > value.pixels_left / h ||
    (picture && value.frames_left == 0) {
    decode_fail(
      budget,
      LimitExceeded,
      "Picture size, cumulative pixels or picture count exceeds the selected policy",
    )
    return false
  }
  value.pixels_left -= w * h
  if picture {
    value.frames_left -= 1
  }
  true
}

///|
/// Bounds parser stack use even when optional decode work limits are absent.
/// Normal AVIF box hierarchies require far fewer than 64 nested containers.
const AVIF_MAX_BOX_DEPTH : Int = 64