use std::future::Future;
use std::pin::Pin;

use tokio::task::JoinHandle;

#[cfg(feature = "native-runtime")]
pub type RuntimeBoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;

#[cfg(not(any(feature = "native-runtime", feature = "wasm-runtime")))]
pub type RuntimeBoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;

#[cfg(feature = "wasm-runtime")]
pub type RuntimeBoxFuture<T> = Pin<Box<dyn Future<Output = T>>>;

#[cfg(feature = "native-runtime")]
pub trait RuntimeFuture: Future + Send + 'static {}

#[cfg(feature = "native-runtime")]
impl<F> RuntimeFuture for F where F: Future + Send + 'static {}

#[cfg(not(any(feature = "native-runtime", feature = "wasm-runtime")))]
pub trait RuntimeFuture: Future + Send + 'static {}

#[cfg(not(any(feature = "native-runtime", feature = "wasm-runtime")))]
impl<F> RuntimeFuture for F where F: Future + Send + 'static {}

#[cfg(feature = "wasm-runtime")]
pub trait RuntimeFuture: Future + 'static {}

#[cfg(feature = "wasm-runtime")]
impl<F> RuntimeFuture for F where F: Future + 'static {}

#[cfg(feature = "native-runtime")]
pub trait RuntimeFutureOutput: Send + 'static {}

#[cfg(feature = "native-runtime")]
impl<T> RuntimeFutureOutput for T where T: Send + 'static {}

#[cfg(not(any(feature = "native-runtime", feature = "wasm-runtime")))]
pub trait RuntimeFutureOutput: Send + 'static {}

#[cfg(not(any(feature = "native-runtime", feature = "wasm-runtime")))]
impl<T> RuntimeFutureOutput for T where T: Send + 'static {}

#[cfg(feature = "wasm-runtime")]
pub trait RuntimeFutureOutput: 'static {}

#[cfg(feature = "wasm-runtime")]
impl<T> RuntimeFutureOutput for T where T: 'static {}

#[derive(Clone, Copy, Debug, Default)]
pub struct RuntimeSpawner;

impl RuntimeSpawner {
	#[cfg(feature = "native-runtime")]
	pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
	where
		F: RuntimeFuture,
		F::Output: RuntimeFutureOutput,
	{
		tokio::spawn(future)
	}

	#[cfg(not(any(feature = "native-runtime", feature = "wasm-runtime")))]
	pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
	where
		F: RuntimeFuture,
		F::Output: RuntimeFutureOutput,
	{
		tokio::spawn(future)
	}

	#[cfg(feature = "wasm-runtime")]
	pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
	where
		F: RuntimeFuture,
		F::Output: RuntimeFutureOutput,
	{
		tokio::task::spawn_local(future)
	}
}

#[cfg(feature = "native-runtime")]
pub fn boxed_runtime_future<F, T>(future: F) -> RuntimeBoxFuture<T>
where
	F: Future<Output = T> + Send + 'static,
{
	Box::pin(future)
}

#[cfg(not(any(feature = "native-runtime", feature = "wasm-runtime")))]
pub fn boxed_runtime_future<F, T>(future: F) -> RuntimeBoxFuture<T>
where
	F: Future<Output = T> + Send + 'static,
{
	Box::pin(future)
}

#[cfg(feature = "wasm-runtime")]
pub fn boxed_runtime_future<F, T>(future: F) -> RuntimeBoxFuture<T>
where
	F: Future<Output = T> + 'static,
{
	Box::pin(future)
}

#[cfg(all(test, feature = "wasm-runtime"))]
mod tests {
	use std::cell::RefCell;
	use std::rc::Rc;

	use super::{RuntimeBoxFuture, boxed_runtime_future};

	fn accepts_wasm_local_callback(
		callback: impl Fn() -> RuntimeBoxFuture<()> + 'static,
	) -> impl Fn() -> RuntimeBoxFuture<()> {
		callback
	}

	#[test]
	fn wasm_runtime_box_future_accepts_local_callbacks() {
		let state = Rc::new(RefCell::new(0));
		let callback = accepts_wasm_local_callback({
			let state = state.clone();
			move || {
				let state = state.clone();
				boxed_runtime_future(async move {
					*state.borrow_mut() += 1;
				})
			}
		});

		let _future = callback();
	}
}
