// SPDX-License-Identifier: Apache-2.0
// Copyright © 2025 Au-Zone Technologies. All Rights Reserved.

//! CDR (Common Data Representation) serialization and deserialization support.
//!
//! This module provides serialize/deserialize functions for all schema types
//! using ROS2 CDR encoding format.

use serde::{Deserialize, Serialize};

/// Error type for serialization/deserialization operations
#[derive(Debug)]
pub enum Error {
    /// CDR serialization error
    Serialization(cdr::Error),
    /// CDR deserialization error
    Deserialization(cdr::Error),
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Error::Serialization(e) => write!(f, "Serialization error: {}", e),
            Error::Deserialization(e) => write!(f, "Deserialization error: {}", e),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Error::Serialization(e) => Some(e),
            Error::Deserialization(e) => Some(e),
        }
    }
}

/// Serialize a message to CDR format
///
/// # Arguments
/// * `msg` - The message to serialize (must implement `Serialize`)
///
/// # Returns
/// * `Ok(Vec<u8>)` - The serialized message as bytes
/// * `Err(Error)` - Serialization error
///
/// # Example
/// ```
/// use edgefirst_schemas::std_msgs::Header;
/// use edgefirst_schemas::builtin_interfaces::Time;
/// use edgefirst_schemas::serde_cdr::serialize;
///
/// let header = Header {
///     stamp: Time { sec: 0, nanosec: 0 },
///     frame_id: "camera".to_string(),
/// };
/// let bytes = serialize(&header).unwrap();
/// ```
pub fn serialize<T: Serialize>(msg: &T) -> Result<Vec<u8>, Error> {
    cdr::serialize::<_, _, cdr::CdrLe>(msg, cdr::size::Infinite).map_err(Error::Serialization)
}

/// Deserialize a message from CDR format
///
/// # Arguments
/// * `bytes` - The serialized message bytes
///
/// # Returns
/// * `Ok(T)` - The deserialized message
/// * `Err(Error)` - Deserialization error
///
/// # Example
/// ```
/// use edgefirst_schemas::std_msgs::Header;
/// use edgefirst_schemas::builtin_interfaces::Time;
/// use edgefirst_schemas::serde_cdr::{serialize, deserialize};
///
/// let header = Header {
///     stamp: Time { sec: 0, nanosec: 0 },
///     frame_id: "camera".to_string(),
/// };
/// let bytes = serialize(&header).unwrap();
/// let deserialized: Header = deserialize(&bytes).unwrap();
/// assert_eq!(header, deserialized);
/// ```
pub fn deserialize<'a, T: Deserialize<'a>>(bytes: &'a [u8]) -> Result<T, Error> {
    cdr::deserialize(bytes).map_err(Error::Deserialization)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::builtin_interfaces::Time;
    use crate::std_msgs::Header;

    #[test]
    fn serialize_deserialize_roundtrip() {
        // Test with data and empty strings
        let cases = [
            Header {
                stamp: Time::new(42, 123456789),
                frame_id: "test_frame".to_string(),
            },
            Header {
                stamp: Time::new(0, 0),
                frame_id: String::new(),
            },
        ];
        for header in &cases {
            let bytes = serialize(header).unwrap();
            assert_eq!(*header, deserialize::<Header>(&bytes).unwrap());
        }
    }

    #[test]
    fn error_display() {
        // Verify error formatting works
        let bad_bytes = &[0u8; 1];
        let result: Result<Header, _> = deserialize(bad_bytes);
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(err.to_string().contains("Deserialization error"));
    }

    #[test]
    fn error_source() {
        // Verify Error::source() returns the underlying CDR error
        use std::error::Error as StdError;

        let bad_bytes = &[0u8; 1];
        let result: Result<Header, _> = deserialize(bad_bytes);
        let err = result.unwrap_err();

        // source() should return Some pointing to the underlying cdr::Error
        let source = err.source();
        assert!(
            source.is_some(),
            "Error::source() should return the underlying error"
        );

        // The source should be a cdr::Error (we can't match on it directly, but
        // we can verify it exists and has a description)
        let source_str = source.unwrap().to_string();
        assert!(
            !source_str.is_empty(),
            "Source error should have a description"
        );
    }
}
