"""
Provides utilities for configuring dynamic module namespaces via PEP 562.
"""

import importlib
import importlib.metadata
import importlib.util
import sys
import threading
from collections.abc import Callable, Iterable
from enum import StrEnum, auto
from typing import Any

__all__ = ["ImportMode", "defer_imports"]


class ImportMode(StrEnum):
    """Define the execution behavior for dynamic submodule resolution."""

    DEFERRED = auto()
    LAZY = auto()


def defer_imports(
    name: str,
    modules: Iterable[str],
    /,
    extras: Iterable[str] | None = None,
    mode: ImportMode | str = ImportMode.DEFERRED,
    distribution: str | None = None,
) -> tuple[Callable[[str], Any], Callable[[], list[str]]]:
    """
    Create getattr and dir functions for dynamic module attribute resolution.

    Parameters
    ----------
    name : str
        The absolute name of the parent module.
    modules : Iterable[str]
        Submodules managed by the namespace hooks.
    extras : Iterable[str], optional
        Static attributes to include in the module's directory listing.
    mode : ImportMode | str, default ImportMode.DEFERRED
        The execution model applied upon attribute resolution. DEFERRED executes
        the target module immediately upon access. LAZY defers execution until
        a sub-attribute is accessed.
    distribution : str, optional
        The name of the distribution this module belongs to. If defined, a
        __version__ attribute will be added to the module.

    Returns
    -------
    __getattr__ : Callable[[str], Any]
        A function conforming to the __getattr__ specification.
    __dir__ : Callable[[], list[str]]
        A function conforming to the __dir__ specification.
    """
    module_set = frozenset(modules)
    directory_set = set(module_set) | set(extras or [])
    if distribution is not None:
        directory_set.add("__version__")
    directory = tuple(sorted(directory_set))
    lock = threading.Lock()

    # Fail early on invalid configuration rather than at attribute access time
    try:
        active_mode = ImportMode(mode)
    except ValueError:
        valid_modes = [m.value for m in ImportMode]
        msg = f"Invalid mode {mode!r}. Expected one of: {valid_modes}"
        raise ValueError(msg)  # noqa: B904

    def __getattr__(attr_name: str) -> Any:
        # print(f"DEBUG: __getattr__({name=}, {attr_name=})")
        if attr_name == "__version__" and distribution is not None:
            try:
                return importlib.metadata.version(distribution)
            except importlib.metadata.PackageNotFoundError:
                return "unknown"

        if attr_name in module_set:
            fullname = f"{name}.{attr_name}"
            # print(f"DEBUG: importing {fullname=}")
            module = importlib.import_module(fullname)
            setattr(sys.modules[name], attr_name, module)
            return module

        msg = f"Module {name!r} has no attribute {attr_name!r}"
        raise AttributeError(msg)

    def __dir__() -> list[str]:
        return list(directory)

    return __getattr__, __dir__
