from __future__ import annotations
import sys
from importlib.machinery import ModuleSpec, PathFinder
from importlib.machinery import all_suffixes as module_suffixes
from importlib.util import spec_from_file_location
from itertools import chain
from pathlib import Path

MAPPING: dict[str, str] = {'acp_adapter': '/tmp/hermes-agent/acp_adapter', 'agent': '/tmp/hermes-agent/agent', 'batch_runner': '/tmp/hermes-agent/batch_runner', 'cli': '/tmp/hermes-agent/cli', 'cron': '/tmp/hermes-agent/cron', 'gateway': '/tmp/hermes-agent/gateway', 'hermes_cli': '/tmp/hermes-agent/hermes_cli', 'hermes_constants': '/tmp/hermes-agent/hermes_constants', 'hermes_logging': '/tmp/hermes-agent/hermes_logging', 'hermes_state': '/tmp/hermes-agent/hermes_state', 'hermes_time': '/tmp/hermes-agent/hermes_time', 'model_tools': '/tmp/hermes-agent/model_tools', 'plugins': '/tmp/hermes-agent/plugins', 'rl_cli': '/tmp/hermes-agent/rl_cli', 'run_agent': '/tmp/hermes-agent/run_agent', 'tools': '/tmp/hermes-agent/tools', 'toolset_distributions': '/tmp/hermes-agent/toolset_distributions', 'toolsets': '/tmp/hermes-agent/toolsets', 'trajectory_compressor': '/tmp/hermes-agent/trajectory_compressor', 'tui_gateway': '/tmp/hermes-agent/tui_gateway', 'utils': '/tmp/hermes-agent/utils'}
NAMESPACES: dict[str, list[str]] = {'plugins.observability': ['/tmp/hermes-agent/plugins/observability'], 'plugins.example-dashboard': ['/tmp/hermes-agent/plugins/example-dashboard'], 'plugins.strike-freedom-cockpit': ['/tmp/hermes-agent/plugins/strike-freedom-cockpit'], 'plugins.image_gen': ['/tmp/hermes-agent/plugins/image_gen'], 'plugins.example-dashboard.dashboard': ['/tmp/hermes-agent/plugins/example-dashboard/dashboard'], 'plugins.example-dashboard.dashboard.dist': ['/tmp/hermes-agent/plugins/example-dashboard/dashboard/dist'], 'plugins.strike-freedom-cockpit.dashboard': ['/tmp/hermes-agent/plugins/strike-freedom-cockpit/dashboard'], 'plugins.strike-freedom-cockpit.theme': ['/tmp/hermes-agent/plugins/strike-freedom-cockpit/theme'], 'plugins.strike-freedom-cockpit.dashboard.dist': ['/tmp/hermes-agent/plugins/strike-freedom-cockpit/dashboard/dist'], 'tools.neutts_samples': ['/tmp/hermes-agent/tools/neutts_samples']}
PATH_PLACEHOLDER = '__editable__.hermes_agent-0.11.0.finder' + ".__path_hook__"


class _EditableFinder:  # MetaPathFinder
    @classmethod
    def find_spec(cls, fullname: str, path=None, target=None) -> ModuleSpec | None:  # type: ignore
        # Top-level packages and modules (we know these exist in the FS)
        if fullname in MAPPING:
            pkg_path = MAPPING[fullname]
            return cls._find_spec(fullname, Path(pkg_path))

        # Handle immediate children modules (required for namespaces to work)
        # To avoid problems with case sensitivity in the file system we delegate
        # to the importlib.machinery implementation.
        parent, _, child = fullname.rpartition(".")
        if parent and parent in MAPPING:
            return PathFinder.find_spec(fullname, path=[MAPPING[parent]])

        # Other levels of nesting should be handled automatically by importlib
        # using the parent path.
        return None

    @classmethod
    def _find_spec(cls, fullname: str, candidate_path: Path) -> ModuleSpec | None:
        init = candidate_path / "__init__.py"
        candidates = (candidate_path.with_suffix(x) for x in module_suffixes())
        for candidate in chain([init], candidates):
            if candidate.exists():
                return spec_from_file_location(fullname, candidate)
        return None


class _EditableNamespaceFinder:  # PathEntryFinder
    @classmethod
    def _path_hook(cls, path) -> type[_EditableNamespaceFinder]:
        if path == PATH_PLACEHOLDER:
            return cls
        raise ImportError

    @classmethod
    def _paths(cls, fullname: str) -> list[str]:
        paths = NAMESPACES[fullname]
        if not paths and fullname in MAPPING:
            paths = [MAPPING[fullname]]
        # Always add placeholder, for 2 reasons:
        # 1. __path__ cannot be empty for the spec to be considered namespace.
        # 2. In the case of nested namespaces, we need to force
        #    import machinery to query _EditableNamespaceFinder again.
        return [*paths, PATH_PLACEHOLDER]

    @classmethod
    def find_spec(cls, fullname: str, target=None) -> ModuleSpec | None:  # type: ignore
        if fullname in NAMESPACES:
            spec = ModuleSpec(fullname, None, is_package=True)
            spec.submodule_search_locations = cls._paths(fullname)
            return spec
        return None

    @classmethod
    def find_module(cls, _fullname) -> None:
        return None


def install():
    if not any(finder == _EditableFinder for finder in sys.meta_path):
        sys.meta_path.append(_EditableFinder)

    if not NAMESPACES:
        return

    if not any(hook == _EditableNamespaceFinder._path_hook for hook in sys.path_hooks):
        # PathEntryFinder is needed to create NamespaceSpec without private APIS
        sys.path_hooks.append(_EditableNamespaceFinder._path_hook)
    if PATH_PLACEHOLDER not in sys.path:
        sys.path.append(PATH_PLACEHOLDER)  # Used just to trigger the path hook
