///|
/// Transition shapes for generating in-between GIF frames.
pub(all) enum TransitionKind {
  CrossFade
  WipeLeft
  WipeRight
  WipeUp
  WipeDown
  ZoomIn
  ZoomOut
  Dissolve
} derive(Eq, Debug)

///|
pub(all) struct TransitionOptions {
  kind : TransitionKind
  steps : Int
  delay_cs : Int
  seed : Int
} derive(Debug)

///|
pub fn TransitionOptions::default() -> TransitionOptions {
  { kind: CrossFade, steps: 6, delay_cs: 4, seed: 1 }
}

///|
pub fn TransitionOptions::kind(
  self : TransitionOptions,
  value : TransitionKind,
) -> TransitionOptions {
  { ..self, kind: value }
}

///|
pub fn TransitionOptions::steps(
  self : TransitionOptions,
  value : Int,
) -> TransitionOptions {
  {
    ..self,
    steps: if value < 1 {
      1
    } else if value > 256 {
      256
    } else {
      value
    },
  }
}

///|
pub fn TransitionOptions::delay(
  self : TransitionOptions,
  value : Int,
) -> TransitionOptions {
  {
    ..self,
    delay_cs: if value < 0 {
      0
    } else if value > 65535 {
      65535
    } else {
      value
    },
  }
}

///|
pub fn TransitionOptions::seed(
  self : TransitionOptions,
  value : Int,
) -> TransitionOptions {
  { ..self, seed: value }
}

///|
fn transition_pixel(
  left : Rgba,
  right : Rgba,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  step : Int,
  steps : Int,
  kind : TransitionKind,
  seed : Int,
) -> Rgba {
  let progress = if steps <= 0 { 1000 } else { step * 1000 / steps }
  let safe_width = if width == 0 { 1 } else { width }
  let safe_height = if height == 0 { 1 } else { height }
  match kind {
    CrossFade => mix_color(left, right, progress, 1000)
    WipeLeft => if x * 1000 / safe_width <= progress { right } else { left }
    WipeRight =>
      if (width - x) * 1000 / safe_width <= progress {
        right
      } else {
        left
      }
    WipeUp => if y * 1000 / safe_height <= progress { right } else { left }
    WipeDown =>
      if (height - y) * 1000 / safe_height <= progress {
        right
      } else {
        left
      }
    ZoomIn => {
      let center_x = width / 2
      let center_y = height / 2
      let source_x = center_x + (x - center_x) * (1000 - progress) / 1000
      let source_y = center_y + (y - center_y) * (1000 - progress) / 1000
      if source_x == x && source_y == y {
        right
      } else {
        mix_color(left, right, progress, 1000)
      }
    }
    ZoomOut => {
      let pulse = (progress + (x + y + seed) % 17) % 1000
      mix_color(left, right, pulse, 1000)
    }
    Dissolve => {
      let noise = ((x * 928371 + y * 364479 + seed * 17) % 1000).abs()
      if noise <= progress {
        right
      } else {
        left
      }
    }
  }
}

///|
fn transition_canvas(
  left : Canvas,
  right : Canvas,
  step : Int,
  steps : Int,
  options : TransitionOptions,
) -> Canvas {
  let width = if left.width < right.width { left.width } else { right.width }
  let height = if left.height < right.height {
    left.height
  } else {
    right.height
  }
  let result = Canvas::new(width~, height~, fill=rgba(0, 0, 0, 0))
  for y in 0.. Array[Frame] {
  let result : Array[Frame] = []
  let steps = if options.steps < 1 { 1 } else { options.steps }
  for step in 1.. Animation {
  if self.frames.length() <= 1 {
    self
  } else {
    let result = Animation::new(width=self.width, height=self.height)
      .with_loop_count(loop_count=self.loop_count)
      .with_background(background=self.background)
    for i, frame in self.frames {
      ignore(result.add_frame(frame))
      if i + 1 < self.frames.length() {
        for transition in transition_frames(frame, self.frames[i + 1], options~) {
          ignore(result.add_frame(transition))
        }
      }
    }
    result
  }
}

///|
/// Crossfade two complete animations by pairing frames cyclically.
pub fn crossfade_animations(
  left : Animation,
  right : Animation,
  steps : Int,
  delay_cs : Int,
) -> Animation {
  let width = if left.width < right.width { left.width } else { right.width }
  let height = if left.height < right.height {
    left.height
  } else {
    right.height
  }
  let result = Animation::new(width~, height~)
  let count = if left.frame_count() > right.frame_count() {
    left.frame_count()
  } else {
    right.frame_count()
  }
  let safe_steps = if steps < 1 { 1 } else { steps }
  for index in 0.. Animation {
  let safe_steps = if steps < 1 { 1 } else { steps }
  let result = Animation::new(width=left.width, height=left.height)
  for step in 0..<=safe_steps {
    ignore(
      result.add_frame(
        Frame::new(
          canvas=transition_canvas(
            left,
            right,
            step,
            safe_steps,
            TransitionOptions::default(),
          ),
          delay_cs~,
        ),
      ),
    )
  }
  let mut step = safe_steps - 1
  while step > 0 {
    ignore(
      result.add_frame(
        Frame::new(
          canvas=transition_canvas(
            left,
            right,
            step,
            safe_steps,
            TransitionOptions::default(),
          ),
          delay_cs~,
        ),
      ),
    )
    step -= 1
  }
  result
}