"""AlgorithmRegistry - registers algorithms and resolves dependencies via topological sort."""
from __future__ import annotations
from collections import defaultdict
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from app.engine.base import BaseAlgorithm
    from app.engine.context import AlgorithmContext


class AlgorithmRegistry:
    """Central registry for all algorithms.

    Handles:
    - Registration of algorithm instances
    - Dependency resolution (topological sort)
    - Enable/disable algorithms via settings
    - Execution in correct order
    """

    def __init__(self):
        self._algorithms: dict[str, 'BaseAlgorithm'] = {}
        self._disabled: set[str] = set()
        self._sorted_order: list[str] | None = None

    def register(self, algorithm: 'BaseAlgorithm'):
        """Register an algorithm instance."""
        self._algorithms[algorithm.algorithm_id] = algorithm
        self._sorted_order = None  # Invalidate cache

    def disable(self, algorithm_id: str):
        """Disable an algorithm (skip during pipeline execution)."""
        self._disabled.add(algorithm_id)

    def enable(self, algorithm_id: str):
        """Re-enable a previously disabled algorithm."""
        self._disabled.discard(algorithm_id)

    def get(self, algorithm_id: str) -> 'BaseAlgorithm | None':
        return self._algorithms.get(algorithm_id)

    def list_algorithms(self) -> list[dict]:
        """List all registered algorithms with metadata."""
        return [
            {
                'id': a.algorithm_id,
                'name': a.display_name,
                'category': a.category,
                'version': a.version,
                'dependencies': a.dependencies,
                'enabled': a.algorithm_id not in self._disabled,
            }
            for a in self._algorithms.values()
        ]

    # ------------------------------------------------------------------
    # Dependency resolution
    # ------------------------------------------------------------------

    def _topological_sort(self) -> list[str]:
        """Topological sort of algorithms based on dependencies."""
        in_degree: dict[str, int] = defaultdict(int)
        graph: dict[str, list[str]] = defaultdict(list)

        active_ids = {
            aid for aid in self._algorithms if aid not in self._disabled
        }

        for aid in active_ids:
            algo = self._algorithms[aid]
            for dep in algo.dependencies:
                if dep in active_ids:
                    graph[dep].append(aid)
                    in_degree[aid] += 1
            if aid not in in_degree:
                in_degree[aid] = 0

        queue = [aid for aid in active_ids if in_degree[aid] == 0]
        ordered: list[str] = []

        while queue:
            queue.sort()  # deterministic order for same-level algorithms
            node = queue.pop(0)
            ordered.append(node)
            for neighbour in graph[node]:
                in_degree[neighbour] -= 1
                if in_degree[neighbour] == 0:
                    queue.append(neighbour)

        if len(ordered) != len(active_ids):
            missing = active_ids - set(ordered)
            raise ValueError(
                f'Circular dependency detected among: {missing}'
            )

        return ordered

    def get_execution_order(self) -> list[str]:
        """Get the resolved execution order (cached)."""
        if self._sorted_order is None:
            self._sorted_order = self._topological_sort()
        return self._sorted_order

    def run_all(self, ctx: 'AlgorithmContext') -> 'AlgorithmContext':
        """Execute all enabled algorithms in dependency order."""
        for aid in self.get_execution_order():
            algo = self._algorithms[aid]
            try:
                ctx = algo.compute(ctx)
                ctx.algorithm_versions[aid] = algo.version

                contribution = algo.get_signal_contribution(ctx)
                if contribution:
                    ctx.signal_contributions[aid] = contribution
            except Exception as e:
                ctx.errors.append({'algorithm': aid, 'error': str(e)})

        return ctx
