use std::ops::{Deref, DerefMut};

use crate::{Observable, Signal};
use derive_where::derive_where;
use variadics_please::all_tuples;

use super::ErasedObservable;

#[derive_where(Clone)]
pub struct ErasedObservables<Out, OutError, const SIZE: usize>
where
	Out: Signal,
	OutError: Signal,
{
	observables: [ErasedObservable<Out, OutError>; SIZE],
}

impl<Out, OutError, const SIZE: usize> Deref for ErasedObservables<Out, OutError, SIZE>
where
	Out: Signal,
	OutError: Signal,
{
	type Target = [ErasedObservable<Out, OutError>; SIZE];

	fn deref(&self) -> &Self::Target {
		&self.observables
	}
}

impl<Out, OutError, const SIZE: usize> DerefMut for ErasedObservables<Out, OutError, SIZE>
where
	Out: Signal,
	OutError: Signal,
{
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.observables
	}
}

impl<Out, OutError, const SIZE: usize, O> From<[O; SIZE]> for ErasedObservables<Out, OutError, SIZE>
where
	Out: 'static + Signal,
	OutError: 'static + Signal,
	O: 'static + Observable<Out = Out, OutError = OutError> + Send + Sync,
{
	fn from(value: [O; SIZE]) -> Self {
		Self {
			observables: value.map(|observable| ErasedObservable::new(observable)),
		}
	}
}

macro_rules! tuple_len {
	() => { 0usize };
	(@one $name:ident) => { 1usize };
	($($name:ident),+) => {
		0usize $(+ tuple_len!(@one $name))*
	};
}

macro_rules! impl_tuple_erased_observable {
	($(#[$meta:meta])* $($name:ident),*) => {
		$(#[$meta])*
		impl<
			Out: 'static + Signal,
			OutError: 'static + Signal,
			$($name: 'static + Observable<Out = Out, OutError = OutError> + Send + Sync),*
		> From<($($name,)*)> for ErasedObservables<Out, OutError, { tuple_len!($($name),*) }>
		{
			fn from(value: ($($name,)*)) -> Self {
				#[allow(non_snake_case)]
				let ($($name,)*) = value;

				Self {
					observables: [
						$(ErasedObservable::new($name),)*
					],
				}
			}
		}
	};
}

all_tuples!(impl_tuple_erased_observable, 1, 15, O);
