//! Canonical encoding and decoding for BigInt.
//!
//! Binary format: [sign: u8][limb_count: u32 LE][limbs: u64[] LE]
//!
//! All fields are encoded little-endian. The encoding is canonical,
//! meaning exactly one valid encoding exists per integer value.

use crate::error::{BigIntError, Result};
use crate::limbs::{Limb, canonicalize, is_zero};
use crate::types::{BigInt, BigIntCore};

#[cfg(feature = "alloc")]
/// Encode a BigInt to canonical binary format.
///
/// Format: [sign: u8][limb_count: u32 LE][limbs: u64[] LE]
pub fn encode(bi: &BigInt) -> alloc::vec::Vec<u8> {
    let limbs = bi.limbs();
    let canonical_limbs = {
        let mut limbs_copy = limbs.to_vec();
        let len = canonicalize(&mut limbs_copy);
        limbs_copy.truncate(len);
        limbs_copy
    };

    let limb_count = canonical_limbs.len() as u32;
    let sign_byte = if bi.sign() { 1u8 } else { 0u8 };

    // Calculate total size: 1 (sign) + 4 (limb_count) + 8 * limb_count (limbs)
    let mut result = alloc::vec::Vec::with_capacity(1 + 4 + (8 * canonical_limbs.len()));

    // Write sign byte
    result.push(sign_byte);

    // Write limb count (little-endian u32)
    result.extend_from_slice(&limb_count.to_le_bytes());

    // Write limbs (little-endian u64 each)
    for &limb in &canonical_limbs {
        result.extend_from_slice(&limb.to_le_bytes());
    }

    result
}

#[cfg(feature = "alloc")]
/// Decode a BigInt from canonical binary format.
///
/// Validates that the encoding is canonical:
/// - Zero must have sign=0, n=1, limbs\[0\]=0
/// - No leading zeros for nonzero values
/// - Sign consistency
pub fn decode(data: &[u8]) -> Result<BigInt> {
    if data.len() < 5 {
        return Err(BigIntError::InvalidEncoding);
    }

    // Read sign byte
    let sign_byte = data[0];
    if sign_byte > 1 {
        return Err(BigIntError::InvalidEncoding);
    }
    let sign = sign_byte != 0;

    // Read limb count (little-endian u32)
    let limb_count_bytes = data[1..5]
        .try_into()
        .map_err(|_| BigIntError::InvalidEncoding)?;
    let limb_count = u32::from_le_bytes(limb_count_bytes) as usize;

    // Validate limb count
    if limb_count == 0 || limb_count > crate::gas::MAX_LIMBS {
        return Err(BigIntError::InvalidEncoding);
    }

    // Calculate expected data length
    let expected_len = 1 + 4 + (8 * limb_count);
    if data.len() < expected_len {
        return Err(BigIntError::InvalidEncoding);
    }

    // Read limbs
    let mut limbs = alloc::vec::Vec::with_capacity(limb_count);
    for i in 0..limb_count {
        let offset = 5 + (i * 8);
        let limb_bytes = data[offset..offset + 8]
            .try_into()
            .map_err(|_| BigIntError::InvalidEncoding)?;
        let limb = Limb::from_le_bytes(limb_bytes);
        limbs.push(limb);
    }

    // Validate canonical form
    // 1. Zero: sign=0, n=1, limbs[0]=0
    let is_zero_value = is_zero(&limbs);
    if is_zero_value {
        if sign {
            return Err(BigIntError::InvalidEncoding); // Negative zero forbidden
        }
        if limb_count != 1 || limbs[0] != 0 {
            return Err(BigIntError::InvalidEncoding);
        }
    } else {
        // 2. No leading zeros: limbs[n-1] ≠ 0
        if limbs[limb_count - 1] == 0 {
            return Err(BigIntError::InvalidEncoding);
        }

        // 3. Sign consistency: if sign=1, value must be negative
        // (We can't verify this without knowing the actual value, but we check
        // that the encoding is minimal)
    }

    // Create BigInt
    let max_limbs = limb_count.max(crate::gas::MAX_LIMBS);
    let mut result = BigInt::from_limbs(&limbs, max_limbs)?;
    result.set_sign(sign);

    // Final canonicalization check
    result.canonicalize()?;

    Ok(result)
}

#[cfg(all(test, feature = "alloc"))]
mod tests {
    use super::*;
    use alloc::vec;

    #[test]
    fn test_encode_zero() {
        let bi = BigInt::from_u64(0, 10);
        let encoded = encode(&bi);
        assert_eq!(encoded[0], 0); // sign
        assert_eq!(u32::from_le_bytes(encoded[1..5].try_into().unwrap()), 1); // limb_count
        assert_eq!(u64::from_le_bytes(encoded[5..13].try_into().unwrap()), 0); // limb[0]
    }

    #[test]
    fn test_encode_decode_roundtrip() {
        let bi = BigInt::from_u64(42, 10);
        let encoded = encode(&bi);
        let decoded = decode(&encoded).unwrap();
        assert_eq!(decoded.limbs()[0], 42);
        assert_eq!(decoded.sign(), bi.sign());
    }

    #[test]
    fn test_decode_invalid_negative_zero() {
        // Create invalid encoding: sign=1, but value is zero
        let mut invalid = vec![1u8]; // sign = 1 (negative)
        invalid.extend_from_slice(&1u32.to_le_bytes()); // limb_count = 1
        invalid.extend_from_slice(&0u64.to_le_bytes()); // limb[0] = 0
        assert!(decode(&invalid).is_err());
    }

    #[test]
    fn test_decode_invalid_leading_zeros() {
        // Create invalid encoding: nonzero value with leading zero
        let mut invalid = vec![0u8]; // sign = 0
        invalid.extend_from_slice(&2u32.to_le_bytes()); // limb_count = 2
        invalid.extend_from_slice(&42u64.to_le_bytes()); // limb[0] = 42
        invalid.extend_from_slice(&0u64.to_le_bytes()); // limb[1] = 0 (invalid!)
        assert!(decode(&invalid).is_err());
    }
}
