///|
/// Deterministic benchmark workload for GIF encoding, decoding, and quantization.
pub(all) struct BenchmarkWorkload {
  width : Int
  height : Int
  frames : Int
  palette_colors : Int
  delay_cs : Int
} derive(Debug)

///|
pub fn BenchmarkWorkload::default() -> BenchmarkWorkload {
  { width: 128, height: 96, frames: 12, palette_colors: 64, delay_cs: 6 }
}

///|
pub fn BenchmarkWorkload::size(
  self : BenchmarkWorkload,
  width : Int,
  height : Int,
) -> BenchmarkWorkload {
  {
    ..self,
    width: if width < 1 {
      1
    } else {
      width
    },
    height: if height < 1 {
      1
    } else {
      height
    },
  }
}

///|
pub fn BenchmarkWorkload::frame_count(
  self : BenchmarkWorkload,
  count : Int,
) -> BenchmarkWorkload {
  { ..self, frames: if count < 1 { 1 } else { count } }
}

///|
pub fn BenchmarkWorkload::colors(
  self : BenchmarkWorkload,
  count : Int,
) -> BenchmarkWorkload {
  { ..self, palette_colors: clamp_palette_size(count) }
}

///|
pub(all) struct BenchmarkResult {
  workload : BenchmarkWorkload
  encoded_bytes : Int
  estimated_bytes : Int
  decoded_frames : Int
  source_pixels : Int
  roundtrip_equal : Bool
  palette_error : Int
  lzw_saving_per_mille : Int
} derive(Debug)

///|
pub fn BenchmarkResult::workload(self : BenchmarkResult) -> BenchmarkWorkload {
  self.workload
}

///|
pub fn BenchmarkResult::encoded_bytes(self : BenchmarkResult) -> Int {
  self.encoded_bytes
}

///|
pub fn BenchmarkResult::estimated_bytes(self : BenchmarkResult) -> Int {
  self.estimated_bytes
}

///|
pub fn BenchmarkResult::decoded_frames(self : BenchmarkResult) -> Int {
  self.decoded_frames
}

///|
pub fn BenchmarkResult::source_pixels(self : BenchmarkResult) -> Int {
  self.source_pixels
}

///|
pub fn BenchmarkResult::roundtrip_equal(self : BenchmarkResult) -> Bool {
  self.roundtrip_equal
}

///|
pub fn BenchmarkResult::palette_error(self : BenchmarkResult) -> Int {
  self.palette_error
}

///|
pub fn BenchmarkResult::lzw_saving_per_mille(self : BenchmarkResult) -> Int {
  self.lzw_saving_per_mille
}

///|
fn benchmark_canvas(width : Int, height : Int, frame_index : Int) -> Canvas {
  let canvas = Canvas::new(width~, height~, fill=rgba(12, 18, 30, 255))
  let center_x = (frame_index * 7 + width / 3) % width
  let center_y = (frame_index * 5 + height / 2) % height
  for y in 0.. 12 { 255 } else { 0 }
      canvas.pixels[x + y * width] = rgba(
        ((wave + ring) % 256).to_byte(),
        ((wave * 2 + frame_index * 11) % 256).to_byte(),
        ((255 - wave + ring / 2) % 256).to_byte(),
        if (x + y + frame_index) % 29 == 0 {
          160
        } else {
          255
        },
      )
    }
  }
  canvas
}

///|
/// Generate the shared benchmark workload without randomness or external files.
pub fn benchmark_animation(workload : BenchmarkWorkload) -> Animation {
  let safe_width = if workload.width < 1 { 1 } else { workload.width }
  let safe_height = if workload.height < 1 { 1 } else { workload.height }
  let safe_frames = if workload.frames < 1 { 1 } else { workload.frames }
  let safe_delay = if workload.delay_cs < 0 { 0 } else { workload.delay_cs }
  let result = Animation::new(width=safe_width, height=safe_height)
  for index in 0.. BenchmarkResult raise GifError {
  let animation = benchmark_animation(workload)
  let options = EncodeOptions::new(
    max_colors=workload.palette_colors,
    default_delay_cs=workload.delay_cs,
  )
  let encoded = encode_with_options(animation, options)
  let decoded = decode(encoded)
  let estimates = estimate_size(animation, max_colors=workload.palette_colors)
  let quantized = quantize_animation(
    animation,
    PalettePlan::new(LocalMedianCut, workload.palette_colors),
  )
  let mut error_total = 0
  for i in 0.. String {
  "frames=\{self.workload.frames} size=\{self.workload.width}x\{self.workload.height} " +
  "encoded=\{self.encoded_bytes} estimated=\{self.estimated_bytes} " +
  "decoded=\{self.decoded_frames} pixels=\{self.source_pixels} " +
  "roundtrip=\{self.roundtrip_equal} palette_error=\{self.palette_error}"
}

///|
/// Compare two benchmark runs by encoded size and quality error.
pub fn benchmark_delta(
  left : BenchmarkResult,
  right : BenchmarkResult,
) -> (Int, Int) {
  (
    right.encoded_bytes - left.encoded_bytes,
    right.palette_error - left.palette_error,
  )
}

///|
/// Return a workload scaled for a quick local smoke check.
pub fn quick_benchmark_workload() -> BenchmarkWorkload {
  BenchmarkWorkload::default().size(32, 24).frame_count(4).colors(32)
}

///|
/// Return a workload that exercises the full export path.
pub fn acceptance_benchmark_workload() -> BenchmarkWorkload {
  BenchmarkWorkload::default()
}