from __future__ import annotations

from collections.abc import Iterable
from importlib.metadata import EntryPoint, entry_points

from govoplan_core.core.modules import ModuleManifest

ENTRY_POINT_GROUP = "govoplan.modules"


class ModuleDiscoveryError(RuntimeError):
    pass


def _load_manifest(entry_point: EntryPoint) -> ModuleManifest:
    loaded = entry_point.load()
    manifest = loaded() if callable(loaded) else loaded
    if not isinstance(manifest, ModuleManifest):
        raise ModuleDiscoveryError(f"Entry point {entry_point.name!r} did not return a ModuleManifest")
    return manifest


def iter_module_entry_points(group: str = ENTRY_POINT_GROUP) -> tuple[EntryPoint, ...]:
    discovered = entry_points()
    if hasattr(discovered, "select"):
        return tuple(discovered.select(group=group))
    return tuple(discovered.get(group, ()))


def discover_module_manifests(
    *,
    enabled_modules: Iterable[str] | None = None,
    group: str = ENTRY_POINT_GROUP,
    ignore_load_errors: bool = False,
) -> tuple[ModuleManifest, ...]:
    enabled = set(enabled_modules) if enabled_modules is not None else None
    manifests: list[ModuleManifest] = []
    for entry_point in iter_module_entry_points(group):
        if enabled is not None and entry_point.name not in enabled:
            continue
        try:
            manifest = _load_manifest(entry_point)
        except ModuleNotFoundError as exc:
            module_root = entry_point.module.split(".", 1)[0]
            if ignore_load_errors and exc.name == module_root:
                continue
            raise
        if enabled is not None and manifest.id not in enabled:
            continue
        manifests.append(manifest)
    return tuple(sorted(manifests, key=lambda item: item.id))
