///|
/// The 61-entry HPACK static header table (RFC 7541, Appendix A). Entry `i`
/// (0-based here) carries HPACK index `i + 1`; use `hpack_static_entry` for the
/// 1-based RFC lookup. Values are empty for name-only entries.
let static_table : Array[(String, String)] = [
  (":authority", ""),
  (":method", "GET"),
  (":method", "POST"),
  (":path", "/"),
  (":path", "/index.html"),
  (":scheme", "http"),
  (":scheme", "https"),
  (":status", "200"),
  (":status", "204"),
  (":status", "206"),
  (":status", "304"),
  (":status", "400"),
  (":status", "404"),
  (":status", "500"),
  ("accept-charset", ""),
  ("accept-encoding", "gzip, deflate"),
  ("accept-language", ""),
  ("accept-ranges", ""),
  ("accept", ""),
  ("access-control-allow-origin", ""),
  ("age", ""),
  ("allow", ""),
  ("authorization", ""),
  ("cache-control", ""),
  ("content-disposition", ""),
  ("content-encoding", ""),
  ("content-language", ""),
  ("content-length", ""),
  ("content-location", ""),
  ("content-range", ""),
  ("content-type", ""),
  ("cookie", ""),
  ("date", ""),
  ("etag", ""),
  ("expect", ""),
  ("expires", ""),
  ("from", ""),
  ("host", ""),
  ("if-match", ""),
  ("if-modified-since", ""),
  ("if-none-match", ""),
  ("if-range", ""),
  ("if-unmodified-since", ""),
  ("last-modified", ""),
  ("link", ""),
  ("location", ""),
  ("max-forwards", ""),
  ("proxy-authenticate", ""),
  ("proxy-authorization", ""),
  ("range", ""),
  ("referer", ""),
  ("refresh", ""),
  ("retry-after", ""),
  ("server", ""),
  ("set-cookie", ""),
  ("strict-transport-security", ""),
  ("transfer-encoding", ""),
  ("user-agent", ""),
  ("vary", ""),
  ("via", ""),
  ("www-authenticate", ""),
]

///|
/// The 61-entry HPACK static header table (RFC 7541, Appendix A) as `(name,
/// value)` pairs in RFC index order, i.e. `result[0]` is index 1 (`:authority`)
/// and `result[60]` is index 61 (`www-authenticate`).
pub fn hpack_static_table() -> Array[(String, String)] {
  static_table
}

///|
/// Look up an HPACK static-table entry by its 1-based RFC index (`1..=61`),
/// returning `(name, value)` or `None` when the index is out of range.
pub fn hpack_static_entry(index : Int) -> (String, String)? {
  if index < 1 || index > static_table.length() {
    None
  } else {
    Some(static_table[index - 1])
  }
}

///|
/// Encode `value` as an HPACK integer with an `prefix_bits`-bit prefix (RFC 7541
/// §5.1). The high `8 - prefix_bits` bits of the first octet are left zero for
/// the caller to OR in any flag bits. Examples: `10` on a 5-bit prefix is
/// `[0x0A]`; `1337` on a 5-bit prefix is `[0x1F, 0x9A, 0x0A]`.
pub fn hpack_encode_int(value : Int, prefix_bits : Int) -> Bytes {
  let buf = Buffer()
  let max_prefix = (1 << prefix_bits) - 1
  if value < max_prefix {
    buf.write_byte(value.to_byte())
  } else {
    buf.write_byte(max_prefix.to_byte())
    let mut rest = value - max_prefix
    while rest >= 128 {
      buf.write_byte((rest % 128 + 128).to_byte())
      rest = rest / 128
    }
    buf.write_byte(rest.to_byte())
  }
  buf.to_bytes()
}

///|
/// Decode an HPACK integer with an `prefix_bits`-bit prefix from `data` starting
/// at `offset` (RFC 7541 §5.1), returning `(value, bytes_consumed)`. Any flag
/// bits above the prefix in the first octet are masked off and ignored.
pub fn hpack_decode_int(
  data : Bytes,
  offset : Int,
  prefix_bits : Int,
) -> (Int, Int) raise HpackError {
  if offset >= data.length() {
    raise HpackDecodeError("truncated HPACK integer prefix")
  }
  let max_prefix = (1 << prefix_bits) - 1
  let first = data[offset].to_int() & max_prefix
  if first < max_prefix {
    (first, 1)
  } else {
    // Accumulate in 64 bits and reject anything that would not fit a positive 32-bit
    // Int (RFC 7541 §5.1), so a truncated or over-long continuation raises instead of
    // reading past the buffer or overflowing.
    let mut value = max_prefix.to_int64()
    let mut shift = 0
    let mut consumed = 1
    for ;; {
      if offset + consumed >= data.length() {
        raise HpackDecodeError("truncated HPACK integer")
      }
      if shift >= 35 {
        raise HpackDecodeError("HPACK integer too long")
      }
      let octet = data[offset + consumed].to_int()
      consumed = consumed + 1
      value = value + (octet & 127).to_int64() * (1L << shift)
      shift = shift + 7
      if value > 0x7FFFFFFFL {
        raise HpackDecodeError("HPACK integer too large")
      }
      if (octet & 128) == 0 {
        break
      }
    }
    (value.to_int(), consumed)
  }
}

///|
/// Whether the string literal at `offset` is Huffman-coded, i.e. the `H` bit
/// (the top bit of the length octet) is set (RFC 7541 §5.2).
pub fn hpack_string_is_huffman(data : Bytes, offset : Int) -> Bool {
  (data[offset].to_int() & 0x80) != 0
}

///|
/// Encode `octets` as a non-Huffman HPACK string literal (RFC 7541 §5.2): the
/// length as a 7-bit-prefix integer with the `H` bit clear, followed by the raw
/// octets.
pub fn hpack_encode_string(octets : Bytes) -> Bytes {
  let buf = Buffer()
  buf.write_bytes(hpack_encode_int(octets.length(), 7))
  buf.write_bytes(octets)
  buf.to_bytes()
}

///|
/// Decode an HPACK string literal from `data` at `offset`, returning `(octets,
/// bytes_consumed)`. The length is read as a 7-bit-prefix integer (the `H` bit
/// is masked off); this is the inverse of `hpack_encode_string` for `H = 0`.
/// Use `hpack_string_is_huffman` first if the literal may be Huffman-coded, as
/// Huffman decoding is not applied here.
pub fn hpack_decode_string(
  data : Bytes,
  offset : Int,
) -> (Bytes, Int) raise HpackError {
  let (len, int_len) = hpack_decode_int(data, offset, 7)
  let start = offset + int_len
  // Bound the literal against the buffer without recomputing `start + len` (which
  // could wrap), so a length claiming more than remains raises instead of slicing OOB.
  if start > data.length() || len > data.length() - start {
    raise HpackDecodeError("HPACK string literal runs past the block")
  }
  (data[start:start + len].to_owned(), int_len + len)
}