///|
/// Compression algorithms for the Compressed Data packet
/// (RFC 9580 §9.4).
pub(all) enum CompressedAlgo {
  Uncompressed
  Zip
  Zlib
  BZip2
} derive(Eq, Debug)

///|
pub fn CompressedAlgo::number(self : CompressedAlgo) -> Int32 {
  match self {
    Uncompressed => 0
    Zip => 1
    Zlib => 2
    BZip2 => 3
  }
}

///|
pub fn compressed_algo_of_number(n : Int32) -> CompressedAlgo? {
  match n {
    0 => Some(Uncompressed)
    1 => Some(Zip)
    2 => Some(Zlib)
    3 => Some(BZip2)
    _ => None
  }
}

///|
/// Raw DEFLATE (RFC 1951) — the ZIP algorithm of the Compressed packet.
pub fn zip_compress(data : BytesView) -> Bytes {
  @flate.deflate_all(data.to_owned())
}

///|
/// Raw DEFLATE decompression. The decompression end must be complete:
/// incoming mail may arrive compressed with dynamic Huffman tables.
pub fn zip_decompress(data : BytesView) -> Bytes raise {
  @flate.inflate_all(data.to_owned()) catch {
    _ => @error.fail("compress: inflate failed")
  }
}

///|
/// ZLIB (RFC 1950) wrapper — algorithm 2 of the Compressed packet.
pub fn zlib_compress(data : BytesView) -> Bytes {
  @zlib.compress(data)
}

///|
pub fn zlib_decompress(data : BytesView) -> Bytes raise {
  @zlib.decompress(data) catch {
    _ => @error.fail("compress: zlib inflate failed")
  }
}

///|
pub extend CompressedAlgo with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend CompressedAlgo with Eq::{not_equal, equal}

///|
/// Raw DEFLATE decompression with a hard output cap (M4): flate's
/// limited inflater stops (raising) once the cap is exceeded, so the
/// bomb never materializes.
pub fn zip_decompress_capped(data : BytesView, max_out : Int32) -> Bytes raise {
  @flate.inflate_all(data, max_output=max_out) catch {
    _ => @error.fail("compress: inflate failed (limit \{max_out} octets)")
  }
}

///|
/// ZLIB decompression with a hard output cap (M4): the streaming decoder
/// runs with bounded output windows, so total allocation stays at the cap
/// instead of materializing the payload first. Checksum and framing stay
/// with the decoder.
pub fn zlib_decompress_capped(data : BytesView, max_out : Int32) -> Bytes raise {
  guard max_out >= 0 else { @error.fail("compress: bad output cap") }
  let dec = @zlib.Decoder::Decoder()
  let out : Array[Byte] = []
  let mut pos = 0
  for ;; {
    let chunk = FixedArray::make(32768, b'\x00')
    let status = dec.step(
      data.exact_view()[pos:data.length()],
      chunk.mut_view(),
      end=true,
    ) catch {
      ZlibError(_, msg) => @error.fail("compress: zlib inflate failed: \{msg}")
    }
    pos = pos + dec.last_consumed()
    let produced = dec.last_produced()
    if out.length() + produced > max_out {
      @error.fail(
        "compress: decompressed output exceeds the \{max_out}-octet limit",
      )
    }
    for i in 0.. return Bytes::from_array(out)
      NeedMoreInput => @error.fail("compress: truncated zlib stream")
      NeedMoreOutput => continue
    }
  }
}