///|
let png_signature = b"\x89PNG\r\n\x1a\n"

///|
fn validate_chunk_crc(
  kind : BytesView,
  payload : BytesView,
  expected : Int,
) -> Bool {
  let crc = @checksum.Crc32::new()
  crc.update(kind[:])
  crc.update(payload[:])
  crc.finish().reinterpret_as_int() == expected
}

///|
fn inflate_png_idat(input : Bytes, limit : Int) -> Bytes raise ImageError {
  let decoder = @zlib.Decoder::new()
  let output = Buffer()
  let scratch = FixedArray::make(32768, b'\x00')
  let mut position = 0
  while !decoder.is_finished() {
    let _ = decoder.step(input[position:], scratch.mut_view(), end=true) catch {
      _ => raise ImageError(InvalidInput, "PNG IDAT zlib stream is invalid")
    }
    let consumed = decoder.last_consumed()
    let produced = decoder.last_produced()
    if output.length() > limit - produced {
      raise ImageError(
        ResourceLimit,
        "PNG decompressed data exceeds image bounds",
      )
    }
    for i in 0.. Bool {
  match transparency {
    Some(key) => key.length() == 2 && key[0] == b'\x00' && key[1] == sample
    None => false
  }
}

///|
fn transparent_rgb(
  red : Byte,
  green : Byte,
  blue : Byte,
  transparency : Bytes?,
) -> Bool {
  match transparency {
    Some(key) =>
      key.length() == 6 &&
      key[0] == b'\x00' &&
      key[1] == red &&
      key[2] == b'\x00' &&
      key[3] == green &&
      key[4] == b'\x00' &&
      key[5] == blue
    None => false
  }
}

///|
/// Decode a non-interlaced, 8-bit PNG into RGBA8. Every chunk checksum and the
/// zlib Adler-32 checksum are verified before returning pixels.
pub fn decode_png(
  input : Bytes,
  limits? : DecodeLimits = default_decode_limits(),
) -> Image raise ImageError {
  if input.length() > limits.max_input_bytes {
    raise ImageError(ResourceLimit, "PNG input exceeds max_input_bytes")
  }
  if input.length() < 8 || input[:8] != png_signature[:] {
    raise ImageError(InvalidInput, "not a PNG signature")
  }
  let mut offset = 8
  let mut width = 0
  let mut height = 0
  let mut color_type = -1
  let mut channels = 0
  let mut got_ihdr = false
  let mut got_iend = false
  let mut got_idat = false
  let mut got_palette = false
  let mut palette : Bytes? = None
  let mut transparency : Bytes? = None
  let idat = Buffer()
  while offset < input.length() {
    if input.length() - offset < 12 {
      raise ImageError(InvalidInput, "truncated PNG chunk")
    }
    let length = u32_be(input, offset)
    if length < 0 || length > input.length() - offset - 12 {
      raise ImageError(InvalidInput, "invalid PNG chunk length")
    }
    let kind = input[offset + 4:offset + 8]
    let payload = input[offset + 8:offset + 8 + length]
    let expected_crc = u32_be(input, offset + 8 + length)
    if !validate_chunk_crc(kind, payload, expected_crc) {
      raise ImageError(InvalidInput, "PNG chunk checksum mismatch")
    }
    offset = offset + 12 + length
    if kind == b"IHDR"[:] {
      if got_ihdr || length != 13 {
        raise ImageError(InvalidInput, "invalid PNG IHDR")
      }
      width = u32_be(payload, 0)
      height = u32_be(payload, 4)
      if width <= 0 ||
        height <= 0 ||
        width > limits.max_dimension ||
        height > limits.max_dimension ||
        width > limits.max_pixels / height {
        raise ImageError(ResourceLimit, "PNG dimensions exceed decode limits")
      }
      if payload[8].to_int() != 8 ||
        payload[10].to_int() != 0 ||
        payload[11].to_int() != 0 ||
        payload[12].to_int() != 0 {
        raise ImageError(
          UnsupportedFormat,
          "only non-interlaced 8-bit PNG is supported",
        )
      }
      color_type = payload[9].to_int()
      channels = match color_type {
        0 => 1
        2 => 3
        3 => 1
        4 => 2
        6 => 4
        _ => raise ImageError(UnsupportedFormat, "unsupported PNG color type")
      }
      got_ihdr = true
    } else if kind == b"PLTE"[:] {
      if !got_ihdr ||
        got_idat ||
        got_palette ||
        (color_type != 2 && color_type != 3 && color_type != 6) ||
        length == 0 ||
        length % 3 != 0 ||
        length > 768 {
        raise ImageError(InvalidInput, "invalid PNG palette")
      }
      palette = Some(Bytes::from_array(payload.to_array()))
      got_palette = true
    } else if kind == b"tRNS"[:] {
      if !got_ihdr ||
        got_idat ||
        transparency is Some(_) ||
        (color_type == 3 && !got_palette) {
        raise ImageError(InvalidInput, "PNG tRNS is out of order or duplicated")
      }
      let valid_length = match color_type {
        0 => length == 2
        2 => length == 6
        3 => length > 0 && length <= 256
        4 | 6 => false
        _ => false
      }
      if !valid_length {
        raise ImageError(InvalidInput, "invalid PNG tRNS chunk")
      }
      transparency = Some(Bytes::from_array(payload.to_array()))
    } else if kind == b"IDAT"[:] {
      if !got_ihdr || got_iend {
        raise ImageError(InvalidInput, "PNG IDAT is out of order")
      }
      got_idat = true
      idat.write_bytes(payload)
    } else if kind == b"IEND"[:] {
      if length != 0 || !got_ihdr {
        raise ImageError(InvalidInput, "invalid PNG IEND")
      }
      got_iend = true
      if offset != input.length() {
        raise ImageError(InvalidInput, "trailing data after PNG IEND")
      }
    } else if (kind[0].to_int() & 0x20) == 0 {
      raise ImageError(UnsupportedFormat, "unsupported PNG critical chunk")
    }
  }
  if !got_ihdr || !got_iend || idat.length() == 0 {
    raise ImageError(InvalidInput, "PNG is missing required chunks")
  }
  if color_type == 3 && palette is None {
    raise ImageError(InvalidInput, "indexed PNG is missing PLTE")
  }
  match (palette, transparency) {
    (Some(colors), Some(alpha)) if color_type == 3 &&
      alpha.length() > colors.length() / 3 =>
      raise ImageError(InvalidInput, "PNG tRNS exceeds PLTE entries")
    _ => ()
  }
  let row_bytes = width * channels
  let expected = height * (row_bytes + 1)
  let inflated = inflate_png_idat(idat.to_bytes(), expected)
  if inflated.length() != expected {
    raise ImageError(InvalidInput, "PNG scanline data length is invalid")
  }
  let scanlines = Array::make(height * row_bytes, b'\x00')
  let rgba = Array::make(width * height * 4, b'\x00')
  for y in 0.. 4 {
      raise ImageError(InvalidInput, "invalid PNG scanline filter")
    }
    for x in 0..= channels {
        scanlines[y * row_bytes + x - channels].to_int()
      } else {
        0
      }
      let above = if y > 0 {
        scanlines[(y - 1) * row_bytes + x].to_int()
      } else {
        0
      }
      let upper_left = if y > 0 && x >= channels {
        scanlines[(y - 1) * row_bytes + x - channels].to_int()
      } else {
        0
      }
      let value = match filter {
        0 => raw
        1 => (raw + left) & 0xff
        2 => (raw + above) & 0xff
        3 => (raw + ((left + above) >> 1)) & 0xff
        _ => (raw + paeth(left, above, upper_left)) & 0xff
      }
      scanlines[y * row_bytes + x] = value.to_byte()
    }
    for x in 0.. {
          let g = scanlines[src]
          rgba[dst] = g
          rgba[dst + 1] = g
          rgba[dst + 2] = g
          rgba[dst + 3] = if transparent_gray(g, transparency) {
            b'\x00'
          } else {
            b'\xff'
          }
        }
        2 => {
          let red = scanlines[src]
          let green = scanlines[src + 1]
          let blue = scanlines[src + 2]
          rgba[dst] = red
          rgba[dst + 1] = green
          rgba[dst + 2] = blue
          rgba[dst + 3] = if transparent_rgb(red, green, blue, transparency) {
            b'\x00'
          } else {
            b'\xff'
          }
        }
        3 => {
          let index = scanlines[src].to_int()
          let p = match palette {
            Some(value) => value
            None => raise ImageError(InvalidInput, "missing PNG palette")
          }
          if index * 3 + 2 >= p.length() {
            raise ImageError(InvalidInput, "PNG palette index is out of range")
          }
          rgba[dst] = p[index * 3]
          rgba[dst + 1] = p[index * 3 + 1]
          rgba[dst + 2] = p[index * 3 + 2]
          rgba[dst + 3] = match transparency {
            Some(alpha) =>
              if index < alpha.length() {
                alpha[index]
              } else {
                b'\xff'
              }
            None => b'\xff'
          }
        }
        4 => {
          let g = scanlines[src]
          rgba[dst] = g
          rgba[dst + 1] = g
          rgba[dst + 2] = g
          rgba[dst + 3] = scanlines[src + 1]
        }
        _ => {
          rgba[dst] = scanlines[src]
          rgba[dst + 1] = scanlines[src + 1]
          rgba[dst + 2] = scanlines[src + 2]
          rgba[dst + 3] = scanlines[src + 3]
        }
      }
    }
  }
  Image::new(width, height, Bytes::from_array(rgba))
}

///|
fn write_png_chunk(out : Buffer, kind : Bytes, payload : Bytes) -> Unit {
  write_u32_be(out, payload.length())
  out.write_bytes(kind[:])
  out.write_bytes(payload[:])
  let crc = @checksum.Crc32::new()
  crc.update(kind[:])
  crc.update(payload[:])
  write_u32_be(out, crc.finish().reinterpret_as_int())
}

///|
/// Encode image pixels as a deterministic RGBA PNG with no metadata chunks.
pub fn encode_png(
  image : Image,
  compression_level? : Int = 6,
) -> Bytes raise ImageError {
  let expected = image.width * image.height
  if image.width <= 0 ||
    image.height <= 0 ||
    expected > 0x3fffffff / 4 ||
    image.pixels.length() != expected * 4 {
    raise ImageError(InvalidDimensions, "invalid RGBA8 image")
  }
  let raw = Buffer()
  for y in 0..