///|
/// Exact clap fractions. Sizes/denominators are unsigned 32-bit values;
/// offsets are signed 32-bit numerators. No floating-point rounding is applied.
pub(all) struct AvifCleanAperture {
  width_n : Int64
  width_d : Int64
  height_n : Int64
  height_d : Int64
  horizontal_n : Int64
  horizontal_d : Int64
  vertical_n : Int64
  vertical_d : Int64
} derive(Eq, Debug)

///|
/// Rotation is anticlockwise in quarter turns. Mirror mode 0 exchanges top
/// and bottom; mode 1 exchanges left and right (HEIF mode, not the old axis).
pub(all) enum AvifDisplayTransform {
  Crop(AvifCleanAperture)
  Rotate(Int)
  Mirror(Int)
} derive(Eq, Debug)

///|
/// Owned metadata for a primary item or a selected picture track. Exif is the
/// item payload after its four-byte offset field; exif_tiff_offset locates TIFF
/// inside it. ICC/Exif/XMP are opaque bytes, not an ICC engine or an XML parser.
/// Transforms retain property-association order. Pixel aspect ratio is metadata
/// only; applying raster transforms does not resample nonsquare pixels.
pub struct AvifMetadata {
  primary_item_id : Int?
  track_id : Int?
  icc : FixedArray[Byte]?
  exif : FixedArray[Byte]?
  exif_tiff_offset : Int?
  xmp : FixedArray[Byte]?
  pixel_aspect_ratio : (Int64, Int64)?
  transforms : FixedArray[AvifDisplayTransform]
}

///|
priv struct AvifDisplayMap {
  width : Int
  height : Int
  xx : Int
  xy : Int
  yx : Int
  yy : Int
  tx : Int
  ty : Int
}

///|
fn avif_crop_rect(
  crop : AvifCleanAperture,
  width : Int,
  height : Int,
) -> Result[(Int, Int, Int, Int), DecodeError] {
  for
    value in [
      crop.width_n,
      crop.width_d,
      crop.height_n,
      crop.height_d,
      crop.horizontal_d,
      crop.vertical_d,
    ] {
    if value <= 0L || value > 4294967295L {
      return Err(
        decode_error(
          InvalidData,
          "clap",
          "Aperture sizes and denominators must be positive uint32 values",
        ),
      )
    }
  }
  if crop.horizontal_n < -2147483648L ||
    crop.horizontal_n > 2147483647L ||
    crop.vertical_n < -2147483648L ||
    crop.vertical_n > 2147483647L {
    return Err(
      decode_error(
        InvalidData,
        "clap",
        "Aperture offset numerator is outside int32",
      ),
    )
  }
  if crop.width_n % crop.width_d != 0L ||
    crop.height_n % crop.height_d != 0L ||
    crop.horizontal_n * 2L % crop.horizontal_d != 0L ||
    crop.vertical_n * 2L % crop.vertical_d != 0L {
    return Err(
      decode_error(
        Unsupported,
        "clap",
        "Fractional aperture requires resampling; metadata remains available",
      ),
    )
  }
  let w = crop.width_n / crop.width_d
  let h = crop.height_n / crop.height_d
  let x2 = width.to_int64() - w + crop.horizontal_n * 2L / crop.horizontal_d
  let y2 = height.to_int64() - h + crop.vertical_n * 2L / crop.vertical_d
  if x2 % 2L != 0L || y2 % 2L != 0L {
    return Err(
      decode_error(
        Unsupported,
        "clap",
        "Aperture origin is not an integer pixel",
      ),
    )
  }
  let x = x2 / 2L
  let y = y2 / 2L
  if w > width.to_int64() ||
    h > height.to_int64() ||
    x < 0L ||
    y < 0L ||
    x > width.to_int64() - w ||
    y > height.to_int64() - h {
    return Err(
      decode_error(InvalidData, "clap", "Aperture is outside the image"),
    )
  }
  Ok((x.to_int(), y.to_int(), w.to_int(), h.to_int()))
}

///|
fn avif_display_map(
  metadata : AvifMetadata,
  width : Int,
  height : Int,
) -> Result[AvifDisplayMap, DecodeError] {
  if width <= 0 || height <= 0 || width > MAX_IMAGE_PIXELS / height {
    return Err(decode_error(InvalidData, "display", "Invalid image dimensions"))
  }
  let mut map : AvifDisplayMap = {
    width,
    height,
    xx: 1,
    xy: 0,
    yx: 0,
    yy: 1,
    tx: 0,
    ty: 0,
  }
  for transform in metadata.transforms {
    match transform {
      Crop(crop) => {
        let (x, y, w, h) = match avif_crop_rect(crop, map.width, map.height) {
          Ok(rect) => rect
          Err(error) => return Err(error)
        }
        map = {
          ..map,
          width: w,
          height: h,
          tx: map.tx + map.xx * x + map.xy * y,
          ty: map.ty + map.yx * x + map.yy * y,
        }
      }
      Rotate(turns) => {
        if turns < 0 || turns > 3 {
          return Err(
            decode_error(InvalidData, "irot", "Rotation must be in 0..3"),
          )
        }
        for _ in 0..
        if mode == 0 {
          map = {
            ..map,
            xy: -map.xy,
            yy: -map.yy,
            tx: map.tx + map.xy * (map.height - 1),
            ty: map.ty + map.yy * (map.height - 1),
          }
        } else if mode == 1 {
          map = {
            ..map,
            xx: -map.xx,
            yx: -map.yx,
            tx: map.tx + map.xx * (map.width - 1),
            ty: map.ty + map.yx * (map.width - 1),
          }
        } else {
          return Err(
            decode_error(InvalidData, "imir", "Mirror mode must be 0 or 1"),
          )
        }
    }
  }
  Ok(map)
}

///|
/// Validate the transform chain and report resulting raster dimensions.
pub fn AvifMetadata::display_size(
  self : AvifMetadata,
  width : Int,
  height : Int,
) -> Result[(Int, Int), DecodeError] {
  match avif_display_map(self, width, height) {
    Ok(map) => Ok((map.width, map.height))
    Err(error) => Err(error)
  }
}

///|
fn[T] avif_display_pixels(
  metadata : AvifMetadata,
  pixels : FixedArray[T],
  width : Int,
  height : Int,
) -> Result[(Int, Int, FixedArray[T]), DecodeError] {
  let map = match avif_display_map(metadata, width, height) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  if pixels.length() != width * height * 4 {
    return Err(
      decode_error(
        InvalidData,
        "display",
        "RGBA buffer size does not match the image",
      ),
    )
  }
  let output = FixedArray::makei(map.width * map.height * 4, i => {
    let x = i / 4 % map.width
    let y = i / 4 / map.width
    let sx = map.xx * x + map.xy * y + map.tx
    let sy = map.yx * x + map.yy * y + map.ty
    pixels[(sy * width + sx) * 4 + i % 4]
  })
  Ok((map.width, map.height, output))
}

///|
/// Explicit, exact raster permutation/crop. Color and alpha move together;
/// ICC, Exif orientation and pixel aspect ratio are not applied implicitly.
/// The result owns a new buffer even for an empty transform chain.
pub fn AvifMetadata::apply_rgba16(
  self : AvifMetadata,
  image : Image16,
) -> Result[Image16, DecodeError] {
  match avif_display_pixels(self, image.data, image.width, image.height) {
    Ok((width, height, data)) => Ok({ width, height, data, })
    Err(error) => Err(error)
  }
}

///|
pub fn AvifMetadata::apply_rgba8(
  self : AvifMetadata,
  image : Image,
) -> Result[Image, DecodeError] {
  match avif_display_pixels(self, image.data, image.width, image.height) {
    Ok((width, height, data)) => Ok({ width, height, data, })
    Err(error) => Err(error)
  }
}