use alloc::borrow::Cow;
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::collections::VecDeque;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::hash::BuildHasher;
use core::hash::Hash;

#[cfg(not(feature = "std"))]
use hashbrown::HashMap;
#[cfg(feature = "std")]
use std::collections::HashMap;

#[cfg(not(feature = "std"))]
use hashbrown::HashSet;
#[cfg(feature = "std")]
use std::collections::HashSet;

/// Allow turning a value into an "owned" variant, which can then be
/// returned, moved, etc.
///
/// This usually involves allocating buffers for `Cow<'a, str>`, etc.
///
/// Shamelessly copied from [merde](https://github.com/bearcove/merde)
pub trait IntoStatic: Sized {
    /// The "owned" variant of the type. For `Cow<'a, str>`, this is `Cow<'static, str>`, for example.
    type Output: 'static;

    /// Turns the value into an "owned" variant, which can then be returned, moved, etc.
    ///
    /// This allocates, for all but the most trivial types.
    fn into_static(self) -> Self::Output;
}

impl<T, E> IntoStatic for Result<T, E>
where
    T: IntoStatic,
    E: IntoStatic,
{
    type Output = Result<T::Output, E::Output>;

    fn into_static(self) -> Self::Output {
        match self {
            Ok(v) => Ok(v.into_static()),
            Err(e) => Err(e.into_static()),
        }
    }
}

impl<T> IntoStatic for Cow<'_, T>
where
    T: ToOwned + ?Sized + 'static,
{
    type Output = Cow<'static, T>;

    #[inline(always)]
    fn into_static(self) -> Self::Output {
        match self {
            Cow::Borrowed(b) => Cow::Owned(b.to_owned()),
            Cow::Owned(o) => Cow::Owned(o),
        }
    }
}

macro_rules! impl_into_static_passthru {
    ($($ty:ty),+) => {
        $(
            impl IntoStatic for $ty {
                type Output = $ty;

                #[inline(always)]
                fn into_static(self) -> Self::Output {
                    self
                }
            }
        )+
    };
}

impl_into_static_passthru!(
    String,
    u128,
    u64,
    u32,
    u16,
    u8,
    i128,
    i64,
    i32,
    i16,
    i8,
    bool,
    char,
    usize,
    isize,
    f32,
    f64,
    crate::deps::smol_str::SmolStr
);

impl IntoStatic for &str {
    type Output = crate::deps::smol_str::SmolStr;

    #[inline]
    fn into_static(self) -> Self::Output {
        crate::deps::smol_str::SmolStr::new(self)
    }
}

impl<T: IntoStatic> IntoStatic for Box<T> {
    type Output = Box<T::Output>;

    fn into_static(self) -> Self::Output {
        Box::new((*self).into_static())
    }
}

impl IntoStatic for bytes::Bytes {
    type Output = bytes::Bytes;

    fn into_static(self) -> Self::Output {
        self
    }
}

impl IntoStatic for () {
    type Output = ();

    fn into_static(self) -> Self::Output {
        self
    }
}

impl<T: IntoStatic> IntoStatic for Option<T> {
    type Output = Option<T::Output>;

    fn into_static(self) -> Self::Output {
        self.map(|v| v.into_static())
    }
}

impl<T: IntoStatic> IntoStatic for Vec<T> {
    type Output = Vec<T::Output>;

    fn into_static(self) -> Self::Output {
        self.into_iter().map(|v| v.into_static()).collect()
    }
}

impl<T: IntoStatic + Clone> IntoStatic for Arc<T> {
    type Output = Arc<T::Output>;

    fn into_static(self) -> Self::Output {
        let t: T = (*self).clone();
        Arc::new(t.into_static())
    }
}

impl<K, V, S> IntoStatic for HashMap<K, V, S>
where
    S: BuildHasher + Default + 'static,
    K: IntoStatic + Eq + Hash,
    V: IntoStatic,
    K::Output: Eq + Hash,
{
    type Output = HashMap<K::Output, V::Output, S>;

    fn into_static(self) -> Self::Output {
        self.into_iter()
            .map(|(k, v)| (k.into_static(), v.into_static()))
            .collect()
    }
}

impl<K, V> IntoStatic for BTreeMap<K, V>
where
    K: IntoStatic + Ord,
    V: IntoStatic,
    K::Output: Ord,
{
    type Output = BTreeMap<K::Output, V::Output>;

    fn into_static(self) -> Self::Output {
        self.into_iter()
            .map(|(k, v)| (k.into_static(), v.into_static()))
            .collect()
    }
}

impl<T: IntoStatic> IntoStatic for HashSet<T>
where
    T::Output: Eq + Hash,
{
    type Output = HashSet<T::Output>;

    fn into_static(self) -> Self::Output {
        self.into_iter().map(|v| v.into_static()).collect()
    }
}

impl<T: IntoStatic> IntoStatic for VecDeque<T> {
    type Output = VecDeque<T::Output>;

    fn into_static(self) -> Self::Output {
        self.into_iter().map(|v| v.into_static()).collect()
    }
}

impl<T1: IntoStatic> IntoStatic for (T1,) {
    type Output = (T1::Output,);

    fn into_static(self) -> Self::Output {
        (self.0.into_static(),)
    }
}

impl<T1: IntoStatic, T2: IntoStatic> IntoStatic for (T1, T2) {
    type Output = (T1::Output, T2::Output);

    fn into_static(self) -> Self::Output {
        (self.0.into_static(), self.1.into_static())
    }
}

impl<T1: IntoStatic, T2: IntoStatic, T3: IntoStatic> IntoStatic for (T1, T2, T3) {
    type Output = (T1::Output, T2::Output, T3::Output);

    fn into_static(self) -> Self::Output {
        (
            self.0.into_static(),
            self.1.into_static(),
            self.2.into_static(),
        )
    }
}

impl<T1: IntoStatic, T2: IntoStatic, T3: IntoStatic, T4: IntoStatic> IntoStatic
    for (T1, T2, T3, T4)
{
    type Output = (T1::Output, T2::Output, T3::Output, T4::Output);

    fn into_static(self) -> Self::Output {
        (
            self.0.into_static(),
            self.1.into_static(),
            self.2.into_static(),
            self.3.into_static(),
        )
    }
}

impl<T1: IntoStatic, T2: IntoStatic, T3: IntoStatic, T4: IntoStatic, T5: IntoStatic> IntoStatic
    for (T1, T2, T3, T4, T5)
{
    type Output = (T1::Output, T2::Output, T3::Output, T4::Output, T5::Output);

    fn into_static(self) -> Self::Output {
        (
            self.0.into_static(),
            self.1.into_static(),
            self.2.into_static(),
            self.3.into_static(),
            self.4.into_static(),
        )
    }
}

impl<T1: IntoStatic, T2: IntoStatic, T3: IntoStatic, T4: IntoStatic, T5: IntoStatic, T6: IntoStatic>
    IntoStatic for (T1, T2, T3, T4, T5, T6)
{
    type Output = (
        T1::Output,
        T2::Output,
        T3::Output,
        T4::Output,
        T5::Output,
        T6::Output,
    );

    fn into_static(self) -> Self::Output {
        (
            self.0.into_static(),
            self.1.into_static(),
            self.2.into_static(),
            self.3.into_static(),
            self.4.into_static(),
            self.5.into_static(),
        )
    }
}

impl<
    T1: IntoStatic,
    T2: IntoStatic,
    T3: IntoStatic,
    T4: IntoStatic,
    T5: IntoStatic,
    T6: IntoStatic,
    T7: IntoStatic,
> IntoStatic for (T1, T2, T3, T4, T5, T6, T7)
{
    type Output = (
        T1::Output,
        T2::Output,
        T3::Output,
        T4::Output,
        T5::Output,
        T6::Output,
        T7::Output,
    );

    fn into_static(self) -> Self::Output {
        (
            self.0.into_static(),
            self.1.into_static(),
            self.2.into_static(),
            self.3.into_static(),
            self.4.into_static(),
            self.5.into_static(),
            self.6.into_static(),
        )
    }
}

impl<
    T1: IntoStatic,
    T2: IntoStatic,
    T3: IntoStatic,
    T4: IntoStatic,
    T5: IntoStatic,
    T6: IntoStatic,
    T7: IntoStatic,
    T8: IntoStatic,
> IntoStatic for (T1, T2, T3, T4, T5, T6, T7, T8)
{
    type Output = (
        T1::Output,
        T2::Output,
        T3::Output,
        T4::Output,
        T5::Output,
        T6::Output,
        T7::Output,
        T8::Output,
    );

    fn into_static(self) -> Self::Output {
        (
            self.0.into_static(),
            self.1.into_static(),
            self.2.into_static(),
            self.3.into_static(),
            self.4.into_static(),
            self.5.into_static(),
            self.6.into_static(),
            self.7.into_static(),
        )
    }
}
