#![doc = include_str!("../README.md")]

use core::future::Future;
use futures::future::{BoxFuture, LocalBoxFuture};

pub use futures::future::FutureExt;
pub use futures_kind_macros::kinds;

/// An abstraction over [`Future`]s.
///
/// This trait allows you to write async code that works with both `Send` and `!Send` futures,
/// avoiding the need to duplicate trait implementations for different runtime requirements.
///
/// # Example
///
/// Define a database trait generic over [`FutureKind`]:
///
/// ```rust
/// use futures_kind::{FutureKind, Sendable, Local};
/// use futures::future::{BoxFuture, LocalBoxFuture, FutureExt};
///
/// // Database trait that works with any FutureKind
/// trait Database<K: FutureKind> {
///     fn get<'a>(&'a self, key: &'a str) -> K::Future<'a, Option<String>>;
///     fn set<'a>(&'a mut self, key: &'a str, value: String) -> K::Future<'a, ()>;
/// }
///
/// struct InMemoryDb {
///     data: std::collections::HashMap<String, String>,
/// }
///
/// // Implement for Send futures (e.g., tokio runtime)
/// impl Database<Sendable> for InMemoryDb {
///     fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<String>> {
///         async move { self.data.get(key).cloned() }.boxed()
///     }
///
///     fn set<'a>(&'a mut self, key: &'a str, value: String) -> BoxFuture<'a, ()> {
///         async move {
///             self.data.insert(key.to_string(), value);
///         }.boxed()
///     }
/// }
///
/// // Implement for !Send futures (e.g., Wasm or single-threaded runtime)
/// impl Database<Local> for InMemoryDb {
///     fn get<'a>(&'a self, key: &'a str) -> LocalBoxFuture<'a, Option<String>> {
///         async move { self.data.get(key).cloned() }.boxed_local()
///     }
///
///     fn set<'a>(&'a mut self, key: &'a str, value: String) -> LocalBoxFuture<'a, ()> {
///         async move {
///             self.data.insert(key.to_string(), value);
///         }.boxed_local()
///     }
/// }
/// ```
///
/// # Using with structs
///
/// For cleaner ergonomics, use a struct to carry the type parameter:
///
/// ```rust
/// use std::marker::PhantomData;
/// use futures_kind::{FutureKind, Sendable};
/// use futures::future::{BoxFuture, FutureExt};
///
/// trait Database<K: FutureKind> {
///     fn get<'a>(&'a self, key: &'a str) -> K::Future<'a, Option<String>>;
/// }
///
/// struct InMemoryDb {
///     data: std::collections::HashMap<String, String>,
/// }
///
/// impl Database<Sendable> for InMemoryDb {
///     fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<String>> {
///         async move { self.data.get(key).cloned() }.boxed()
///     }
/// }
///
/// // Repository struct carries the FutureKind parameter
/// struct Repository<K: FutureKind> {
///     db: InMemoryDb,
///     _marker: PhantomData<K>,
/// }
///
/// impl<K: FutureKind> Repository<K>
/// where
///     InMemoryDb: Database<K>
/// {
///     fn new(db: InMemoryDb) -> Self {
///         Self {
///             db,
///             _marker: PhantomData,
///         }
///     }
///
///     async fn fetch_user(&self, user_id: &str) -> Option<String> {
///         Database::<K>::get(&self.db, user_id).await
///     }
/// }
/// ```
pub trait FutureKind {
    /// An abstraction over future types.
    ///
    /// This is especially useful for abstracting over `Send` and `!Send` futures.
    type Future<'a, T: 'a>: Future<Output = T> + 'a;

    /// Lift a future into this kind's future type.
    ///
    /// # Example
    ///
    /// ```rust
    /// use futures_kind::{FutureKind, Sendable, Local};
    /// use futures::future::{BoxFuture, LocalBoxFuture};
    ///
    /// fn sendable_example() -> BoxFuture<'static, u32> {
    ///     Sendable::into_kind(async { 42 })
    /// }
    ///
    /// fn local_example() -> LocalBoxFuture<'static, u32> {
    ///     Local::into_kind(async { 42 })
    /// }
    /// ```
    fn into_kind<'a, T, F>(f: F) -> Self::Future<'a, T>
    where
        T: 'a,
        F: Future<Output = T> + 'a,
        Self::Future<'a, T>: IntoFutureKind<'a, T, F>,
    {
        IntoFutureKind::into_kind(f)
    }
}

/// Abstraction over [`Send`] futures.
///
/// Use this when your async code needs to work with multi-threaded runtimes
/// like `tokio` or `async-std` that require futures to be `Send`.
///
/// # Example
///
/// ```rust
/// use futures_kind::{FutureKind, Sendable};
/// use futures::future::{BoxFuture, FutureExt};
///
/// trait Cache<K: FutureKind> {
///     fn fetch<'a>(&'a self, key: &'a str) -> K::Future<'a, Option<String>>;
/// }
///
/// struct RedisCache;
///
/// // Implement for Send futures - can be used with tokio
/// impl Cache<Sendable> for RedisCache {
///     fn fetch<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<String>> {
///         async move {
///             // Simulate fetching from Redis
///             Some(format!("value-{}", key))
///         }.boxed()
///     }
/// }
/// ```
#[derive(Debug, Clone, Copy)]
pub enum Sendable {}
impl FutureKind for Sendable {
    type Future<'a, T: 'a> = BoxFuture<'a, T>;
}

/// Abstraction over [`!Send`][Send] futures.
///
/// Use this when your async code runs in single-threaded environments like
/// Wasm, browser contexts, or single-threaded executors that don't require
/// futures to be `Send`.
///
/// # Example
///
/// ```rust
/// use futures_kind::{FutureKind, Local};
/// use futures::future::{LocalBoxFuture, FutureExt};
///
/// trait Cache<K: FutureKind> {
///     fn fetch<'a>(&'a self, key: &'a str) -> K::Future<'a, Option<String>>;
/// }
///
/// struct BrowserCache;
///
/// // Implement for !Send futures - can be used in Wasm
/// impl Cache<Local> for BrowserCache {
///     fn fetch<'a>(&'a self, key: &'a str) -> LocalBoxFuture<'a, Option<String>> {
///         async move {
///             // Simulate fetching from browser storage
///             Some(format!("value-{}", key))
///         }.boxed_local()
///     }
/// }
/// ```
#[derive(Debug, Clone, Copy)]
pub enum Local {}
impl FutureKind for Local {
    type Future<'a, T: 'a> = LocalBoxFuture<'a, T>;
}

/// A trait for lifting futures into their [`FutureKind`]-specific type.
///
/// This abstracts over the wrapping operation, allowing generic code to construct
/// the appropriate future type without knowing the concrete kind.
///
/// Typically you'll use [`FutureKind::into_kind`] instead of calling this directly.
///
/// # Example
///
/// ```rust
/// use futures_kind::{FutureKind, Sendable, Local};
/// use futures::future::{BoxFuture, LocalBoxFuture};
///
/// trait Counter<K: FutureKind> {
///     fn next(&self) -> K::Future<'_, u32>;
/// }
///
/// struct Memory { val: u32 }
///
/// impl Counter<Sendable> for Memory {
///     fn next(&self) -> BoxFuture<'_, u32> {
///         let val = self.val;
///         Sendable::into_kind(async move { val + 1 })
///     }
/// }
///
/// impl Counter<Local> for Memory {
///     fn next(&self) -> LocalBoxFuture<'_, u32> {
///         let val = self.val;
///         Local::into_kind(async move { val + 1 })
///     }
/// }
/// ```
pub trait IntoFutureKind<'a, T, F>
where
    T: 'a,
    F: Future<Output = T> + 'a,
{
    /// Lift a future into this type.
    fn into_kind(f: F) -> Self;
}

impl<'a, T, F> IntoFutureKind<'a, T, F> for BoxFuture<'a, T>
where
    T: 'a,
    F: Future<Output = T> + Send + 'a,
{
    fn into_kind(f: F) -> Self {
        Box::pin(f)
    }
}

impl<'a, T, F> IntoFutureKind<'a, T, F> for LocalBoxFuture<'a, T>
where
    T: 'a,
    F: Future<Output = T> + 'a,
{
    fn into_kind(f: F) -> Self {
        Box::pin(f)
    }
}
