"""Load user-defined custom strategies from ~/.config/probegpt/strategies/.

Drop a .py file in that directory containing a class that inherits from
probegpt.strategies.base.AbstractStrategy. It will be discovered automatically
at runtime and added to the strategy pool.

Example (~/.config/probegpt/strategies/my_strategy.py):

    from probegpt.strategies.base import AbstractStrategy
    from probegpt.core.models import Probe

    class MyStrategy(AbstractStrategy):
        def get_name(self) -> str:
            return "my_custom"

        def get_description(self) -> str:
            return "My custom attack vector"

        def generate(self, seeds, model, count=5, **kwargs) -> list[str]:
            ...
"""

from __future__ import annotations

import importlib.util
import sys
from pathlib import Path

from probegpt.strategies.base import AbstractStrategy


def load_custom_strategies(directory: Path) -> list[AbstractStrategy]:
    """Discover and instantiate all AbstractStrategy subclasses in directory.

    Returns an empty list if the directory does not exist or contains no
    valid strategy files. Errors in individual files are silently skipped
    so one bad file does not break the whole run.
    """
    strategies: list[AbstractStrategy] = []

    if not directory.exists():
        return strategies

    for path in sorted(directory.glob("*.py")):
        module_name = f"_probegpt_custom_{path.stem}"
        try:
            spec = importlib.util.spec_from_file_location(module_name, path)
            if spec is None or spec.loader is None:
                continue
            module = importlib.util.module_from_spec(spec)
            sys.modules[module_name] = module
            spec.loader.exec_module(module)  # type: ignore[union-attr]

            for attr_name in dir(module):
                obj = getattr(module, attr_name)
                if (
                    isinstance(obj, type)
                    and issubclass(obj, AbstractStrategy)
                    and obj is not AbstractStrategy
                ):
                    try:
                        strategies.append(obj())
                    except TypeError:
                        pass  # Requires constructor args — skip silently
        except Exception:
            pass  # Bad file — skip silently

    return strategies
