///|
/// A geometric transform that preserves the pixel grid.
pub(all) enum CanvasRotation {
  Clockwise90
  HalfTurn
  Clockwise270
} derive(Eq, Debug)

///|
/// Rotate a canvas without interpolation.
pub fn Canvas::rotate(self : Canvas, rotation : CanvasRotation) -> Canvas {
  match rotation {
    Clockwise90 => self.rotate90()
    HalfTurn => self.rotate180()
    Clockwise270 => self.rotate270()
  }
}

///|
/// Rotate clockwise. The old top-left pixel becomes the top-right pixel.
pub fn Canvas::rotate90(self : Canvas) -> Canvas {
  let result = Canvas::new(
    width=self.height,
    height=self.width,
    fill=rgba(0, 0, 0, 0),
  )
  for y in 0.. Canvas {
  let result = Canvas::new(
    width=self.width,
    height=self.height,
    fill=rgba(0, 0, 0, 0),
  )
  for y in 0.. Canvas {
  let result = Canvas::new(
    width=self.height,
    height=self.width,
    fill=rgba(0, 0, 0, 0),
  )
  for y in 0.. Canvas {
  if left < 0 || top < 0 || right < 0 || bottom < 0 {
    panic()
  }
  let result = Canvas::new(
    width=left + self.width + right,
    height=top + self.height + bottom,
    fill~,
  )
  for y in 0.. Canvas {
  let result = self.copy()
  for i in 0.. Canvas {
  let result = self.copy()
  for i in 0.. GifRect? {
  let mut min_x = self.width
  let mut min_y = self.height
  let mut max_x = -1
  let mut max_y = -1
  for y in 0..= alpha_threshold {
        if x < min_x {
          min_x = x
        }
        if y < min_y {
          min_y = y
        }
        if x > max_x {
          max_x = x
        }
        if y > max_y {
          max_y = y
        }
      }
    }
  }
  if max_x < 0 {
    None
  } else {
    Some(
      GifRect::new(
        left=min_x,
        top=min_y,
        width=max_x - min_x + 1,
        height=max_y - min_y + 1,
      ),
    )
  }
}

///|
/// Crop transparent margins. An empty canvas returns a 1x1 transparent canvas.
pub fn Canvas::trim_transparent(
  self : Canvas,
  alpha_threshold? : Byte = 1,
) -> Canvas {
  match self.opaque_bounds(alpha_threshold~) {
    None => Canvas::new(width=1, height=1, fill=rgba(0, 0, 0, 0))
    Some(bounds) =>
      self.crop(
        x=bounds.left(),
        y=bounds.top(),
        width=bounds.width(),
        height=bounds.height(),
      )
  }
}

///|
/// Composite a source image over this canvas at an offset.
pub fn Canvas::overlay(
  self : Canvas,
  source : Canvas,
  left~ : Int,
  top~ : Int,
) -> Canvas {
  let result = self.copy()
  ignore(result.blit(source, destination_x=left, destination_y=top))
  result
}

///|
/// Create a tiled checkerboard useful as a transparency preview background.
pub fn checkerboard_canvas(
  width~ : Int,
  height~ : Int,
  cell_size? : Int = 8,
  light? : Rgba = rgb(238, 238, 238),
  dark? : Rgba = rgb(204, 204, 204),
) -> Canvas {
  if width < 0 || height < 0 || cell_size <= 0 {
    panic()
  }
  let result = Canvas::new(width~, height~, fill=light)
  for y in 0.. Canvas {
  checkerboard_canvas(width=self.width, height=self.height, cell_size~).overlay(
    self,
    left=0,
    top=0,
  )
}

///|
/// Nearest-neighbor scale to fit inside a box, preserving aspect ratio.
pub fn Canvas::contain(
  self : Canvas,
  width~ : Int,
  height~ : Int,
  fill? : Rgba = rgba(0, 0, 0, 0),
) -> Canvas {
  if width < 0 || height < 0 {
    panic()
  }
  if self.width == 0 || self.height == 0 || width == 0 || height == 0 {
    Canvas::new(width~, height~, fill~)
  } else {
    let by_width = width * self.height / self.width
    let scaled_width = if by_width <= height {
      width
    } else {
      height * self.width / self.height
    }
    let scaled_height = if by_width <= height { by_width } else { height }
    let scaled = self.resize_nearest(width=scaled_width, height=scaled_height)
    let result = Canvas::new(width~, height~, fill~)
    let left = (width - scaled_width) / 2
    let top = (height - scaled_height) / 2
    ignore(result.blit(scaled, destination_x=left, destination_y=top))
    result
  }
}

///|
/// Nearest-neighbor scale to cover a box, cropping the overflow.
pub fn Canvas::cover(self : Canvas, width~ : Int, height~ : Int) -> Canvas {
  if width < 0 || height < 0 {
    panic()
  }
  if self.width == 0 || self.height == 0 || width == 0 || height == 0 {
    Canvas::new(width~, height~, fill=rgba(0, 0, 0, 0))
  } else {
    let by_width = width * self.height / self.width
    let scaled_width = if by_width >= height {
      width
    } else {
      height * self.width / self.height
    }
    let scaled_height = if by_width >= height { by_width } else { height }
    let scaled = self.resize_nearest(width=scaled_width, height=scaled_height)
    let left = (scaled_width - width) / 2
    let top = (scaled_height - height) / 2
    scaled.crop(x=left, y=top, width~, height~)
  }
}

///|
/// Return the four corner colors in clockwise order.
pub fn Canvas::corners(self : Canvas) -> Array[Rgba] {
  if self.width == 0 || self.height == 0 {
    []
  } else {
    [
      self.pixels[0],
      self.pixels[self.width - 1],
      self.pixels[(self.height - 1) * self.width + self.width - 1],
      self.pixels[(self.height - 1) * self.width],
    ]
  }
}

///|
/// Return a clipped rectangle as a value suitable for frame metadata.
pub fn Canvas::bounds(self : Canvas) -> GifRect {
  GifRect::new(left=0, top=0, width=self.width, height=self.height)
}