///|
/// Perceptual-style quality metrics for export verification.
pub(all) struct AnimationQuality {
  compared_frames : Int
  mean_error : Int
  maximum_error : Int
  differing_frames : Int
  mean_psnr_milli : Int
} derive(Debug)

///|
pub fn AnimationQuality::compared_frames(self : AnimationQuality) -> Int {
  self.compared_frames
}

///|
pub fn AnimationQuality::mean_error(self : AnimationQuality) -> Int {
  self.mean_error
}

///|
pub fn AnimationQuality::maximum_error(self : AnimationQuality) -> Int {
  self.maximum_error
}

///|
pub fn AnimationQuality::differing_frames(self : AnimationQuality) -> Int {
  self.differing_frames
}

///|
pub fn AnimationQuality::mean_psnr(self : AnimationQuality) -> Int {
  self.mean_psnr_milli
}

///|
/// Compare matching frame positions using existing deterministic metrics.
pub fn compare_animation_quality(
  left : Animation,
  right : Animation,
) -> AnimationQuality {
  let count = if left.frame_count() < right.frame_count() {
    left.frame_count()
  } else {
    right.frame_count()
  }
  let mut total_error = 0
  let mut maximum = 0
  let mut different = 0
  let mut total_psnr = 0
  for i in 0.. maximum {
      maximum = metrics.maximum_error()
    }
    if metrics.differing_pixels() > 0 {
      different += 1
    }
    total_psnr += metrics.psnr_milli()
  }
  AnimationQuality::{
    compared_frames: count,
    mean_error: if count == 0 {
      0
    } else {
      total_error / count
    },
    maximum_error: maximum,
    differing_frames: different,
    mean_psnr_milli: if count == 0 {
      0
    } else {
      total_psnr / count
    },
  }
}

///|
/// Compare decoded output against source animation shape and colors.
pub fn encoded_quality(
  source : Animation,
  encoded : Bytes,
) -> AnimationQuality? {
  match decode_checked(encoded) {
    None => None
    Some(decoded) => Some(compare_animation_quality(source, decoded))
  }
}

///|
/// Return true when every compared frame meets a mean error budget.
pub fn AnimationQuality::within_error(
  self : AnimationQuality,
  maximum_mean_error : Int,
) -> Bool {
  self.compared_frames() > 0 && self.mean_error() <= maximum_mean_error
}

///|
/// Return a compact score where 1000 means exact frame equality.
pub fn AnimationQuality::score(self : AnimationQuality) -> Int {
  if self.compared_frames() == 0 {
    0
  } else {
    let score = 1000 - self.mean_error() * 1000 / 195075
    if score < 0 {
      0
    } else {
      score
    }
  }
}