// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use crate::buf::BufferMarker;
use crate::DataError;
use crate::DataLocale;
use crate::DynamicDataMarker;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
use core::fmt::Debug;
use core::marker::PhantomData;
#[cfg(feature = "alloc")]
use core::ops::Deref;
use yoke::cartable_ptr::CartableOptionPointer;
use yoke::*;

#[cfg(feature = "alloc")]
#[cfg(not(feature = "sync"))]
use alloc::rc::Rc as SelectedRc;
#[cfg(feature = "alloc")]
#[cfg(feature = "sync")]
use alloc::sync::Arc as SelectedRc;

/// A response object containing metadata about the returned data.
#[derive(Debug, Clone, PartialEq, Default)]
#[non_exhaustive]
pub struct DataResponseMetadata {
    /// The resolved locale of the returned data, if locale fallbacking was performed.
    pub locale: Option<DataLocale>,
    /// The format of the buffer for buffer-backed data, if known (for example, JSON).
    pub buffer_format: Option<crate::buf::BufferFormat>,
    /// An optional checksum. This can be used to ensure consistency across different markers.
    pub checksum: Option<u64>,
}

impl DataResponseMetadata {
    /// Sets the checksum.
    pub fn with_checksum(self, checksum: u64) -> Self {
        Self {
            checksum: Some(checksum),
            ..self
        }
    }
}

/// A container for data payloads returned from a data provider.
///
/// [`DataPayload`] is built on top of the [`yoke`] framework, which allows for cheap, zero-copy
/// operations on data via the use of self-references.
///
/// The type of the data stored in [`DataPayload`] is determined by the [`DynamicDataMarker`] type parameter.
///
/// ## Accessing the data
///
/// To get a reference to the data inside [`DataPayload`], use [`DataPayload::get()`]. If you need
/// to store the data for later use, you need to store the [`DataPayload`] itself, since `get` only
/// returns a reference with an ephemeral lifetime.
///
/// ## Mutating the data
///
/// To modify the data stored in a [`DataPayload`], use [`DataPayload::with_mut()`].
///
/// ## Transforming the data to a different type
///
/// To transform a [`DataPayload`] to a different type backed by the same data store (cart), use
/// [`DataPayload::map_project()`] or one of its sister methods.
///
/// # Cargo feature: `sync`
///
/// By default, the payload uses non-concurrent reference counting internally, and hence is neither
/// [`Sync`] nor [`Send`]; if these traits are required, the `sync` Cargo feature can be enabled.
///
/// # Examples
///
/// Basic usage, using the `HelloWorldV1` marker:
///
/// ```
/// use icu_provider::hello_world::*;
/// use icu_provider::prelude::*;
/// use std::borrow::Cow;
///
/// let payload = DataPayload::<HelloWorldV1>::from_owned(HelloWorld {
///     message: Cow::Borrowed("Demo"),
/// });
///
/// assert_eq!("Demo", payload.get().message);
/// ```
pub struct DataPayload<M: DynamicDataMarker>(pub(crate) DataPayloadInner<M>);

/// A container for data payloads with storage for something else.
///
/// The type parameter `O` is stored as part of the interior enum, leading to
/// better stack size optimization. `O` can be as large as the [`DataPayload`]
/// minus two words without impacting stack size.
///
/// # Examples
///
/// Create and use DataPayloadOr:
///
/// ```
/// use icu_locale_core::langid;
/// use icu_provider::hello_world::*;
/// use icu_provider::prelude::*;
/// use icu_provider::DataPayloadOr;
///
/// let response: DataResponse<HelloWorldV1> = HelloWorldProvider
///     .load(DataRequest {
///         id: DataIdentifierBorrowed::for_locale(&langid!("de").into()),
///         ..Default::default()
///     })
///     .expect("Loading should succeed");
///
/// let payload_some =
///     DataPayloadOr::<HelloWorldV1, ()>::from_payload(response.payload);
/// let payload_none = DataPayloadOr::<HelloWorldV1, ()>::from_other(());
///
/// assert_eq!(
///     payload_some.get(),
///     Ok(&HelloWorld {
///         message: "Hallo Welt".into()
///     })
/// );
/// assert_eq!(payload_none.get(), Err(&()));
/// ```
///
/// Stack size comparison:
///
/// ```
/// use core::mem::size_of;
/// use icu_provider::prelude::*;
/// use icu_provider::DataPayloadOr;
///
/// const W: usize = size_of::<usize>();
///
/// // Data struct is 3 words:
/// icu_provider::data_marker!(SampleV1, [usize; 3]);
///
/// // DataPayload adds a word for a total of 4 words:
/// assert_eq!(W * 4, size_of::<DataPayload<SampleV1>>());
///
/// // Option<DataPayload> balloons to 5 words:
/// assert_eq!(W * 5, size_of::<Option<DataPayload<SampleV1>>>());
///
/// // But, using DataPayloadOr is the same size as DataPayload:
/// assert_eq!(W * 4, size_of::<DataPayloadOr<SampleV1, ()>>());
///
/// // The largest optimized Other type is two words smaller than the DataPayload:
/// assert_eq!(W * 4, size_of::<DataPayloadOr<SampleV1, [usize; 1]>>());
/// assert_eq!(W * 4, size_of::<DataPayloadOr<SampleV1, [usize; 2]>>());
/// assert_eq!(W * 5, size_of::<DataPayloadOr<SampleV1, [usize; 3]>>());
/// ```
pub struct DataPayloadOr<M: DynamicDataMarker, O>(pub(crate) DataPayloadOrInner<M, O>);

pub(crate) enum DataPayloadInner<M: DynamicDataMarker> {
    Yoke(Yoke<M::DataStruct, CartableOptionPointer<CartInner>>),
    StaticRef(&'static M::DataStruct),
}

pub(crate) enum DataPayloadOrInner<M: DynamicDataMarker, O> {
    Yoke(Yoke<M::DataStruct, CartableOptionPointer<CartInner>>),
    Inner(DataPayloadOrInnerInner<M, O>),
}

pub(crate) enum DataPayloadOrInnerInner<M: DynamicDataMarker, O> {
    StaticRef(&'static M::DataStruct),
    Other(O),
}

/// The type of the "cart" that is used by [`DataPayload`].
///
/// This type is public but the inner cart type is private. To create a
/// [`Yoke`] with this cart, use [`Cart::try_make_yoke`]. Then, convert
/// it to a [`DataPayload`] with [`DataPayload::from_yoked_buffer`].
#[derive(Clone, Debug)]
#[allow(clippy::redundant_allocation)] // false positive, it's cheaper to wrap an existing Box in an Rc than to reallocate a huge Rc
pub struct Cart(#[allow(dead_code)] CartInner);

/// The actual cart type (private typedef).
#[cfg(feature = "alloc")]
pub(crate) type CartInner = SelectedRc<Box<[u8]>>;
#[cfg(not(feature = "alloc"))]
pub(crate) type CartInner = &'static ();

// Safety: Rc, Arc, and () are CloneableCart, and our impl delegates.
unsafe impl yoke::CloneableCart for Cart {}

#[cfg(feature = "alloc")]
impl Deref for Cart {
    type Target = Box<[u8]>;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
// Safety: both Rc and Arc are StableDeref, and our impl delegates.
#[cfg(feature = "alloc")]
unsafe impl stable_deref_trait::StableDeref for Cart {}

impl Cart {
    #[cfg(feature = "alloc")]
    /// Creates a `Yoke<Y, Option<Cart>>` from owned bytes by applying `f`.
    pub fn try_make_yoke<Y, F, E>(cart: Box<[u8]>, f: F) -> Result<Yoke<Y, Option<Self>>, E>
    where
        for<'a> Y: Yokeable<'a>,
        F: FnOnce(&[u8]) -> Result<<Y as Yokeable>::Output, E>,
    {
        Yoke::try_attach_to_cart(SelectedRc::new(cart), |b| f(b))
            // Safety: The cart is only wrapped, no data is leaked
            .map(|yoke| unsafe { yoke.replace_cart(Cart) })
            .map(Yoke::wrap_cart_in_option)
    }

    /// Helper function to convert `Yoke<Y, Option<Cart>>` to `Yoke<Y, Option<CartInner>>`.
    #[inline]
    pub(crate) fn unwrap_cart<Y>(yoke: Yoke<Y, Option<Cart>>) -> Yoke<Y, Option<CartInner>>
    where
        for<'a> Y: Yokeable<'a>,
    {
        // Safety: `Cart` has one field and we are removing it from the newtype,
        // and we are preserving it in the new cart, unwrapping it from the newtype.
        unsafe { yoke.replace_cart(|option_cart| option_cart.map(|cart| cart.0)) }
    }
}

impl<M> Debug for DataPayload<M>
where
    M: DynamicDataMarker,
    for<'a> &'a <M::DataStruct as Yokeable<'a>>::Output: Debug,
{
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        self.get().fmt(f)
    }
}

impl<M, O> Debug for DataPayloadOr<M, O>
where
    M: DynamicDataMarker,
    for<'a> &'a <M::DataStruct as Yokeable<'a>>::Output: Debug,
    O: Debug,
{
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        self.get()
            .map(|v| Debug::fmt(&v, f))
            .unwrap_or_else(|v| Debug::fmt(v, f))
    }
}

/// Cloning a DataPayload is generally a cheap operation.
/// See notes in the `Clone` impl for [`Yoke`].
///
/// # Examples
///
/// ```no_run
/// use icu_provider::hello_world::*;
/// use icu_provider::prelude::*;
///
/// let resp1: DataPayload<HelloWorldV1> = todo!();
/// let resp2 = resp1.clone();
/// ```
impl<M> Clone for DataPayload<M>
where
    M: DynamicDataMarker,
    for<'a> <M::DataStruct as Yokeable<'a>>::Output: Clone,
{
    fn clone(&self) -> Self {
        Self(match &self.0 {
            DataPayloadInner::Yoke(yoke) => DataPayloadInner::Yoke(yoke.clone()),
            DataPayloadInner::StaticRef(r) => DataPayloadInner::StaticRef(*r),
        })
    }
}

impl<M, O> Clone for DataPayloadOr<M, O>
where
    M: DynamicDataMarker,
    for<'a> <M::DataStruct as Yokeable<'a>>::Output: Clone,
    O: Clone,
{
    fn clone(&self) -> Self {
        Self(match &self.0 {
            DataPayloadOrInner::Yoke(yoke) => DataPayloadOrInner::Yoke(yoke.clone()),
            DataPayloadOrInner::Inner(DataPayloadOrInnerInner::StaticRef(r)) => {
                DataPayloadOrInner::Inner(DataPayloadOrInnerInner::StaticRef(*r))
            }
            DataPayloadOrInner::Inner(DataPayloadOrInnerInner::Other(o)) => {
                DataPayloadOrInner::Inner(DataPayloadOrInnerInner::Other(o.clone()))
            }
        })
    }
}

impl<M> PartialEq for DataPayload<M>
where
    M: DynamicDataMarker,
    for<'a> <M::DataStruct as Yokeable<'a>>::Output: PartialEq,
{
    fn eq(&self, other: &Self) -> bool {
        self.get() == other.get()
    }
}
impl<M, O> PartialEq for DataPayloadOr<M, O>
where
    M: DynamicDataMarker,
    for<'a> <M::DataStruct as Yokeable<'a>>::Output: PartialEq,
    O: Eq,
{
    fn eq(&self, other: &Self) -> bool {
        match (self.get(), other.get()) {
            (Ok(x), Ok(y)) => x == y,
            (Err(x), Err(y)) => x == y,
            _ => false,
        }
    }
}

impl<M> Eq for DataPayload<M>
where
    M: DynamicDataMarker,
    for<'a> <M::DataStruct as Yokeable<'a>>::Output: Eq,
{
}

impl<M, O> Eq for DataPayloadOr<M, O>
where
    M: DynamicDataMarker,
    for<'a> <M::DataStruct as Yokeable<'a>>::Output: Eq,
    O: Eq,
{
}

#[test]
fn test_clone_eq() {
    use crate::hello_world::*;
    let p1 = DataPayload::<HelloWorldV1>::from_static_str("Demo");
    #[allow(clippy::redundant_clone)]
    let p2 = p1.clone();
    assert_eq!(p1, p2);

    let p1 = DataPayloadOr::<HelloWorldV1, usize>::from_payload(p1);
    #[allow(clippy::redundant_clone)]
    let p2 = p1.clone();
    assert_eq!(p1, p2);

    let p3 = DataPayloadOr::<HelloWorldV1, usize>::from_other(555);
    #[allow(clippy::redundant_clone)]
    let p4 = p3.clone();
    assert_eq!(p3, p4);

    let p5 = DataPayloadOr::<HelloWorldV1, usize>::from_other(666);
    assert_ne!(p3, p5);
    assert_ne!(p4, p5);

    assert_ne!(p1, p3);
    assert_ne!(p1, p4);
    assert_ne!(p1, p5);
    assert_ne!(p2, p3);
    assert_ne!(p2, p4);
    assert_ne!(p2, p5);
}

impl<M> DataPayload<M>
where
    M: DynamicDataMarker,
{
    /// Convert a fully owned (`'static`) data struct into a DataPayload.
    ///
    /// This constructor creates `'static` payloads.
    ///
    /// # Examples
    ///
    /// ```
    /// use icu_provider::hello_world::*;
    /// use icu_provider::prelude::*;
    /// use std::borrow::Cow;
    ///
    /// let local_struct = HelloWorld {
    ///     message: Cow::Owned("example".to_owned()),
    /// };
    ///
    /// let payload = DataPayload::<HelloWorldV1>::from_owned(local_struct.clone());
    ///
    /// assert_eq!(payload.get(), &local_struct);
    /// ```
    #[inline]
    pub fn from_owned(data: M::DataStruct) -> Self {
        Self(DataPayloadInner::Yoke(
            Yoke::new_owned(data).convert_cart_into_option_pointer(),
        ))
    }

    /// Construct a [`DataPayload`] from a static reference.
    ///
    /// This is mainly used by databake.
    #[inline]
    pub const fn from_static_ref(data: &'static M::DataStruct) -> Self {
        Self(DataPayloadInner::StaticRef(data))
    }

    /// Mutate the data contained in this DataPayload.
    ///
    /// For safety, all mutation operations must take place within a helper function that cannot
    /// borrow data from the surrounding context.
    ///
    /// # Examples
    ///
    /// Basic usage:
    ///
    /// ```
    /// use icu_provider::hello_world::HelloWorldV1;
    /// use icu_provider::prelude::*;
    ///
    /// let mut payload = DataPayload::<HelloWorldV1>::from_static_str("Hello");
    ///
    /// payload.with_mut(|s| s.message.to_mut().push_str(" World"));
    ///
    /// assert_eq!("Hello World", payload.get().message);
    /// ```
    ///
    /// To transfer data from the context into the data struct, use the `move` keyword:
    ///
    /// ```
    /// use icu_provider::hello_world::HelloWorldV1;
    /// use icu_provider::prelude::*;
    ///
    /// let mut payload = DataPayload::<HelloWorldV1>::from_static_str("Hello");
    ///
    /// let suffix = " World";
    /// payload.with_mut(move |s| s.message.to_mut().push_str(suffix));
    ///
    /// assert_eq!("Hello World", payload.get().message);
    /// ```
    pub fn with_mut<'a, F>(&'a mut self, f: F)
    where
        F: 'static + for<'b> FnOnce(&'b mut <M::DataStruct as Yokeable<'a>>::Output),
        M::DataStruct: zerofrom::ZeroFrom<'static, M::DataStruct>,
    {
        if let DataPayloadInner::StaticRef(r) = self.0 {
            self.0 = DataPayloadInner::Yoke(
                Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r))
                    .convert_cart_into_option_pointer(),
            );
        }
        match &mut self.0 {
            DataPayloadInner::Yoke(yoke) => yoke.with_mut(f),
            _ => unreachable!(),
        }
    }

    /// Borrows the underlying data.
    ///
    /// This function should be used like `Deref` would normally be used. For more information on
    /// why DataPayload cannot implement `Deref`, see the `yoke` crate.
    ///
    /// # Examples
    ///
    /// ```
    /// use icu_provider::hello_world::HelloWorldV1;
    /// use icu_provider::prelude::*;
    ///
    /// let payload = DataPayload::<HelloWorldV1>::from_static_str("Demo");
    ///
    /// assert_eq!("Demo", payload.get().message);
    /// ```
    #[inline]
    #[allow(clippy::needless_lifetimes)]
    pub fn get<'a>(&'a self) -> &'a <M::DataStruct as Yokeable<'a>>::Output {
        match &self.0 {
            DataPayloadInner::Yoke(yoke) => yoke.get(),
            DataPayloadInner::StaticRef(r) => Yokeable::transform(*r),
        }
    }

    /// Borrows the underlying data statically if possible.
    ///
    /// This will succeed if [`DataPayload`] is constructed with [`DataPayload::from_static_ref`], which is used by
    /// baked providers.
    #[inline]
    pub fn get_static(&self) -> Option<&'static <M::DataStruct as Yokeable<'static>>::Output> {
        match &self.0 {
            DataPayloadInner::Yoke(_) => None,
            DataPayloadInner::StaticRef(r) => Some(Yokeable::transform(*r)),
        }
    }

    /// Maps `DataPayload<M>` to `DataPayload<M2>` by projecting it with [`Yoke::map_project`].
    ///
    /// This is accomplished by a function that takes `M`'s data type and returns `M2`'s data
    /// type. The function takes a second argument which should be ignored. For more details,
    /// see [`Yoke::map_project()`].
    ///
    /// The standard [`DataPayload::map_project()`] function moves `self` and cannot capture any
    /// data from its context. Use one of the sister methods if you need these capabilities:
    ///
    /// - [`DataPayload::map_project_cloned()`] if you don't have ownership of `self`
    /// - [`DataPayload::try_map_project()`] to bubble up an error
    /// - [`DataPayload::try_map_project_cloned()`] to do both of the above
    ///
    /// # Examples
    ///
    /// Map from `HelloWorld` to a `Cow<str>` containing just the message:
    ///
    /// ```
    /// use icu_provider::hello_world::*;
    /// use icu_provider::prelude::*;
    /// use std::borrow::Cow;
    ///
    /// // A custom marker type is required when using `map_project`. The DataStruct should be the
    /// // target type, and the Cart should correspond to the type being transformed.
    ///
    /// struct HelloWorldV1MessageMarker;
    /// impl DynamicDataMarker for HelloWorldV1MessageMarker {
    ///     type DataStruct = Cow<'static, str>;
    /// }
    ///
    /// let p1: DataPayload<HelloWorldV1> = DataPayload::from_owned(HelloWorld {
    ///     message: Cow::Borrowed("Hello World"),
    /// });
    ///
    /// assert_eq!("Hello World", p1.get().message);
    ///
    /// let p2: DataPayload<HelloWorldV1MessageMarker> = p1.map_project(|obj, _| obj.message);
    ///
    /// // Note: at this point, p1 has been moved.
    /// assert_eq!("Hello World", p2.get());
    /// ```
    #[allow(clippy::type_complexity)]
    pub fn map_project<M2, F>(self, f: F) -> DataPayload<M2>
    where
        M2: DynamicDataMarker,
        F: for<'a> FnOnce(
            <M::DataStruct as Yokeable<'a>>::Output,
            PhantomData<&'a ()>,
        ) -> <M2::DataStruct as Yokeable<'a>>::Output,
        M::DataStruct: zerofrom::ZeroFrom<'static, M::DataStruct>,
    {
        DataPayload(DataPayloadInner::Yoke(
            match self.0 {
                DataPayloadInner::Yoke(yoke) => yoke,
                DataPayloadInner::StaticRef(r) => Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r))
                    .convert_cart_into_option_pointer(),
            }
            .map_project(f),
        ))
    }

    /// Version of [`DataPayload::map_project()`] that borrows `self` instead of moving `self`.
    ///
    /// # Examples
    ///
    /// Same example as above, but this time, do not move out of `p1`:
    ///
    /// ```
    /// // Same imports and definitions as above
    /// # use icu_provider::hello_world::*;
    /// # use icu_provider::prelude::*;
    /// # use std::borrow::Cow;
    /// # struct HelloWorldV1MessageMarker;
    /// # impl DynamicDataMarker for HelloWorldV1MessageMarker {
    /// #     type DataStruct = Cow<'static, str>;
    /// # }
    ///
    /// let p1: DataPayload<HelloWorldV1> = DataPayload::from_owned(HelloWorld {
    ///     message: Cow::Borrowed("Hello World"),
    /// });
    ///
    /// assert_eq!("Hello World", p1.get().message);
    ///
    /// let p2: DataPayload<HelloWorldV1MessageMarker> =
    ///     p1.map_project_cloned(|obj, _| obj.message.clone());
    ///
    /// // Note: p1 is still valid.
    /// assert_eq!(p1.get().message, *p2.get());
    /// ```
    #[allow(clippy::type_complexity)]
    pub fn map_project_cloned<'this, M2, F>(&'this self, f: F) -> DataPayload<M2>
    where
        M2: DynamicDataMarker,
        F: for<'a> FnOnce(
            &'this <M::DataStruct as Yokeable<'a>>::Output,
            PhantomData<&'a ()>,
        ) -> <M2::DataStruct as Yokeable<'a>>::Output,
    {
        DataPayload(DataPayloadInner::Yoke(match &self.0 {
            DataPayloadInner::Yoke(yoke) => yoke.map_project_cloned(f),
            DataPayloadInner::StaticRef(r) => {
                let output: <M2::DataStruct as Yokeable<'static>>::Output =
                    f(Yokeable::transform(*r), PhantomData);
                // Safety: <M2::Yokeable as Yokeable<'static>>::Output is the same type as M2::Yokeable;
                // we're going from 'static to 'static, however in a generic context it's not
                // clear to the compiler that that is the case. We have to use the unsafe make API to do this.
                let yokeable: M2::DataStruct = unsafe { M2::DataStruct::make(output) };
                Yoke::new_owned(yokeable).convert_cart_into_option_pointer()
            }
        }))
    }

    /// Version of [`DataPayload::map_project()`] that bubbles up an error from `f`.
    ///
    /// # Examples
    ///
    /// Same example as above, but bubble up an error:
    ///
    /// ```
    /// // Same imports and definitions as above
    /// # use icu_provider::hello_world::*;
    /// # use icu_provider::prelude::*;
    /// # use std::borrow::Cow;
    /// # struct HelloWorldV1MessageMarker;
    /// # impl DynamicDataMarker for HelloWorldV1MessageMarker {
    /// #     type DataStruct = Cow<'static, str>;
    /// # }
    ///
    /// let p1: DataPayload<HelloWorldV1> = DataPayload::from_owned(HelloWorld {
    ///     message: Cow::Borrowed("Hello World"),
    /// });
    ///
    /// assert_eq!("Hello World", p1.get().message);
    ///
    /// let string_to_append = "Extra";
    /// let p2: DataPayload<HelloWorldV1MessageMarker> =
    ///     p1.try_map_project(|mut obj, _| {
    ///         if obj.message.is_empty() {
    ///             return Err("Example error");
    ///         }
    ///         obj.message.to_mut().push_str(string_to_append);
    ///         Ok(obj.message)
    ///     })?;
    ///
    /// assert_eq!("Hello WorldExtra", p2.get());
    /// # Ok::<(), &'static str>(())
    /// ```
    #[allow(clippy::type_complexity)]
    pub fn try_map_project<M2, F, E>(self, f: F) -> Result<DataPayload<M2>, E>
    where
        M2: DynamicDataMarker,
        F: for<'a> FnOnce(
            <M::DataStruct as Yokeable<'a>>::Output,
            PhantomData<&'a ()>,
        ) -> Result<<M2::DataStruct as Yokeable<'a>>::Output, E>,
        M::DataStruct: zerofrom::ZeroFrom<'static, M::DataStruct>,
    {
        Ok(DataPayload(DataPayloadInner::Yoke(
            match self.0 {
                DataPayloadInner::Yoke(yoke) => yoke,
                DataPayloadInner::StaticRef(r) => Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r))
                    .convert_cart_into_option_pointer(),
            }
            .try_map_project(f)?,
        )))
    }

    /// Version of [`DataPayload::map_project_cloned()`] that  bubbles up an error from `f`.
    ///
    /// # Examples
    ///
    /// Same example as above, but bubble up an error:
    ///
    /// ```
    /// // Same imports and definitions as above
    /// # use icu_provider::hello_world::*;
    /// # use icu_provider::prelude::*;
    /// # use std::borrow::Cow;
    /// # struct HelloWorldV1MessageMarker;
    /// # impl DynamicDataMarker for HelloWorldV1MessageMarker {
    /// #     type DataStruct = Cow<'static, str>;
    /// # }
    ///
    /// let p1: DataPayload<HelloWorldV1> = DataPayload::from_owned(HelloWorld {
    ///     message: Cow::Borrowed("Hello World"),
    /// });
    ///
    /// assert_eq!("Hello World", p1.get().message);
    ///
    /// let string_to_append = "Extra";
    /// let p2: DataPayload<HelloWorldV1MessageMarker> = p1
    ///     .try_map_project_cloned(|obj, _| {
    ///         if obj.message.is_empty() {
    ///             return Err("Example error");
    ///         }
    ///         let mut message = obj.message.clone();
    ///         message.to_mut().push_str(string_to_append);
    ///         Ok(message)
    ///     })?;
    ///
    /// // Note: p1 is still valid, but the values no longer equal.
    /// assert_ne!(p1.get().message, *p2.get());
    /// assert_eq!("Hello WorldExtra", p2.get());
    /// # Ok::<(), &'static str>(())
    /// ```
    #[allow(clippy::type_complexity)]
    pub fn try_map_project_cloned<'this, M2, F, E>(&'this self, f: F) -> Result<DataPayload<M2>, E>
    where
        M2: DynamicDataMarker,
        F: for<'a> FnOnce(
            &'this <M::DataStruct as Yokeable<'a>>::Output,
            PhantomData<&'a ()>,
        ) -> Result<<M2::DataStruct as Yokeable<'a>>::Output, E>,
    {
        Ok(DataPayload(DataPayloadInner::Yoke(match &self.0 {
            DataPayloadInner::Yoke(yoke) => yoke.try_map_project_cloned(f)?,
            DataPayloadInner::StaticRef(r) => {
                let output: <M2::DataStruct as Yokeable<'static>>::Output =
                    f(Yokeable::transform(*r), PhantomData)?;
                // Safety: <M2::Yokeable as Yokeable<'static>>::Output is the same type as M2::Yokeable,
                // and `output` is `'static` so there are no lifetimes to manage for `make()`
                Yoke::new_owned(unsafe { M2::DataStruct::make(output) })
                    .convert_cart_into_option_pointer()
            }
        })))
    }

    /// Convert between two [`DynamicDataMarker`] types that are compatible with each other
    /// with compile-time type checking.
    ///
    /// This happens if they both have the same [`DynamicDataMarker::DataStruct`] type.
    ///
    /// Can be used to erase the marker of a data payload in cases where multiple markers correspond
    /// to the same data struct.
    ///
    /// For runtime dynamic casting, use [`DataPayload::dynamic_cast_mut()`].
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use icu_provider::hello_world::*;
    /// use icu_provider::prelude::*;
    ///
    /// struct CustomHelloWorldV1;
    /// impl DynamicDataMarker for CustomHelloWorldV1 {
    ///     type DataStruct = HelloWorld<'static>;
    /// }
    ///
    /// let hello_world: DataPayload<HelloWorldV1> = todo!();
    /// let custom: DataPayload<CustomHelloWorldV1> = hello_world.cast();
    /// ```
    #[inline]
    pub fn cast<M2>(self) -> DataPayload<M2>
    where
        M2: DynamicDataMarker<DataStruct = M::DataStruct>,
    {
        DataPayload(match self.0 {
            DataPayloadInner::Yoke(yoke) => DataPayloadInner::Yoke(yoke),
            DataPayloadInner::StaticRef(r) => DataPayloadInner::StaticRef(r),
        })
    }

    /// Convert between two [`DynamicDataMarker`] types that are compatible with each other
    /// with compile-time type checking.
    ///
    /// This happens if they both have the same [`DynamicDataMarker::DataStruct`] type.
    ///
    /// Can be used to erase the marker of a data payload in cases where multiple markers correspond
    /// to the same data struct.
    #[inline]
    pub fn cast_ref<M2>(&self) -> &DataPayload<M2>
    where
        M2: DynamicDataMarker<DataStruct = M::DataStruct>,
    {
        // SAFETY: As seen in the implementation of `cast`, the struct is the same, it's just the generic that changes.
        unsafe { core::mem::transmute(self) }
    }

    /// Convert a [`DataPayload`] to one of the same type with runtime type checking.
    ///
    /// Primarily useful to convert from a generic to a concrete marker type.
    ///
    /// If the `M2` type argument does not match the true marker type, a `DataError` is returned.
    ///
    /// For compile-time static casting, use [`DataPayload::cast()`].
    ///
    /// # Examples
    ///
    /// Short-circuit a data request request based on the marker, returning
    /// a result from a different data provider:
    ///
    /// ```
    /// use core::any::TypeId;
    /// use icu_locale_core::locale;
    /// use icu_provider::hello_world::*;
    /// use icu_provider::prelude::*;
    /// use icu_provider_adapters::empty::EmptyDataProvider;
    /// use std::borrow::Cow;
    ///
    /// struct MyForkingProvider<P0, P1> {
    ///     fallback_provider: P0,
    ///     hello_world_provider: P1,
    /// }
    ///
    /// impl<M, P0, P1> DataProvider<M> for MyForkingProvider<P0, P1>
    /// where
    ///     M: DataMarker,
    ///     P0: DataProvider<M>,
    ///     P1: DataProvider<HelloWorldV1>,
    /// {
    ///     #[inline]
    ///     fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
    ///         if TypeId::of::<HelloWorldV1>() == TypeId::of::<M>() {
    ///             let response = DataProvider::<HelloWorldV1>::load(
    ///                 &self.hello_world_provider,
    ///                 req,
    ///             )?;
    ///             Ok(DataResponse {
    ///                 metadata: response.metadata,
    ///                 payload: response.payload.dynamic_cast()?,
    ///             })
    ///         } else {
    ///             self.fallback_provider.load(req)
    ///         }
    ///     }
    /// }
    ///
    /// let provider = MyForkingProvider {
    ///     fallback_provider: EmptyDataProvider::new(),
    ///     hello_world_provider: HelloWorldProvider,
    /// };
    ///
    /// let formatter =
    ///     HelloWorldFormatter::try_new_unstable(&provider, locale!("de").into())
    ///         .unwrap();
    ///
    /// // This succeeds because the data was loaded from HelloWorldProvider
    /// // rather than the empty fallback provider.
    /// assert_eq!(formatter.format_to_string(), "Hallo Welt");
    /// ```
    pub fn dynamic_cast<M2>(self) -> Result<DataPayload<M2>, DataError>
    where
        M2: DynamicDataMarker,
    {
        let mut option_self = Some(self);
        let mut option_out = None::<DataPayload<M2>>;
        if let Some(x) = (&mut option_out as &mut dyn core::any::Any).downcast_mut() {
            core::mem::swap(&mut option_self, x);
            debug_assert!(option_out.is_some());
            if let Some(out) = option_out {
                return Ok(out);
            }
        }
        Err(DataError::for_type::<M2>().with_str_context(core::any::type_name::<M>()))
    }

    /// Convert a mutable reference of a [`DataPayload`] to another mutable reference
    /// of the same type with runtime type checking.
    ///
    /// Primarily useful to convert from a generic to a concrete marker type.
    ///
    /// If the `M2` type argument does not match the true marker type, a `DataError` is returned.
    ///
    /// For compile-time static casting, use [`DataPayload::cast()`].
    ///
    /// # Examples
    ///
    /// Change the results of a particular request based on marker:
    ///
    /// ```
    /// use icu_locale_core::locale;
    /// use icu_provider::hello_world::*;
    /// use icu_provider::prelude::*;
    ///
    /// struct MyWrapper<P> {
    ///     inner: P,
    /// }
    ///
    /// impl<M, P> DataProvider<M> for MyWrapper<P>
    /// where
    ///     M: DataMarker,
    ///     P: DataProvider<M>,
    /// {
    ///     #[inline]
    ///     fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
    ///         let mut res = self.inner.load(req)?;
    ///         let mut cast_result =
    ///             res.payload.dynamic_cast_mut::<HelloWorldV1>();
    ///         if let Ok(ref mut concrete_payload) = cast_result {
    ///             // Add an emoji to the hello world message
    ///             concrete_payload.with_mut(|data| {
    ///                 data.message.to_mut().insert_str(0, "✨ ");
    ///             });
    ///         }
    ///         Ok(res)
    ///     }
    /// }
    ///
    /// let provider = MyWrapper {
    ///     inner: HelloWorldProvider,
    /// };
    /// let formatter =
    ///     HelloWorldFormatter::try_new_unstable(&provider, locale!("de").into())
    ///         .unwrap();
    ///
    /// assert_eq!(formatter.format_to_string(), "✨ Hallo Welt");
    /// ```
    #[inline]
    pub fn dynamic_cast_mut<M2>(&mut self) -> Result<&mut DataPayload<M2>, DataError>
    where
        M2: DynamicDataMarker,
    {
        let this: &mut dyn core::any::Any = self;
        if let Some(this) = this.downcast_mut() {
            Ok(this)
        } else {
            Err(DataError::for_type::<M2>().with_str_context(core::any::type_name::<M>()))
        }
    }
}

impl DataPayload<BufferMarker> {
    /// Converts an owned byte buffer into a `DataPayload<BufferMarker>`.
    #[cfg(feature = "alloc")]
    pub fn from_owned_buffer(buffer: Box<[u8]>) -> Self {
        let yoke = Yoke::attach_to_cart(SelectedRc::new(buffer), |b| &**b)
            .wrap_cart_in_option()
            .convert_cart_into_option_pointer();
        Self(DataPayloadInner::Yoke(yoke))
    }

    /// Converts a yoked byte buffer into a `DataPayload<BufferMarker>`.
    pub fn from_yoked_buffer(yoke: Yoke<&'static [u8], Option<Cart>>) -> Self {
        let yoke = Cart::unwrap_cart(yoke);
        Self(DataPayloadInner::Yoke(
            yoke.convert_cart_into_option_pointer(),
        ))
    }

    /// Converts a static byte buffer into a `DataPayload<BufferMarker>`.
    pub fn from_static_buffer(buffer: &'static [u8]) -> Self {
        Self(DataPayloadInner::Yoke(
            Yoke::new_owned(buffer).convert_cart_into_option_pointer(),
        ))
    }
}

impl<M> Default for DataPayload<M>
where
    M: DynamicDataMarker,
    M::DataStruct: Default,
{
    fn default() -> Self {
        Self::from_owned(Default::default())
    }
}

impl<M, O> DataPayloadOr<M, O>
where
    M: DynamicDataMarker,
{
    /// Creates a [`DataPayloadOr`] from a [`DataPayload`].
    #[inline]
    pub fn from_payload(payload: DataPayload<M>) -> Self {
        match payload.0 {
            DataPayloadInner::Yoke(yoke) => Self(DataPayloadOrInner::Yoke(yoke)),
            DataPayloadInner::StaticRef(r) => Self(DataPayloadOrInner::Inner(
                DataPayloadOrInnerInner::StaticRef(r),
            )),
        }
    }

    /// Creates a [`DataPayloadOr`] from the other type `O`.
    #[inline]
    pub fn from_other(other: O) -> Self {
        Self(DataPayloadOrInner::Inner(DataPayloadOrInnerInner::Other(
            other,
        )))
    }

    /// Returns whether this object represents a [`DataPayload`].
    #[inline]
    pub fn is_payload(&self) -> bool {
        match &self.0 {
            DataPayloadOrInner::Yoke(_) => true,
            DataPayloadOrInner::Inner(DataPayloadOrInnerInner::StaticRef(_)) => true,
            DataPayloadOrInner::Inner(DataPayloadOrInnerInner::Other(_)) => false,
        }
    }

    /// Gets the value from this [`DataPayload`] as `Ok` or the other type as `Err`.
    #[allow(clippy::needless_lifetimes)]
    #[inline]
    pub fn get<'a>(&'a self) -> Result<&'a <M::DataStruct as Yokeable<'a>>::Output, &'a O> {
        match &self.0 {
            DataPayloadOrInner::Yoke(yoke) => Ok(yoke.get()),
            DataPayloadOrInner::Inner(DataPayloadOrInnerInner::StaticRef(r)) => {
                Ok(Yokeable::transform(*r))
            }
            DataPayloadOrInner::Inner(DataPayloadOrInnerInner::Other(o)) => Err(o),
        }
    }

    /// Consumes this [`DataPayloadOr`], returning either the wrapped
    /// [`DataPayload`] or the other type.
    #[inline]
    pub fn into_inner(self) -> Result<DataPayload<M>, O> {
        match self.0 {
            DataPayloadOrInner::Yoke(yoke) => Ok(DataPayload(DataPayloadInner::Yoke(yoke))),
            DataPayloadOrInner::Inner(DataPayloadOrInnerInner::StaticRef(r)) => {
                Ok(DataPayload(DataPayloadInner::StaticRef(r)))
            }
            DataPayloadOrInner::Inner(DataPayloadOrInnerInner::Other(o)) => Err(o),
        }
    }
}

impl<M> DataPayloadOr<M, ()>
where
    M: DynamicDataMarker,
{
    /// Convenience function to return the other type with value `()`
    #[inline]
    pub fn none() -> Self {
        Self::from_other(())
    }

    /// Convenience function to return `Some` or `None` for other type `()`
    #[allow(clippy::needless_lifetimes)]
    #[inline]
    pub fn get_option<'a>(&'a self) -> Option<&'a <M::DataStruct as Yokeable<'a>>::Output> {
        self.get().ok()
    }
}

/// A response object containing an object as payload and metadata about it.
#[allow(clippy::exhaustive_structs)] // this type is stable
pub struct DataResponse<M>
where
    M: DynamicDataMarker,
{
    /// Metadata about the returned object.
    pub metadata: DataResponseMetadata,

    /// The object itself
    pub payload: DataPayload<M>,
}

impl<M> DataResponse<M>
where
    M: DynamicDataMarker,
{
    /// Convert between two [`DynamicDataMarker`] types that are compatible with each other
    /// with compile-time type checking.
    ///
    /// This happens if they both have the same [`DynamicDataMarker::DataStruct`] type.
    ///
    /// Can be used to erase the marker of a data payload in cases where multiple markers correspond
    /// to the same data struct.
    ///
    /// For runtime dynamic casting, use [`DataPayload::dynamic_cast_mut()`].
    #[inline]
    pub fn cast<M2>(self) -> DataResponse<M2>
    where
        M2: DynamicDataMarker<DataStruct = M::DataStruct>,
    {
        DataResponse {
            metadata: self.metadata,
            payload: self.payload.cast(),
        }
    }
}

impl<M> Debug for DataResponse<M>
where
    M: DynamicDataMarker,
    for<'a> &'a <M::DataStruct as Yokeable<'a>>::Output: Debug,
{
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(
            f,
            "DataResponse {{ metadata: {:?}, payload: {:?} }}",
            self.metadata, self.payload
        )
    }
}

/// Cloning a DataResponse is generally a cheap operation.
/// See notes in the `Clone` impl for [`Yoke`].
///
/// # Examples
///
/// ```no_run
/// use icu_provider::hello_world::*;
/// use icu_provider::prelude::*;
///
/// let resp1: DataResponse<HelloWorldV1> = todo!();
/// let resp2 = resp1.clone();
/// ```
impl<M> Clone for DataResponse<M>
where
    M: DynamicDataMarker,
    for<'a> <M::DataStruct as Yokeable<'a>>::Output: Clone,
{
    fn clone(&self) -> Self {
        Self {
            metadata: self.metadata.clone(),
            payload: self.payload.clone(),
        }
    }
}

#[test]
fn test_debug() {
    use crate::hello_world::*;
    use crate::prelude::*;
    let resp = HelloWorldProvider
        .load(DataRequest {
            id: DataIdentifierBorrowed::for_locale(&icu_locale_core::locale!("en").into()),
            ..Default::default()
        })
        .unwrap();
    assert_eq!("DataResponse { metadata: DataResponseMetadata { locale: None, buffer_format: None, checksum: Some(1234) }, payload: HelloWorld { message: \"Hello World\" } }", format!("{resp:?}"));
}
