//! Shared protobuf wire-format primitives — decode + encode.
//!
//! This module is the single source of truth for varint encoding/decoding,
//! proto string encoding/extraction, etc. All other modules should import
//! from here instead of rolling their own.

/// Decode a varint from a byte slice. Returns `(value, bytes_consumed)`.
///
/// This is the canonical decoder — all other modules should use this.
pub fn decode_varint(buf: &[u8]) -> Option<(u64, usize)> {
    let mut result: u64 = 0;
    let mut shift = 0u32;
    for (i, &byte) in buf.iter().enumerate() {
        if i >= 10 {
            return None; // Too many bytes for a varint
        }
        result |= ((byte & 0x7F) as u64) << shift;
        if byte & 0x80 == 0 {
            return Some((result, i + 1));
        }
        shift += 7;
        if shift >= 64 {
            return None;
        }
    }
    None
}

/// Encode a varint into an existing buffer.
pub fn encode_varint(buf: &mut Vec<u8>, mut val: u64) {
    loop {
        let byte = (val & 0x7F) as u8;
        val >>= 7;
        if val == 0 {
            buf.push(byte);
            break;
        }
        buf.push(byte | 0x80);
    }
}

/// Encode a string/bytes value as a protobuf length-delimited field.
///
/// Produces: `[tag(field_num, wire_type=2)] [len] [data]`
pub fn encode_proto_string(field_num: u32, data: &[u8]) -> Vec<u8> {
    let tag = (field_num << 3) | 2; // wire type 2 = length-delimited
    let mut buf = Vec::with_capacity(1 + 5 + data.len());
    encode_varint(&mut buf, tag as u64);
    encode_varint(&mut buf, data.len() as u64);
    buf.extend_from_slice(data);
    buf
}

/// Extract a string field from raw protobuf bytes by field number.
///
/// Walks top-level fields, skipping varints, 64-bit, 32-bit, and other
/// length-delimited fields until the target field number is found.
/// Only returns the first occurrence.
pub fn extract_proto_string(buf: &[u8], target_field: u32) -> Option<String> {
    let mut i = 0;
    while i < buf.len() {
        let (tag, consumed) = decode_varint(&buf[i..])?;
        i += consumed;
        let field_num = (tag >> 3) as u32;
        let wire_type = (tag & 0x07) as u8;

        match wire_type {
            0 => {
                // Varint — skip
                let (_, c) = decode_varint(&buf[i..])?;
                i += c;
            }
            1 => {
                // 64-bit fixed — skip 8 bytes
                if i + 8 > buf.len() {
                    return None;
                }
                i += 8;
            }
            2 => {
                // Length-delimited
                let (len, c) = decode_varint(&buf[i..])?;
                i += c;
                let len = len as usize;
                if i + len > buf.len() {
                    return None;
                }
                if field_num == target_field {
                    return String::from_utf8(buf[i..i + len].to_vec()).ok();
                }
                i += len;
            }
            5 => {
                // 32-bit fixed — skip 4 bytes
                if i + 4 > buf.len() {
                    return None;
                }
                i += 4;
            }
            _ => return None, // Unknown wire type
        }
    }
    None
}

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

    #[test]
    fn test_decode_varint_basic() {
        assert_eq!(decode_varint(&[0x00]), Some((0, 1)));
        assert_eq!(decode_varint(&[0x01]), Some((1, 1)));
        assert_eq!(decode_varint(&[0x7F]), Some((127, 1)));
        assert_eq!(decode_varint(&[0x80, 0x01]), Some((128, 2)));
        assert_eq!(decode_varint(&[0x96, 0x01]), Some((150, 2)));
        assert_eq!(decode_varint(&[0xAC, 0x02]), Some((300, 2)));
    }

    #[test]
    fn test_encode_decode_roundtrip() {
        for val in [0u64, 1, 127, 128, 300, 1026, u32::MAX as u64, u64::MAX] {
            let mut buf = Vec::new();
            encode_varint(&mut buf, val);
            let (decoded, consumed) = decode_varint(&buf).unwrap();
            assert_eq!(decoded, val, "roundtrip failed for {val}");
            assert_eq!(consumed, buf.len());
        }
    }

    #[test]
    fn test_encode_proto_string() {
        let result = encode_proto_string(1, b"hello");
        // tag(1,2) = 0x0A, len=5, h,e,l,l,o
        assert_eq!(result[0], 0x0A);
        assert_eq!(result[1], 0x05);
        assert_eq!(&result[2..], b"hello");
    }

    #[test]
    fn test_extract_proto_string() {
        // Build: field 1 = "abc", field 2 (varint) = 42, field 3 = "xyz"
        let mut buf = Vec::new();
        buf.extend_from_slice(&encode_proto_string(1, b"abc"));
        // field 2 varint 42: tag = (2<<3)|0 = 0x10, value = 0x2A
        buf.push(0x10);
        buf.push(0x2A);
        buf.extend_from_slice(&encode_proto_string(3, b"xyz"));

        assert_eq!(extract_proto_string(&buf, 1), Some("abc".to_string()));
        assert_eq!(extract_proto_string(&buf, 3), Some("xyz".to_string()));
        assert_eq!(extract_proto_string(&buf, 99), None);
    }
}
