///|
/// Native samples: U16 stores unshifted values; High16 left-aligns the original
/// sample bits. U8 is valid only for eight-bit pictures. No range/RGB conversion.
pub(all) enum PixelPacking {
  U8
  U16LE
  U16BE
  High16LE
  High16BE
} derive(Eq, Debug)

///|
fn output_buffer_fits(
  length : Int,
  offset : Int,
  stride : Int,
  row : Int,
  height : Int,
) -> Bool {
  offset >= 0 &&
  offset <= length &&
  row > 0 &&
  height > 0 &&
  stride >= row &&
  row <= length - offset &&
  height - 1 <= (length - offset - row) / stride
}

///|
/// Copy one native plane into a reusable byte buffer. row_bytes and offset are
/// in bytes; row padding and bytes outside the visible rectangle are untouched.
/// Invalid arguments/samples reject before any destination write. This export
/// does not expose or reuse the decoder's reference buffers.
pub fn Av1NativeFrame::copy_plane_into(
  self : Av1NativeFrame,
  plane : Int,
  destination : FixedArray[Byte],
  row_bytes : Int,
  packing : PixelPacking,
  offset? : Int = 0,
) -> Result[Unit, DecodeError] {
  if !av1_native_pixels_valid(self) ||
    plane < 0 ||
    plane >= self.planes.length() {
    return Err(
      decode_error(
        InvalidData,
        "pixels",
        "Invalid native picture or plane index",
      ),
    )
  }
  if packing == U8 && self.bit_depth != 8 {
    return Err(
      decode_error(
        Unsupported,
        "packing",
        "U8 packing cannot preserve a high-bit-depth sample",
      ),
    )
  }
  let bytes = if packing == U8 { 1 } else { 2 }
  let source = self.planes[plane]
  if !output_buffer_fits(
      destination.length(),
      offset,
      row_bytes,
      source.width * bytes,
      source.height,
    ) {
    return Err(
      decode_error(
        InvalidBuffer,
        "output",
        "Destination offset, stride or capacity is invalid",
      ),
    )
  }
  let shift = if packing == High16LE || packing == High16BE {
    16 - self.bit_depth
  } else {
    0
  }
  let big = packing == U16BE || packing == High16BE
  for y in 0..> 8 } else { value & 255 }).to_byte()
        destination[at + 1] = (if big { value & 255 } else { value >> 8 }).to_byte()
      }
    }
  }
  Ok(())
}

///|
fn av1_rgba16_into(
  color : Av1NativeFrame,
  alpha : Av1NativeFrame?,
  premultiplied : Bool,
  destination : FixedArray[UInt16],
  row_stride : Int,
  offset : Int,
) -> Result[Unit, DecodeError] {
  if !av1_rgba16_inputs_valid(color, alpha, premultiplied) {
    return Err(
      decode_error(
        InvalidData,
        "pixels",
        "Invalid native planes or unsupported color conversion",
      ),
    )
  }
  if !output_buffer_fits(
      destination.length(),
      offset,
      row_stride,
      color.width * 4,
      color.height,
    ) {
    return Err(
      decode_error(
        InvalidBuffer,
        "output",
        "Destination offset, stride or capacity is invalid",
      ),
    )
  }
  if !av1_write_rgba16(
      color, alpha, premultiplied, destination, row_stride, offset,
    ) {
    return Err(
      decode_error(
        InvalidData,
        "color",
        "Color conversion failed; destination may be partially written",
      ),
    )
  }
  Ok(())
}

///|
/// Write straight RGBA16 directly into caller storage. Stride and offset count
/// UInt16 channels, not bytes. Argument rejection leaves the destination intact;
/// an unexpected numerical conversion failure may leave a partial destination.
/// This removes output allocation; reconstruction/validation costs still apply.
pub fn Av1NativeFrame::to_rgba16_into(
  self : Av1NativeFrame,
  destination : FixedArray[UInt16],
  row_stride : Int,
  offset? : Int = 0,
) -> Result[Unit, DecodeError] {
  av1_rgba16_into(self, None, false, destination, row_stride, offset)
}

///|
/// Native alpha and premultiplication use the same precision and order as
/// to_rgba16(). Row padding and surrounding destination channels are untouched.
pub fn AvifNativeImage::to_rgba16_into(
  self : AvifNativeImage,
  destination : FixedArray[UInt16],
  row_stride : Int,
  offset? : Int = 0,
) -> Result[Unit, DecodeError] {
  av1_rgba16_into(
    self.color,
    self.alpha,
    self.premultiplied,
    destination,
    row_stride,
    offset,
  )
}

///|
/// Serialize straight UNORM16 channels, preserving the exact values.
pub fn Image16::copy_bytes_into(
  self : Image16,
  destination : FixedArray[Byte],
  row_bytes : Int,
  big_endian? : Bool = false,
  offset? : Int = 0,
) -> Result[Unit, DecodeError] {
  if self.width <= 0 ||
    self.height <= 0 ||
    self.width > MAX_IMAGE_PIXELS / self.height ||
    self.data.length() != self.width * self.height * 4 {
    return Err(
      decode_error(InvalidData, "pixels", "Invalid RGBA16 image shape"),
    )
  }
  if !output_buffer_fits(
      destination.length(),
      offset,
      row_bytes,
      self.width * 8,
      self.height,
    ) {
    return Err(
      decode_error(
        InvalidBuffer,
        "output",
        "Destination offset, stride or capacity is invalid",
      ),
    )
  }
  for y in 0..> 8 } else { value & 255 }).to_byte()
      destination[at + 1] = (if big_endian { value & 255 } else { value >> 8 }).to_byte()
    }
  }
  Ok(())
}