///|
/// Convert fzip's little-endian footer representation to the canonical
/// Adler-32 value used by RFC 1950 DICTID fields.
fn footer_adler_to_rfc1950(value : UInt) -> UInt {
  ((value & 0x000000FFU) << 24) |
  ((value & 0x0000FF00U) << 8) |
  ((value & 0x00FF0000U) >> 8) |
  ((value & 0xFF000000U) >> 24)
}

///|
/// Write a Zlib DICTID in RFC 1950 network byte order.
fn write_zlib_dict_id(
  data : FixedArray[Byte],
  offset : Int,
  value : UInt,
) -> Unit {
  data[offset] = (value >> 24).to_byte()
  data[offset + 1] = (value >> 16).to_byte()
  data[offset + 2] = (value >> 8).to_byte()
  data[offset + 3] = value.to_byte()
}

///|
/// Read a Zlib DICTID in RFC 1950 network byte order.
fn read_zlib_dict_id(
  data : FixedArray[Byte],
  offset : Int,
) -> UInt raise FzipError {
  if offset < 0 || offset > data.length() - 4 {
    raise fzip_err(InvalidHeader, msg="invalid zlib dictionary ID")
  }
  (data[offset].to_uint() << 24) |
  (data[offset + 1].to_uint() << 16) |
  (data[offset + 2].to_uint() << 8) |
  data[offset + 3].to_uint()
}

///|
/// Write Zlib header
fn zlh(c : FixedArray[Byte], o : ZlibOptions) -> Unit {
  let lv = o.level
  let fl_val = if lv == 0 {
    0
  } else if lv < 6 {
    1
  } else if lv == 9 {
    3
  } else {
    2
  }
  c[0] = b'\x78' // CMF: CM=8, CINFO=7
  let has_dict = match o.dictionary {
    Some(_) => 32
    None => 0
  }
  c[1] = ((fl_val << 6) | has_dict).to_byte()
  // fix check bits
  let check = ((c[0].to_int() << 8) | c[1].to_int()) % 31
  c[1] = (c[1].to_int() | (31 - check)).to_byte()
  match o.dictionary {
    Some(dict) => {
      let h = AdlerState::new()
      h.push(dict)
      write_zlib_dict_id(c, 2, footer_adler_to_rfc1950(h.digest()))
    }
    None => ()
  }
}

///|
/// Parse Zlib header and return data start offset
fn zls(
  d : FixedArray[Byte],
  dictionary : FixedArray[Byte]?,
) -> Int raise FzipError {
  if d.length() < 2 {
    raise fzip_err(InvalidHeader, msg="invalid zlib data")
  }
  if (d[0].to_int() & 15) != 8 ||
    d[0].to_int() >> 4 > 7 ||
    ((d[0].to_int() << 8) | d[1].to_int()) % 31 != 0 {
    raise fzip_err(InvalidHeader, msg="invalid zlib data")
  }
  let dict_flag = (d[1].to_int() >> 5) & 1
  match (dict_flag, dictionary) {
    (1, None) =>
      raise fzip_err(InvalidHeader, msg="invalid zlib data: need dictionary")
    (0, Some(_)) =>
      raise fzip_err(
        InvalidHeader,
        msg="invalid zlib data: unexpected dictionary",
      )
    (1, Some(dict)) => {
      let expected_dict_id = read_zlib_dict_id(d, 2)
      if footer_adler_to_rfc1950(adler32(dict)) != expected_dict_id {
        raise fzip_err(InvalidChecksum, msg="zlib dictionary Adler-32 mismatch")
      }
      6
    }
    _ => 2
  }
}

///|
/// Compress data into a Zlib stream.
///
/// The output contains a Zlib header, a raw DEFLATE payload, and an Adler-32
/// checksum footer. If `ZlibOptions.dictionary` is set, the dictionary checksum
/// is written into the header and callers must provide the same dictionary when
/// decompressing.
pub fn zlib_sync(
  data : FixedArray[Byte],
  opts? : ZlibOptions = ZlibOptions::default(),
) -> FixedArray[Byte] {
  let a = AdlerState::new()
  let pre = match opts.dictionary {
    Some(_) => 6
    None => 2
  }
  let (d, len) = dopt(
    data,
    { level: opts.level, mem: opts.mem, dictionary: opts.dictionary },
    pre,
    4,
    None,
    adler_state=Some(a),
  )
  zlh(d, opts)
  wbytes(d, len - 4, a.digest().reinterpret_as_int())
  trim_buf(d, len)
}

///|
/// Decompress a Zlib stream.
///
/// The Zlib header is validated before inflating the inner DEFLATE payload. The
/// Adler-32 footer is verified by default and can be disabled with
/// `UnzlibOptions.verify_checksum` for trusted inputs. For dictionary streams,
/// fzip always verifies that the header DICTID matches the provided dictionary,
/// even when footer checksum verification is disabled.
pub fn unzlib_sync(
  data : FixedArray[Byte],
  opts? : UnzlibOptions = UnzlibOptions::default(),
) -> FixedArray[Byte] raise FzipError {
  let st = zls(data, opts.dictionary)
  let end = data.length() - 4
  if end < st {
    raise fzip_err(InvalidHeader, msg="invalid zlib data")
  }
  let adler_st : AdlerState? = if opts.verify_checksum {
    Some(AdlerState::new())
  } else {
    None
  }
  let (buf, len) = inflt(
    data,
    InflateState::new(2),
    opts.out,
    opts.dictionary,
    opts.max_input_size,
    opts.max_output_size,
    dat_off=st,
    dat_end=end,
    adler_state=adler_st,
  )
  match adler_st {
    Some(a_s) => {
      let expected_adler = b4(data, end)
      if a_s.digest() != expected_adler {
        raise fzip_err(InvalidChecksum, msg="zlib Adler-32 mismatch")
      }
    }
    None => ()
  }
  trim_buf(buf, len)
}