///|
/// Multi-animation composition helpers for stickers and screen-recording scenes.
pub(all) enum CompositeAlignment {
  TopLeft
  Center
  BottomRight
  Tile
} derive(Eq, Debug)

///|
pub(all) struct CompositeOptions {
  alignment : CompositeAlignment
  gap : Int
  background : Rgba
  loop_to_longest : Bool
} derive(Debug)

///|
pub fn CompositeOptions::default() -> CompositeOptions {
  {
    alignment: Center,
    gap: 0,
    background: rgba(0, 0, 0, 0),
    loop_to_longest: true,
  }
}

///|
pub fn CompositeOptions::alignment(
  self : CompositeOptions,
  value : CompositeAlignment,
) -> CompositeOptions {
  { ..self, alignment: value }
}

///|
pub fn CompositeOptions::gap(
  self : CompositeOptions,
  value : Int,
) -> CompositeOptions {
  { ..self, gap: if value < 0 { 0 } else { value } }
}

///|
pub fn CompositeOptions::background(
  self : CompositeOptions,
  value : Rgba,
) -> CompositeOptions {
  { ..self, background: value }
}

///|
pub fn CompositeOptions::loop_to_longest(
  self : CompositeOptions,
  value : Bool,
) -> CompositeOptions {
  { ..self, loop_to_longest: value }
}

///|
fn aligned_offset(
  width : Int,
  height : Int,
  layer_width : Int,
  layer_height : Int,
  alignment : CompositeAlignment,
) -> (Int, Int) {
  match alignment {
    TopLeft => (0, 0)
    Center => ((width - layer_width) / 2, (height - layer_height) / 2)
    BottomRight => (width - layer_width, height - layer_height)
    Tile => (0, 0)
  }
}

///|
/// Composite one animation over another at a shared timeline position.
pub fn composite_animations(
  base : Animation,
  overlay : Animation,
  options? : CompositeOptions = CompositeOptions::default(),
) -> Animation {
  let width = if base.width > overlay.width {
    base.width
  } else {
    overlay.width
  }
  let height = if base.height > overlay.height {
    base.height
  } else {
    overlay.height
  }
  let count = if options.loop_to_longest {
    if base.frame_count() > overlay.frame_count() {
      base.frame_count()
    } else {
      overlay.frame_count()
    }
  } else if base.frame_count() < overlay.frame_count() {
    base.frame_count()
  } else {
    overlay.frame_count()
  }
  let result = Animation::new(width~, height~)
  if count == 0 {
    result
  } else {
    for index in 0.. overlay_frame.delay_cs() {
        base_frame.delay_cs()
      } else {
        overlay_frame.delay_cs()
      }
      ignore(result.add_frame(Frame::new(canvas~, delay_cs=delay)))
    }
    result
  }
}

///|
/// Compose three animation layers in back-to-front order.
pub fn composite_three(
  base : Animation,
  middle : Animation,
  front : Animation,
  options : CompositeOptions,
) -> Animation {
  composite_animations(
    composite_animations(base, middle, options~),
    front,
    options~,
  )
}

///|
/// Stack a static canvas over every frame at a selected alignment.
pub fn Animation::overlay_static(
  self : Animation,
  overlay : Canvas,
  alignment : CompositeAlignment,
) -> Animation {
  let result = Animation::new(width=self.width, height=self.height)
    .with_loop_count(loop_count=self.loop_count)
    .with_background(background=self.background)
  let (left, top) = aligned_offset(
    self.width,
    self.height,
    overlay.width,
    overlay.height,
    alignment,
  )
  for frame in self.frames {
    let canvas = frame.canvas().copy()
    ignore(canvas.blit(overlay, destination_x=left, destination_y=top))
    ignore(result.add_frame(frame.with_canvas(canvas)))
  }
  result
}

///|
/// Make a tiled animation from a single source animation.
pub fn Animation::tile_frames(
  self : Animation,
  columns : Int,
  rows : Int,
  gap : Int,
  background : Rgba,
) -> Animation {
  let safe_columns = if columns < 1 { 1 } else { columns }
  let safe_rows = if rows < 1 { 1 } else { rows }
  let output_width = self.width * safe_columns + (safe_columns - 1) * gap
  let output_height = self.height * safe_rows + (safe_rows - 1) * gap
  let result = Animation::new(width=output_width, height=output_height)
  for index, frame in self.frames {
    let canvas = Canvas::new(
      width=output_width,
      height=output_height,
      fill=background,
    )
    for row in 0.. Animation {
  if self.frames.length() <= 1 {
    self
  } else {
    let frames : Array[Frame] = []
    let mut current = self.frames[0]
    let mut current_hash = current.canvas().hash()
    for i in 1.. Array[Int] {
  let count = if base.frame_count() < overlay.frame_count() {
    base.frame_count()
  } else {
    overlay.frame_count()
  }
  let result : Array[Int] = []
  for i in 0..