//! Proxy type definition for custom serialization/deserialization.
//!
//! A proxy allows a type to be serialized/deserialized through a different type.
//! This is useful when:
//! - The actual type doesn't have a natural serialization format
//! - You want to use a different representation for wire formats
//! - You need custom validation during deserialization

use crate::{PtrConst, PtrMut, PtrUninit, Shape};

#[cfg(feature = "alloc")]
use alloc::string::String;

/// Function type for proxy deserialization: converts FROM proxy type INTO target type.
///
/// # Safety
/// - `proxy_ptr` must point to a valid, initialized value of the proxy type
/// - `target_ptr` must point to uninitialized memory suitable for the target type
///
/// On success, returns `Ok(PtrMut)` pointing to the initialized target value.
/// On failure, returns `Err(String)` with an error message.
#[cfg(feature = "alloc")]
pub type ProxyConvertInFn =
    unsafe fn(proxy_ptr: PtrConst, target_ptr: PtrUninit) -> Result<PtrMut, String>;

/// Function type for proxy serialization: converts FROM target type INTO proxy type.
///
/// # Safety
/// - `target_ptr` must point to a valid, initialized value of the target type
/// - `proxy_ptr` must point to uninitialized memory suitable for the proxy type
///
/// On success, returns `Ok(PtrMut)` pointing to the initialized proxy value.
/// On failure, returns `Err(String)` with an error message.
#[cfg(feature = "alloc")]
pub type ProxyConvertOutFn =
    unsafe fn(target_ptr: PtrConst, proxy_ptr: PtrUninit) -> Result<PtrMut, String>;

/// Definition of a proxy type for custom serialization/deserialization.
///
/// A `ProxyDef` specifies how to convert between a target type and a proxy type.
/// This can be used at both the container level (on a struct/enum) and the field level.
///
/// # Container-level proxy
///
/// When applied to a struct or enum with `#[facet(proxy = ProxyType)]`:
/// - Serialization: target → proxy via `convert_out`
/// - Deserialization: proxy → target via `convert_in`
///
/// # Field-level proxy
///
/// When applied to a field with `#[facet(proxy = ProxyType)]`:
/// - The field is serialized/deserialized through the proxy type
/// - Requires `TryFrom<ProxyType> for FieldType` and `TryFrom<&FieldType> for ProxyType`
///
/// # Example (generated by derive macro)
///
/// ```ignore
/// ProxyDef {
///     shape: <ProxyType as Facet>::SHAPE,
///     convert_in: Self::__facet_proxy_convert_in,
///     convert_out: Self::__facet_proxy_convert_out,
/// }
/// ```
#[cfg(feature = "alloc")]
#[derive(Clone, Copy)]
pub struct ProxyDef {
    /// The shape of the proxy type.
    pub shape: &'static Shape,

    /// Converts from proxy type to target type (deserialization).
    ///
    /// Called when deserializing: first deserialize into proxy, then convert to target.
    pub convert_in: ProxyConvertInFn,

    /// Converts from target type to proxy type (serialization).
    ///
    /// Called when serializing: convert target to proxy, then serialize proxy.
    pub convert_out: ProxyConvertOutFn,
}

#[cfg(feature = "alloc")]
impl core::fmt::Debug for ProxyDef {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("ProxyDef")
            .field("shape", &self.shape)
            .field("convert_in", &"<fn>")
            .field("convert_out", &"<fn>")
            .finish()
    }
}

/// A format-specific proxy definition.
///
/// This associates a format namespace (like "xml" or "json") with a proxy definition.
/// Used to support `#[facet(xml::proxy = XmlWrapper)]` style attributes where different
/// formats need different proxy types for the same field or container.
///
/// # Resolution order
///
/// When serializing/deserializing, proxies are resolved in this order:
/// 1. Format-specific proxy (e.g., `xml::proxy` when deserializing XML)
/// 2. Format-agnostic proxy (`proxy`)
/// 3. Normal serialization/deserialization (no proxy)
///
/// # Example
///
/// ```ignore
/// #[derive(Facet)]
/// struct MyType {
///     #[facet(xml::proxy = XmlParameterProxy)]
///     #[facet(json::proxy = JsonParameterProxy)]
///     #[facet(proxy = DefaultProxy)]  // fallback for other formats
///     parameter: Parameter,
/// }
/// ```
#[cfg(feature = "alloc")]
#[derive(Clone, Copy, Debug)]
pub struct FormatProxy {
    /// The format namespace (e.g., "xml", "json").
    pub format: &'static str,

    /// The proxy definition for this format.
    pub proxy: &'static ProxyDef,
}
