use super::lazy::LazyKeyInner;
use crate::fmt;

#[doc(hidden)]
#[allow_internal_unstable(thread_local_internals)]
#[allow_internal_unsafe]
#[unstable(feature = "thread_local_internals", issue = "none")]
#[rustc_macro_transparency = "semitransparent"]
pub macro thread_local_inner {
    // used to generate the `LocalKey` value for const-initialized thread locals
    (@key $t:ty, const $init:expr) => {{
        #[inline] // see comments below
        #[deny(unsafe_op_in_unsafe_fn)]
        // FIXME: Use `SyncUnsafeCell` instead of allowing `static_mut_refs` lint
        #[cfg_attr(bootstrap, allow(static_mut_ref))]
        #[cfg_attr(not(bootstrap), allow(static_mut_refs))]
        unsafe fn __getit(
            _init: $crate::option::Option<&mut $crate::option::Option<$t>>,
        ) -> $crate::option::Option<&'static $t> {
            const INIT_EXPR: $t = $init;

            // wasm without atomics maps directly to `static mut`, and dtors
            // aren't implemented because thread dtors aren't really a thing
            // on wasm right now
            //
            // FIXME(#84224) this should come after the `target_thread_local`
            // block.
            static mut VAL: $t = INIT_EXPR;
            unsafe { $crate::option::Option::Some(&VAL) }
        }

        unsafe {
            $crate::thread::LocalKey::new(__getit)
        }
    }},

    // used to generate the `LocalKey` value for `thread_local!`
    (@key $t:ty, $init:expr) => {
        {
            #[inline]
            fn __init() -> $t { $init }
            #[inline]
            unsafe fn __getit(
                init: $crate::option::Option<&mut $crate::option::Option<$t>>,
            ) -> $crate::option::Option<&'static $t> {
                static __KEY: $crate::thread::local_impl::Key<$t> =
                    $crate::thread::local_impl::Key::new();

                unsafe {
                    __KEY.get(move || {
                        if let $crate::option::Option::Some(init) = init {
                            if let $crate::option::Option::Some(value) = init.take() {
                                return value;
                            } else if $crate::cfg!(debug_assertions) {
                                $crate::unreachable!("missing default value");
                            }
                        }
                        __init()
                    })
                }
            }

            unsafe {
                $crate::thread::LocalKey::new(__getit)
            }
        }
    },
    ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => {
        $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> =
            $crate::thread::local_impl::thread_local_inner!(@key $t, $($init)*);
    },
}

/// On some targets like wasm there's no threads, so no need to generate
/// thread locals and we can instead just use plain statics!

pub struct Key<T> {
    inner: LazyKeyInner<T>,
}

unsafe impl<T> Sync for Key<T> {}

impl<T> fmt::Debug for Key<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Key").finish_non_exhaustive()
    }
}

impl<T> Key<T> {
    pub const fn new() -> Key<T> {
        Key { inner: LazyKeyInner::new() }
    }

    pub unsafe fn get(&self, init: impl FnOnce() -> T) -> Option<&'static T> {
        // SAFETY: The caller must ensure no reference is ever handed out to
        // the inner cell nor mutable reference to the Option<T> inside said
        // cell. This make it safe to hand a reference, though the lifetime
        // of 'static is itself unsafe, making the get method unsafe.
        let value = unsafe {
            match self.inner.get() {
                Some(ref value) => value,
                None => self.inner.initialize(init),
            }
        };

        Some(value)
    }
}
