use dioxus_core::ScopeId;
use generational_box::BorrowResult;
use std::{any::Any, cell::RefCell, collections::HashMap, ops::Deref, panic::Location, rc::Rc};
mod memo;
pub use memo::*;
mod signal;
pub use signal::*;
use crate::{Readable, ReadableRef, Signal, Writable, WritableRef};
pub trait InitializeFromFunction<T> {
    fn initialize_from_function(f: fn() -> T) -> Self;
}
impl<T> InitializeFromFunction<T> for T {
    fn initialize_from_function(f: fn() -> T) -> Self {
        f()
    }
}
pub struct Global<T, R = T> {
    constructor: fn() -> R,
    key: GlobalKey<'static>,
    phantom: std::marker::PhantomData<fn() -> T>,
}
impl<T: Clone + 'static, R: Clone + 'static> Deref for Global<T, R>
where
    T: Readable<Target = R> + InitializeFromFunction<R>,
{
    type Target = dyn Fn() -> R;
    fn deref(&self) -> &Self::Target {
        unsafe { Readable::deref_impl(self) }
    }
}
impl<T: Clone + 'static, R: 'static> Readable for Global<T, R>
where
    T: Readable<Target = R> + InitializeFromFunction<R>,
{
    type Target = R;
    type Storage = T::Storage;
    #[track_caller]
    fn try_read_unchecked(
        &self,
    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
        self.resolve().try_read_unchecked()
    }
    #[track_caller]
    fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>> {
        self.resolve().try_peek_unchecked()
    }
}
impl<T: Clone + 'static, R: 'static> Writable for Global<T, R>
where
    T: Writable<Target = R> + InitializeFromFunction<R>,
{
    type Mut<'a, Read: ?Sized + 'static> = T::Mut<'a, Read>;
    fn map_mut<I: ?Sized, U: ?Sized + 'static, F: FnOnce(&mut I) -> &mut U>(
        ref_: Self::Mut<'_, I>,
        f: F,
    ) -> Self::Mut<'_, U> {
        T::map_mut(ref_, f)
    }
    fn try_map_mut<
        I: ?Sized + 'static,
        U: ?Sized + 'static,
        F: FnOnce(&mut I) -> Option<&mut U>,
    >(
        ref_: Self::Mut<'_, I>,
        f: F,
    ) -> Option<Self::Mut<'_, U>> {
        T::try_map_mut(ref_, f)
    }
    fn downcast_lifetime_mut<'a: 'b, 'b, Read: ?Sized + 'static>(
        mut_: Self::Mut<'a, Read>,
    ) -> Self::Mut<'b, Read> {
        T::downcast_lifetime_mut(mut_)
    }
    #[track_caller]
    fn try_write_unchecked(
        &self,
    ) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError> {
        self.resolve().try_write_unchecked()
    }
}
impl<T: Clone + 'static, R: 'static> Global<T, R>
where
    T: Writable<Target = R> + InitializeFromFunction<R>,
{
    pub fn write(&self) -> T::Mut<'static, R> {
        self.resolve().try_write_unchecked().unwrap()
    }
    #[track_caller]
    pub fn with_mut<O>(&self, f: impl FnOnce(&mut R) -> O) -> O {
        self.resolve().with_mut(f)
    }
}
impl<T: Clone + 'static, R> Global<T, R>
where
    T: InitializeFromFunction<R>,
{
    #[track_caller]
    pub const fn new(constructor: fn() -> R) -> Self {
        let key = std::panic::Location::caller();
        Self {
            constructor,
            key: GlobalKey::new(key),
            phantom: std::marker::PhantomData,
        }
    }
    #[track_caller]
    pub const fn with_name(constructor: fn() -> R, key: &'static str) -> Self {
        Self {
            constructor,
            key: GlobalKey::File {
                file: key,
                line: 0,
                column: 0,
                index: 0,
            },
            phantom: std::marker::PhantomData,
        }
    }
    #[track_caller]
    pub const fn with_location(
        constructor: fn() -> R,
        file: &'static str,
        line: u32,
        column: u32,
        index: usize,
    ) -> Self {
        Self {
            constructor,
            key: GlobalKey::File {
                file,
                line: line as _,
                column: column as _,
                index: index as _,
            },
            phantom: std::marker::PhantomData,
        }
    }
    pub fn key(&self) -> GlobalKey<'static> {
        self.key.clone()
    }
    pub fn resolve(&self) -> T {
        let key = self.key();
        let context = get_global_context();
        {
            let read = context.map.borrow();
            if let Some(signal) = read.get(&key) {
                return signal.downcast_ref::<T>().cloned().unwrap();
            }
        }
        let signal = ScopeId::ROOT.in_runtime(|| T::initialize_from_function(self.constructor));
        context
            .map
            .borrow_mut()
            .insert(key, Box::new(signal.clone()));
        signal
    }
    pub fn origin_scope(&self) -> ScopeId {
        ScopeId::ROOT
    }
}
#[derive(Clone, Default)]
pub struct GlobalLazyContext {
    map: Rc<RefCell<HashMap<GlobalKey<'static>, Box<dyn Any>>>>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum GlobalKey<'a> {
    File {
        file: &'a str,
        line: u32,
        column: u32,
        index: u32,
    },
    Raw(&'a str),
}
impl<'a> GlobalKey<'a> {
    pub const fn new(key: &'a Location<'a>) -> Self {
        GlobalKey::File {
            file: key.file(),
            line: key.line(),
            column: key.column(),
            index: 0,
        }
    }
}
impl From<&'static Location<'static>> for GlobalKey<'static> {
    fn from(key: &'static Location<'static>) -> Self {
        Self::new(key)
    }
}
impl GlobalLazyContext {
    pub fn get_signal_with_key<T>(&self, key: GlobalKey) -> Option<Signal<T>> {
        self.map.borrow().get(&key).map(|f| {
            *f.downcast_ref::<Signal<T>>().unwrap_or_else(|| {
                panic!(
                    "Global signal with key {:?} is not of the expected type. Keys are {:?}",
                    key,
                    self.map.borrow().keys()
                )
            })
        })
    }
}
pub fn get_global_context() -> GlobalLazyContext {
    match ScopeId::ROOT.has_context() {
        Some(context) => context,
        None => ScopeId::ROOT.provide_context(Default::default()),
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_global_keys() {
        const MYSIGNAL: GlobalSignal<i32> = GlobalSignal::new(|| 42);
        const MYSIGNAL2: GlobalSignal<i32> = GlobalSignal::new(|| 42);
        const MYSIGNAL3: GlobalSignal<i32> = GlobalSignal::with_name(|| 42, "custom-keyed");
        let a = MYSIGNAL.key();
        let b = MYSIGNAL.key();
        let c = MYSIGNAL.key();
        assert_eq!(a, b);
        assert_eq!(b, c);
        let d = MYSIGNAL2.key();
        assert_ne!(a, d);
        let e = MYSIGNAL3.key();
        assert_ne!(a, e);
    }
}