///|
/// Centralized limits used by public APIs and command-line validation.
pub(all) struct GifLimits {
  max_dimension : Int
  max_colors : Int
  max_delay_cs : Int
  max_frames : Int
  max_comment_bytes : Int
} derive(Debug)

///|
pub fn GifLimits::default() -> GifLimits {
  {
    max_dimension: 65535,
    max_colors: 256,
    max_delay_cs: 65535,
    max_frames: 100000,
    max_comment_bytes: 65535,
  }
}

///|
pub fn GifLimits::dimension(self : GifLimits) -> Int {
  self.max_dimension
}

///|
pub fn GifLimits::colors(self : GifLimits) -> Int {
  self.max_colors
}

///|
pub fn GifLimits::delay(self : GifLimits) -> Int {
  self.max_delay_cs
}

///|
pub fn GifLimits::frames(self : GifLimits) -> Int {
  self.max_frames
}

///|
pub fn GifLimits::comment_bytes(self : GifLimits) -> Int {
  self.max_comment_bytes
}

///|
pub fn GifLimits::valid_dimensions(
  self : GifLimits,
  width : Int,
  height : Int,
) -> Bool {
  width > 0 &&
  height > 0 &&
  width <= self.max_dimension &&
  height <= self.max_dimension
}

///|
pub fn GifLimits::clamp_delay(self : GifLimits, delay : Int) -> Int {
  if delay < 0 {
    0
  } else if delay > self.max_delay_cs {
    self.max_delay_cs
  } else {
    delay
  }
}

///|
pub fn GifLimits::clamp_frames(self : GifLimits, count : Int) -> Int {
  if count < 0 {
    0
  } else if count > self.max_frames {
    self.max_frames
  } else {
    count
  }
}

///|
/// Count violations against canonical GIF limits.
pub fn validate_limits(
  animation : Animation,
  limits : GifLimits,
) -> Array[String] {
  let issues : Array[String] = []
  if !limits.valid_dimensions(animation.width, animation.height) {
    issues.push("logical screen dimensions exceed GIF limits")
  }
  if animation.frame_count() > limits.max_frames {
    issues.push("frame count exceeds configured safety limit")
  }
  for _, frame in animation.frames {
    if frame.canvas().width > limits.max_dimension ||
      frame.canvas().height > limits.max_dimension {
      issues.push("frame {index} exceeds configured dimension limit")
    }
    if frame.delay_cs() > limits.max_delay_cs {
      issues.push("frame delay exceeds 16-bit GIF limit")
    }
  }
  issues
}

///|
/// Clamp an animation to configured dimensions and delay limits.
pub fn Animation::clamp_limits(
  self : Animation,
  limits : GifLimits,
) -> Animation {
  let repaired = repair_animation(self)
  let result = Animation::new(width=repaired.width, height=repaired.height)
    .with_loop_count(loop_count=repaired.loop_count)
    .with_background(background=repaired.background)
  for frame in repaired.frames {
    ignore(
      result.add_frame(
        frame.with_delay(delay_cs=limits.clamp_delay(frame.delay_cs())),
      ),
    )
  }
  result
}