#!/usr/bin/env python3
# --------------------( LICENSE                            )--------------------
# Copyright (c) 2014-2025 Beartype authors.
# See "LICENSE" for further details.

'''
Beartype **import path hook context managers** (i.e., data structure caching
package names on behalf of the higher-level :func:`beartype.claw._clawmain`
submodule, which beartype import path hooks internally created by that submodule
subsequently lookup when deciding whether or not (and how) to decorate by
:func:`beartype.beartype` the currently imported user-specific submodule).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.claw._package.clawpkgtrie import (
    remove_beartype_pathhook_unless_packages_trie)
from beartype.typing import (
    Iterator,
    Optional,
)
from beartype._conf.confcommon import BEARTYPE_CONF_DEFAULT
from beartype._conf.confmain import BeartypeConf
from contextlib import contextmanager

# ....................{ CONTEXTS                           }....................
#FIXME: Highly non-ideal. As defined, this context manager currently fails to
#behave as expected by users. This context manager claims to be thread-safe, but
#almost certainly isn't. Why? Because it registers the beartype_all() hook --
#which then applies to *ALL* threads. Ideally, this context manager should
#*ONLY* apply to the current thread of operation.
#FIXME: Unit test us up, please.
@contextmanager
def beartyping(
    # Optional keyword-only parameters.
    *,
    conf: BeartypeConf = BEARTYPE_CONF_DEFAULT,
) -> Iterator[None]:
    '''
    Context manager temporarily registering a new **universal beartype import
    path hook** (i.e., callable inserted to the front of the standard
    :mod:`sys.path_hooks` list recursively decorating *all* typed callables and
    classes of *all* submodules of *all* packages on the first importation of
    those submodules with the :func:`beartype.beartype` decorator, wrapping
    those callables and classes with performant runtime type-checking).

    Specifically, this context manager (in order):

    #. Temporarily registers this hook by calling the public
       :func:`beartype.claw.beartype_all` function.
    #. Runs the body of the caller-defined ``with beartyping(...):`` block.
    #. Unregisters the hook registered by the prior call to that function.

    This context manager is thread-safe.

    Parameters
    ----------
    conf : BeartypeConf, optional
        **Beartype configuration** (i.e., dataclass configuring the
        :mod:`beartype.beartype` decorator for *all* decoratable objects
        recursively decorated by the path hook added by this function).
        Defaults to ``BeartypeConf()``, the default :math:`O(1)` configuration.

    Yields
    ------
    None
        This context manager yields *no* objects.

    Raises
    ------
    BeartypeClawHookException
        If the passed ``conf`` parameter is *not* a beartype configuration
        (i.e., :class:`.BeartypeConf` instance).

    See Also
    --------
    :func:`beartype.claw.beartype_all`
        Arguably unsafer alternative to this function globalizing the effect of
        this function to *all* imports performed anywhere.
    '''

    # Avoid circular import dependencies.
    from beartype.claw import beartype_all
    from beartype.claw._clawstate import (
        claw_lock,
        claw_state,
    )

    # Prior global beartype configuration registered by a prior call to the
    # beartype_all() function if any *OR* "None" otherwise.
    packages_trie_conf_if_hooked_old: Optional[BeartypeConf] = None

    # Attempt to...
    try:
        # With a "beartype.claw"-specific thread-safe reentrant lock...
        with claw_lock:
            # Store the prior global beartype configuration if any.
            packages_trie_conf_if_hooked_old = (
                claw_state.packages_trie_whitelist.conf_if_hooked)

            # Prevent the beartype_all() function from raising an exception on
            # conflicting registrations of beartype configurations.
            claw_state.packages_trie_whitelist.conf_if_hooked = None

        # Globalize the passed beartype configuration.
        beartype_all(conf=conf)

        # Defer to the caller body of the parent "with beartyping(...):" block.
        yield
    # After doing so (regardless of whether doing so raised an exception)...
    finally:
        # With a "beartype.claw"-specific thread-safe reentrant lock...
        with claw_lock:
            # If the current global beartype configuration is still the passed
            # beartype configuration, then the caller's body of the parent "with
            # beartyping(...):" block has *NOT* itself called the beartype_all()
            # function with a conflicting beartype configuration. In this
            # case...
            if claw_state.packages_trie_whitelist.conf_if_hooked == conf:
                # Restore the prior global beartype configuration if any.
                claw_state.packages_trie_whitelist.conf_if_hooked = (
                    packages_trie_conf_if_hooked_old)

                # Possibly remove our beartype import path hook added by the
                # above call to beartype_all() if *NO* packages are registered.
                remove_beartype_pathhook_unless_packages_trie()
            # Else, the caller's body of the parent "with beartyping(...):"
            # block has itself called the beartype_all() function with a
            # conflicting beartype configuration. In this case, preserve that
            # configuration as is.

# ....................{ CONTEXTS ~ test                    }....................
#FIXME: Unit test us up, please.
@contextmanager
def packages_trie_reverted() -> Iterator[None]:
    '''
    Test-specific context manager reverting (i.e., clearing, resetting) *all*
    **import hook global state** (i.e., the contents of the
    :data:`beartype.claw._clawstate.claw_state` global dataclass) back to its
    **prior state** (i.e., the state *before* invoking this context manager)
    *after* running the body of the caller-defined ``with
    packages_trie_reverted(...):`` block.

    This context manager is thread-safe.

    Caveats
    -------
    **This context manager is intentionally hidden from users as a private
    attribute of this submodule** rather than publicly exported. Why? Because
    this context manager is *only* intended to be invoked by unit and
    integration tests in our test suite.

    Yields
    ------
    None
        This context manager yields *no* objects.
    '''
    # print('Clearing "beartype.claw" state...')

    # Avoid circular import dependencies.
    from beartype.claw._clawstate import (
        claw_lock,
        claw_state,
    )

    #FIXME: Uncomment this *AFTER*:
    #* Correctly implementing the copy_deep() method called below. *sigh*
    #* Defining and calling the set_claw_state() function documented below.
    # # With a submodule-specific thread-safe reentrant lock, capture the prior
    # # global state of the "beartype.claw" subpackage *BEFORE* performing the
    # # caller-defined body of the parent "with" statement.
    # with claw_lock:
    #     # Deep copy of the current beartype import hook state, enabling this
    #     # context manager below to transparently restore this state *BEFORE*
    #     # returning.
    #     claw_state_prior = claw_state.copy_deep()

    # Perform the caller-defined body of the parent "with" statement.
    try:
        yield
    # After doing so, regardless of whether doing so raised an exception...
    finally:
        # print(f'claw_state [after test]: {repr(claw_state)}')

        # With a submodule-specific thread-safe reentrant lock...
        with claw_lock:
            #FIXME: *UHM. NO. WE NOW NEED TO RESTORE THE DEEP COPY MADE ABOVE.*
            #This is sorta trivial, but sorta not. For safety, we probably want
            #to define a new *THREAD-SAFE* utility function in the "_clawstate"
            #submodule resembling:
            #    def set_claw_state(claw_state_new: BeartypeClawState) -> None:
            #        assert isinstance(claw_state_new, BeartypeClawState), (
            #            f'{repr(claw_state_new)} not "beartype.claw" state.')
            #
            #        global claw_state
            #
            #        with claw_lock:
            #            claw_state = claw_state_new

            # Restore the global state of the "beartype.claw" subpackage back to
            # its prior state *BEFORE* performing the caller-defined body of the
            # parent "with" statement.
            claw_state.reinit()

            # If *NO* packages are currently hooked by "beartype.claw" import
            # hooks, remove the beartype import path hook added by any import
            # hooks registered by the caller-defined body of the parent "with"
            # statement. Phew!
            remove_beartype_pathhook_unless_packages_trie()
