// Protobuf varint (ULEB128) encoding and decoding.
// Reference: https://protobuf.dev/programming-guides/encoding/#varints

///|
/// Encode an unsigned 64-bit integer as a protobuf varint into the buffer.
pub fn encode_varint(buf : Buffer, value : UInt64) -> Unit {
  for v = value {
    if v < 128 {
      buf.write_byte(v.to_byte())
      break
    } else {
      buf.write_byte(((v & 127) | 128).to_byte())
      continue v >> 7
    }
  }
}

///|
/// Decode a protobuf varint from bytes at a given position.
/// Returns `(value, bytes_consumed)` or `None` if the varint is truncated.
pub fn decode_varint(bytes : Bytes, pos : Int) -> (UInt64, Int)? {
  if pos < 0 {
    return None
  }
  let mut result : UInt64 = 0
  let mut shift = 0
  // Max bytes needed for a 64-bit varint is ceil(64/7) = 10
  for i in 0..<10 {
    if pos + i >= bytes.length() {
      return None
    }
    let byte = bytes[pos + i].to_uint64()
    if byte < 128 {
      result = result | (byte << shift)
      return Some((result, i + 1))
    }
    result = result | ((byte & 127) << shift)
    shift += 7
  }
  None
}

///|
/// Zigzag encode a signed 32-bit integer to unsigned.
pub fn encode_zigzag32(value : Int) -> UInt {
  ((value << 1) ^ (value >> 31)).reinterpret_as_uint()
}

///|
/// Zigzag decode an unsigned 32-bit integer to signed.
pub fn decode_zigzag32(value : UInt) -> Int {
  (value >> 1).reinterpret_as_int() ^ -(value & 1).reinterpret_as_int()
}

///|
/// Zigzag encode a signed 64-bit integer to unsigned.
pub fn encode_zigzag64(value : Int64) -> UInt64 {
  ((value << 1) ^ (value >> 63)).reinterpret_as_uint64()
}

///|
/// Zigzag decode an unsigned 64-bit integer to signed.
pub fn decode_zigzag64(value : UInt64) -> Int64 {
  (value >> 1).reinterpret_as_int64() ^ -(value & 1).reinterpret_as_int64()
}

///|
/// Encode a signed 32-bit integer as a zigzag-encoded varint.
pub fn encode_sint32(buf : Buffer, value : Int) -> Unit {
  encode_varint(buf, encode_zigzag32(value).to_uint64())
}

///|
/// Encode a signed 64-bit integer as a zigzag-encoded varint.
pub fn encode_sint64(buf : Buffer, value : Int64) -> Unit {
  encode_varint(buf, encode_zigzag64(value))
}

///|
/// Decode a zigzag-encoded signed 32-bit varint from bytes at a given position.
/// Returns `(value, bytes_consumed)` or `None` if the varint is truncated.
pub fn decode_sint32(bytes : Bytes, pos : Int) -> (Int, Int)? {
  match decode_varint(bytes, pos) {
    None => None
    Some((value, consumed)) =>
      Some((decode_zigzag32(value.to_uint()), consumed))
  }
}

///|
/// Decode a zigzag-encoded signed 64-bit varint from bytes at a given position.
/// Returns `(value, bytes_consumed)` or `None` if the varint is truncated.
pub fn decode_sint64(bytes : Bytes, pos : Int) -> (Int64, Int)? {
  match decode_varint(bytes, pos) {
    None => None
    Some((value, consumed)) => Some((decode_zigzag64(value), consumed))
  }
}