// Copyright 2026 TiKV Project Authors. Licensed under Apache-2.0.

use crate::{Error, Result};

pub(crate) fn put_varint32(dst: &mut Vec<u8>, value: u32) {
    put_varint64(dst, u64::from(value));
}

pub(crate) fn put_varint64(dst: &mut Vec<u8>, mut value: u64) {
    while value >= 0x80 {
        dst.push((value as u8) | 0x80);
        value >>= 7;
    }
    dst.push(value as u8);
}

pub(crate) fn put_zigzag64(dst: &mut Vec<u8>, value: i64) {
    put_varint64(dst, ((value as u64) << 1) ^ ((value >> 63) as u64));
}

pub(crate) fn read_varint32(input: &[u8], pos: &mut usize) -> Result<u32> {
    let value = read_varint64(input, pos)?;
    u32::try_from(value).map_err(|_| Error::Corruption("varint32 overflow".into()))
}

pub(crate) fn read_varint64(input: &[u8], pos: &mut usize) -> Result<u64> {
    let mut value = 0u64;
    for shift in (0..70).step_by(7) {
        let byte = *input
            .get(*pos)
            .ok_or_else(|| Error::Corruption("truncated varint".into()))?;
        *pos += 1;
        if shift == 63 && byte > 1 {
            return Err(Error::Corruption("varint64 overflow".into()));
        }
        value |= u64::from(byte & 0x7f) << shift;
        if byte & 0x80 == 0 {
            return Ok(value);
        }
    }
    Err(Error::Corruption("varint64 overflow".into()))
}

pub(crate) fn read_zigzag64(input: &[u8], pos: &mut usize) -> Result<i64> {
    let value = read_varint64(input, pos)?;
    Ok(((value >> 1) as i64) ^ -((value & 1) as i64))
}

pub(crate) fn common_prefix_len(left: &[u8], right: &[u8]) -> usize {
    left.iter()
        .zip(right)
        .position(|(left, right)| left != right)
        .unwrap_or(left.len().min(right.len()))
}

#[cfg(test)]
mod tests {
    use proptest::prelude::*;

    use super::*;

    proptest! {
        #[test]
        fn varint64_round_trip(value in any::<u64>()) {
            let mut encoded = Vec::new();
            put_varint64(&mut encoded, value);
            let mut pos = 0;
            prop_assert_eq!(read_varint64(&encoded, &mut pos).unwrap(), value);
            prop_assert_eq!(pos, encoded.len());
        }

        #[test]
        fn zigzag64_round_trip(value in any::<i64>()) {
            let mut encoded = Vec::new();
            put_zigzag64(&mut encoded, value);
            let mut pos = 0;
            prop_assert_eq!(read_zigzag64(&encoded, &mut pos).unwrap(), value);
            prop_assert_eq!(pos, encoded.len());
        }
    }
}
