///|
priv type ZlibHandle

///|
extern "C" fn zlib_new() -> ZlibHandle = "discord_gateway_zlib_new"

///|
#borrow(handle)
extern "C" fn zlib_status(handle : ZlibHandle) -> Int = "discord_gateway_zlib_status"

///|
#borrow(handle, input)
extern "C" fn zlib_inflate(handle : ZlibHandle, input : Bytes) -> Bytes = "discord_gateway_zlib_inflate"

///|
priv struct ZlibStreamInflater {
  handle : ZlibHandle
  pending : Array[Byte]
}

///|
fn ZlibStreamInflater::ZlibStreamInflater() -> ZlibStreamInflater raise InflateError {
  let handle = zlib_new()
  let status = zlib_status(handle)
  if status != 0 {
    raise InflateError(status~)
  }
  { handle, pending: [], }
}

///|
fn has_sync_flush_suffix(bytes : Array[Byte]) -> Bool {
  let len = bytes.length()
  len >= 4 &&
  bytes[len - 4] == 0x00 &&
  bytes[len - 3] == 0x00 &&
  bytes[len - 2] == 0xff &&
  bytes[len - 1] == 0xff
}

///|
impl Inflater for ZlibStreamInflater with fn push(self, bytes) {
  for byte in bytes {
    self.pending.push(byte)
  }
  if !has_sync_flush_suffix(self.pending) {
    return None
  }
  let compressed = Bytes::from_array(self.pending)
  self.pending.clear()
  let plain = zlib_inflate(self.handle, compressed)
  let status = zlib_status(self.handle)
  if status != 0 {
    raise InflateError(status~)
  }
  Some(@utf8.decode(plain) catch { _ => raise InflateError::InvalidUtf8 })
}

///|
fn new_zlib_stream_inflater() -> &Inflater raise {
  ZlibStreamInflater()
}

///|
/// Whether this backend can inflate Discord's `zlib-stream` transport
/// compression. True on native (zlib is loaded at runtime).
pub fn zlib_stream_supported() -> Bool {
  true
}