///|
/// Timeline metrics used to compare edits before exporting.
pub(all) struct TimelineMetrics {
  frames : Int
  duration_cs : Int
  minimum_delay_cs : Int
  maximum_delay_cs : Int
  mean_delay_cs : Int
  empty_frames : Int
  active_pixels : Int
} derive(Debug)

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

///|
pub fn TimelineMetrics::duration(self : TimelineMetrics) -> Int {
  self.duration_cs
}

///|
pub fn TimelineMetrics::minimum_delay(self : TimelineMetrics) -> Int {
  self.minimum_delay_cs
}

///|
pub fn TimelineMetrics::maximum_delay(self : TimelineMetrics) -> Int {
  self.maximum_delay_cs
}

///|
pub fn TimelineMetrics::mean_delay(self : TimelineMetrics) -> Int {
  self.mean_delay_cs
}

///|
pub fn TimelineMetrics::empty_frames(self : TimelineMetrics) -> Int {
  self.empty_frames
}

///|
pub fn TimelineMetrics::active_pixels(self : TimelineMetrics) -> Int {
  self.active_pixels
}

///|
/// Calculate duration, delay distribution, and visible pixel totals.
pub fn Animation::timeline_metrics(self : Animation) -> TimelineMetrics {
  let mut duration = 0
  let mut minimum = 65535
  let mut maximum = 0
  let mut empty = 0
  let mut active = 0
  for frame in self.frames {
    let delay = frame.delay_cs()
    duration += delay
    if delay < minimum {
      minimum = delay
    }
    if delay > maximum {
      maximum = delay
    }
    for color in frame.canvas().pixels {
      if color.alpha() == 0 {
        empty += 1
      } else {
        active += 1
      }
    }
  }
  let count = self.frame_count()
  TimelineMetrics::{
    frames: count,
    duration_cs: duration,
    minimum_delay_cs: if count == 0 {
      0
    } else {
      minimum
    },
    maximum_delay_cs: maximum,
    mean_delay_cs: if count == 0 {
      0
    } else {
      duration / count
    },
    empty_frames: empty,
    active_pixels: active,
  }
}

///|
/// Return an animation's effective frames per second in milli-Hz.
pub fn Animation::effective_fps_milli(self : Animation) -> Int {
  let duration = self.duration_cs()
  if duration <= 0 {
    0
  } else {
    self.frame_count() * 100000 / duration
  }
}

///|
/// Return the first frame whose delay exceeds a threshold.
pub fn Animation::first_slow_frame(
  self : Animation,
  threshold_cs : Int,
) -> Int? {
  let threshold = if threshold_cs < 0 { 0 } else { threshold_cs }
  for index, frame in self.frames {
    if frame.delay_cs() > threshold {
      return Some(index)
    }
  }
  None
}

///|
/// Compare timeline structure without comparing pixels.
pub fn compare_timeline(left : Animation, right : Animation) -> (Int, Int, Int) {
  let a = left.timeline_metrics()
  let b = right.timeline_metrics()
  (
    b.frames() - a.frames(),
    b.duration() - a.duration(),
    b.mean_delay() - a.mean_delay(),
  )
}

///|
/// Return a copy with pathological delays normalized to a playback floor.
pub fn Animation::with_delay_floor(
  self : Animation,
  floor_cs : Int,
) -> Animation {
  let floor = if floor_cs < 0 {
    0
  } else if floor_cs > 65535 {
    65535
  } else {
    floor_cs
  }
  let frames : Array[Frame] = []
  for frame in self.frames {
    frames.push(
      if frame.delay_cs() < floor {
        frame.with_delay(delay_cs=floor)
      } else {
        frame
      },
    )
  }
  Animation::{ ..self, frames, }
}