import typing
# optimized_lazy_loader.py

import hashlib
import importlib
import importlib.util
import inspect
import sys
import types
from pathlib import Path
from typing import Any, List, Optional, Tuple, Union


def import_lazy(module_name: str, package: Optional[str] = None) -> types.ModuleType:
    """
    延迟导入模块，直到真正使用时才加载。

    这是一个极简的实现：
    - 如果模块已经加载，直接返回
    - 如果模块未加载，创建一个**极简代理**，仅在第一次属性访问时加载真实模块
    - 加载后，代理完全透明，用户感受不到代理的存在

    Parameters
    ----------
    - `module_name` : `str`
        - The name of the module to import lazily.
    - `package` : `str | None`, optional
        - The package context for relative imports, by default None.

    Returns
    ----------
    - `types.ModuleType`
        - The module (real or proxy).
    """
    # Determine the key under which to register in sys.modules
    if package and module_name.startswith('.'):
        full_name = f'{package}{module_name}'
    else:
        full_name = module_name

    # 如果已经加载，直接返回真实模块
    existing = sys.modules.get(full_name)
    if existing is not None:
        return existing

    # 创建极简代理模块
    class _LazyProxy(types.ModuleType):
        def __init__(self):
            super().__init__(full_name)
            self._loaded = False

        def __getattr__(self, name):
            if not self._loaded:
                # 先从 sys.modules 中移除代理，避免递归
                if full_name in sys.modules:
                    del sys.modules[full_name]

                # 加载真实模块
                real_module = importlib.import_module(module_name, package)

                # 替换 sys.modules 中的代理为真实模块
                sys.modules[full_name] = real_module

                # 更新代理状态
                self._loaded = True
                self._real_module = real_module

            # 返回真实模块的属性
            return getattr(self._real_module, name)

        def __dir__(self):
            if not self._loaded:
                # 触发加载
                self.__getattr__('__name__')  # 访问任意属性以触发加载
            return dir(self._real_module)

    # 创建代理并注册到 sys.modules
    proxy = _LazyProxy()
    sys.modules[full_name] = proxy
    return proxy


def import_relative(
    path: Union[str, Path],
    names: Union[str, typing.Sequence[str], Tuple[str, ...], None] = None,
    *,
    based_on: Union[str, Path, None] = None,
) -> Any:
    """
    Dynamically import a module or attributes from a file-system path, without altering sys.path.

    Resolves `path` relative to `based_on` (or the caller's file location if None),
    loads the module under a unique name derived from its absolute path, and optionally
    returns specific attributes.

    Parameters
    ----------
    - `path` : `str | Path`
        - Relative path to the module file (.py) or package directory.
    - `names` : `str | list[str] | tuple[str, ...] | None`, optional
        - If None, return the module object.
        - If a string, return that attribute from the module.
        - If a list/tuple, return a tuple of the requested attributes.
    - `based_on` : `str | Path | None`, optional
        - Base file or directory for resolving `path`; if None, uses the caller's file.

    Returns
    ----------
    - `Any`
        - The imported module, or the requested attribute(s).

    Raises
    ----------
    - `FileNotFoundError`
        - If the resolved path does not exist.
    - `ImportError`
        - If loading the module spec fails.
    - `AttributeError`
        - If a requested name is not found in the module.
    """
    # 1. Locate base directory
    if based_on is None:
        caller_file = inspect.stack()[1].filename
        base_dir = Path(caller_file).parent
    else:
        base = Path(based_on)
        base_dir = base.parent if base.is_file() else base

    # 2. Resolve target path
    target = base_dir / path
    if not target.exists():
        candidate = target.with_suffix('.py')
        if not candidate.exists():
            raise FileNotFoundError(f'Cannot find module at: {target}')
        target = candidate
    if target.is_dir():
        if not (target / '__init__.py').exists():
            raise ImportError(f'Missing __init__.py in {target}')
        # 对于包，使用 __init__.py 作为模块文件
        abs_path = str(target / '__init__.py')
    else:
        abs_path = str(target.resolve())

    # 3. Create a unique module name and load spec
    unique_name = '_relmod_' + hashlib.sha256(abs_path.encode('utf-8')).hexdigest()
    if unique_name in sys.modules:
        module = sys.modules[unique_name]
    else:
        if target.is_dir():
            # 对于包，需要特殊处理
            spec = importlib.util.spec_from_file_location(
                unique_name, abs_path, submodule_search_locations=[str(target)]
            )
        else:
            spec = importlib.util.spec_from_file_location(unique_name, abs_path)

        if spec is None or spec.loader is None:
            raise ImportError(f'Cannot load module spec from {abs_path}')

        module = importlib.util.module_from_spec(spec)
        # Execute the module in its own namespace
        spec.loader.exec_module(module)  # type: ignore
        sys.modules[unique_name] = module

    # 4. Return module or requested attributes
    if names is None:
        return module
    if isinstance(names, str):
        return getattr(module, names)
    return tuple(getattr(module, n) for n in names)
