Documentation
use std::{
	any::{Any, TypeId},
	collections::HashMap,
	sync::{Arc, LazyLock, OnceLock, atomic::Ordering},
};

use sdd::{AtomicOwned, Guard, Owned, Tag};

use crate::{ServiceProvider, error::ServiceNotFoundError};

pub type Scope = HashMap<(TypeId, Option<TypeId>), std::sync::OnceLock<Arc<dyn Any + Send + Sync>>>;

fn refresh_scope_value(provider: &ServiceProvider, current: &Scope) -> Arc<Scope> {
	let mut refreshed = current.clone();
	let fresh = provider.create_scope();
	for key in fresh.keys() {
		let _ = refreshed.entry(*key).or_default();
	}
	Arc::new(refreshed)
}

pub(crate) fn refresh_atomic_scope(
	scope: &AtomicOwned<Arc<Scope>>,
	provider: &ServiceProvider,
) -> Result<Arc<Scope>, ServiceNotFoundError> {
	let guard = Guard::new();
	loop {
		let current = scope.load(Ordering::SeqCst, &guard);
		let refreshed = match current.as_ref() {
			Some(current_scope) => refresh_scope_value(provider, current_scope),
			None => provider.create_scope(),
		};
		match scope.compare_exchange(
			current,
			(Some(Owned::new(Arc::clone(&refreshed))), Tag::None),
			Ordering::SeqCst,
			Ordering::SeqCst,
			&guard,
		) {
			Ok(_) => return Ok(refreshed),
			Err(_) => continue,
		}
	}
}

pub trait ScopeDefault: ScopeProvider + Sized + 'static {
	fn initialize(scope: Arc<Scope>);
	fn update(scope: Arc<Scope>);
	fn set_as_default(self) {
		let provider: Arc<dyn ScopeProvider> = Arc::new(self);
		let _ =
			DEFAULT_SCOPE_PROVIDER.swap((Some(Owned::new(provider)), Tag::None), Ordering::SeqCst);
	}
}

pub trait ScopeProvider: Send + Sync {
	fn provide_scope(&self) -> Result<Arc<Scope>, ServiceNotFoundError>;
}

static DEFAULT_SCOPE_PROVIDER: LazyLock<AtomicOwned<Arc<dyn ScopeProvider>>> =
	LazyLock::new(|| {
		let provider: Arc<dyn ScopeProvider> = Arc::new(GlobalScopeProvider);
		AtomicOwned::new(provider)
	});

pub fn get_default_scope() -> Arc<dyn ScopeProvider> {
	let guard = Guard::new();
	DEFAULT_SCOPE_PROVIDER
		.load(Ordering::SeqCst, &guard)
		.as_ref()
		.map(Arc::clone)
		.expect("Default scope provider not initialized")
}

pub fn get_default_scope_full() -> Arc<dyn ScopeProvider> { get_default_scope() }

pub fn set_default_scope<T: ScopeDefault + 'static>(scope: T) {
	let provider: Arc<dyn ScopeProvider> = Arc::new(scope);
	let _ = DEFAULT_SCOPE_PROVIDER.swap((Some(Owned::new(provider)), Tag::None), Ordering::SeqCst);
}

pub struct GlobalScopeProvider;

impl GlobalScopeProvider {
	pub fn refresh_scope() -> Result<Arc<Scope>, ServiceNotFoundError> {
		let provider = crate::spo().ok_or(ServiceNotFoundError)?;
		Self::refresh_scope_with(&provider)
	}

	pub fn refresh_scope_with(
		provider: &ServiceProvider,
	) -> Result<Arc<Scope>, ServiceNotFoundError> {
		let Some(scope) = GLOBAL_SCOPE.get() else {
			return Err(ServiceNotFoundError);
		};
		refresh_atomic_scope(scope, provider)
	}
}

impl ScopeDefault for GlobalScopeProvider {
	fn initialize(scope: Arc<Scope>) { let _ = GLOBAL_SCOPE.set(AtomicOwned::new(scope)); }

	fn update(new_scope: Arc<Scope>) {
		let Some(scope) = GLOBAL_SCOPE.get() else {
			return;
		};
		let _ = scope.swap((Some(Owned::new(new_scope)), Tag::None), Ordering::SeqCst);
	}
}

impl ScopeProvider for GlobalScopeProvider {
	fn provide_scope(&self) -> Result<Arc<Scope>, ServiceNotFoundError> {
		let Some(scope) = GLOBAL_SCOPE.get() else {
			return Err(ServiceNotFoundError);
		};
		let guard = Guard::new();
		scope
			.load(Ordering::SeqCst, &guard)
			.as_ref()
			.map(Arc::clone)
			.ok_or(ServiceNotFoundError)
	}
}

static GLOBAL_SCOPE: OnceLock<AtomicOwned<Arc<Scope>>> = OnceLock::new();

pub struct ThreadLocalScope;

impl ThreadLocalScope {
	pub fn refresh_scope() -> Result<Arc<Scope>, ServiceNotFoundError> {
		let provider = crate::spo().ok_or(ServiceNotFoundError)?;
		Self::refresh_scope_with(&provider)
	}

	pub fn refresh_scope_with(
		provider: &ServiceProvider,
	) -> Result<Arc<Scope>, ServiceNotFoundError> {
		TL_SCOPE.with(|cell| {
			let Some(scope) = cell.get() else {
				return Err(ServiceNotFoundError);
			};
			refresh_atomic_scope(scope, provider)
		})
	}
}

impl ScopeDefault for ThreadLocalScope {
	fn initialize(scope: Arc<Scope>) { let _ = TL_SCOPE.with(|s| s.set(AtomicOwned::new(scope))); }

	fn update(new_scope: Arc<Scope>) {
		TL_SCOPE.with(|s| {
			let Some(scope) = s.get() else {
				return;
			};
			let _ = scope.swap((Some(Owned::new(new_scope)), Tag::None), Ordering::SeqCst);
		});
	}
}

impl ScopeProvider for ThreadLocalScope {
	fn provide_scope(&self) -> Result<Arc<Scope>, ServiceNotFoundError> {
		TL_SCOPE.with(|s| {
			let Some(scope) = s.get() else {
				return Err(ServiceNotFoundError);
			};
			let guard = Guard::new();
			scope
				.load(Ordering::SeqCst, &guard)
				.as_ref()
				.map(Arc::clone)
				.ok_or(ServiceNotFoundError)
		})
	}
}

thread_local! {
	static TL_SCOPE: OnceLock<AtomicOwned<Arc<Scope>>> = OnceLock::new();
}

#[cfg(feature = "tokio")]
pub mod tasks;