///| JS/WASM target: pure MoonBit compress/decompress implementations

///|
/// Compress data using zlib (fixed Huffman).
pub fn zlib_compress(data : Bytes) -> Bytes {
  let deflated = deflate_compress(data)
  let total_len = 2 + deflated.length() + 4
  let result : FixedArray[Byte] = FixedArray::make(total_len, b'\x00')
  result[0] = b'\x78'
  result[1] = b'\x01'
  for i = 0; i < deflated.length(); i = i + 1 {
    result[2 + i] = deflated[i]
  }
  let checksum = adler32(data)
  write_u32_be(result, 2 + deflated.length(), checksum)
  Bytes::from_array(result)
}

///|
/// Decompress a zlib stream (deflate with checksum) from the given offset.
/// Returns (decompressed_bytes, next_offset).
pub fn zlib_decompress_at(
  data : Bytes,
  start : Int,
) -> (Bytes, Int) raise ZlibError {
  if start < 0 || start + 2 > data.length() {
    raise ZlibError::InvalidData("Zlib data too short")
  }
  let cmf = data[start].to_int()
  let flg = data[start + 1].to_int()
  if (cmf & 0x0f) != 8 {
    raise ZlibError::InvalidData("Unsupported compression method")
  }
  if (cmf * 256 + flg) % 31 != 0 {
    raise ZlibError::InvalidData("Invalid zlib FLG checksum")
  }
  let fdict = (flg >> 5) & 1
  if fdict == 1 {
    raise ZlibError::InvalidData("Preset dictionary not supported")
  }
  let deflate_start = start + 2
  let (result, offset) = inflate_deflate_stream(data, deflate_start)
  if offset + 4 > data.length() {
    raise ZlibError::InvalidData("Missing Adler-32 checksum")
  }
  let stored_checksum = (data[offset].to_int() << 24) |
    (data[offset + 1].to_int() << 16) |
    (data[offset + 2].to_int() << 8) |
    data[offset + 3].to_int()
  let computed_checksum = adler32(result)
  if stored_checksum != computed_checksum {
    raise ZlibError::InvalidData(
      "Adler-32 mismatch: stored=\{stored_checksum}, computed=\{computed_checksum}",
    )
  }
  (result, offset + 4)
}

///|
/// Decompress a full zlib stream.
pub fn zlib_decompress(data : Bytes) -> Bytes raise ZlibError {
  let (result, offset) = zlib_decompress_at(data, 0)
  if offset != data.length() {
    raise ZlibError::InvalidData("Trailing data after zlib stream")
  }
  result
}

///|
/// Compress raw deflate stream (best compression).
pub fn deflate_compress(data : Bytes) -> Bytes {
  deflate_compress_best(data)
}

///|
/// Decompress raw deflate stream from the given offset.
pub fn deflate_decompress_at(
  data : Bytes,
  start : Int,
) -> (Bytes, Int) raise ZlibError {
  inflate_deflate_stream(data, start)
}

///|
/// Decompress a full raw deflate stream.
pub fn deflate_decompress(data : Bytes) -> Bytes raise ZlibError {
  let (result, offset) = deflate_decompress_at(data, 0)
  if offset != data.length() {
    raise ZlibError::InvalidData("Trailing data after deflate stream")
  }
  result
}

///|
/// Compress data using gzip (stored blocks).
pub fn gzip_compress(data : Bytes) -> Bytes {
  gzip_compress_stored(data)
}

///|
/// Decompress gzip stream from the given offset.
pub fn gzip_decompress_at(
  data : Bytes,
  start : Int,
) -> (Bytes, Int) raise ZlibError {
  if start < 0 || start + 10 > data.length() {
    raise ZlibError::InvalidData("Gzip data too short")
  }
  if data[start] != b'\x1f' || data[start + 1] != b'\x8b' {
    raise ZlibError::InvalidData("Invalid gzip header")
  }
  if data[start + 2] != b'\x08' {
    raise ZlibError::InvalidData("Unsupported gzip compression method")
  }
  let flags = data[start + 3].to_int()
  let mut offset = start + 10
  if (flags & 0x04) != 0 {
    let xlen = read_u16_le(data, offset)
    offset += 2
    if offset + xlen > data.length() {
      raise ZlibError::InvalidData("Unexpected end of gzip extra field")
    }
    offset += xlen
  }
  if (flags & 0x08) != 0 {
    while offset < data.length() && data[offset] != b'\x00' {
      offset += 1
    }
    if offset >= data.length() {
      raise ZlibError::InvalidData("Unexpected end of gzip filename")
    }
    offset += 1
  }
  if (flags & 0x10) != 0 {
    while offset < data.length() && data[offset] != b'\x00' {
      offset += 1
    }
    if offset >= data.length() {
      raise ZlibError::InvalidData("Unexpected end of gzip comment")
    }
    offset += 1
  }
  if (flags & 0x02) != 0 {
    if offset + 2 > data.length() {
      raise ZlibError::InvalidData("Unexpected end of gzip header CRC")
    }
    offset += 2
  }
  let (result, end_offset) = inflate_deflate_stream(data, offset)
  if end_offset + 8 > data.length() {
    raise ZlibError::InvalidData("Missing gzip trailer")
  }
  let stored_crc = read_u32_le(data, end_offset)
  let stored_size = read_u32_le(data, end_offset + 4)
  let computed_crc = crc32(result)
  if stored_crc != computed_crc {
    raise ZlibError::InvalidData("CRC32 mismatch in gzip trailer")
  }
  let size = Int::reinterpret_as_uint(result.length())
  if stored_size != size {
    raise ZlibError::InvalidData("ISIZE mismatch in gzip trailer")
  }
  (result, end_offset + 8)
}

///|
/// Decompress a full gzip stream.
pub fn gzip_decompress(data : Bytes) -> Bytes raise ZlibError {
  let (result, offset) = gzip_decompress_at(data, 0)
  if offset != data.length() {
    raise ZlibError::InvalidData("Trailing data after gzip stream")
  }
  result
}