"""CalculatorRegistry — thread-safe registry for metric calculators."""

from __future__ import annotations

import threading
from typing import Any, Callable

from pyfortis._types import MetricResult, MetricSpec, Position
from pyfortis.errors import CalculatorNotFoundError

CalculatorFn = Callable[
    [MetricSpec, dict[str, Position], list[float], dict[str, Any]],
    MetricResult,
]


class CalculatorRegistry:
    """Thread-safe registry for metric calculators."""

    def __init__(self) -> None:
        self._calculators: dict[str, CalculatorFn] = {}
        self._lock = threading.Lock()

    def register(self, name: str, fn: CalculatorFn) -> None:
        with self._lock:
            self._calculators[name] = fn

    def get(self, name: str) -> CalculatorFn | None:
        with self._lock:
            return self._calculators.get(name)

    def has(self, name: str) -> bool:
        with self._lock:
            return name in self._calculators

    def names(self) -> list[str]:
        with self._lock:
            return list(self._calculators.keys())

    def decorator(self, name: str) -> Callable[[CalculatorFn], CalculatorFn]:
        def wrapper(fn: CalculatorFn) -> CalculatorFn:
            self.register(name, fn)
            return fn
        return wrapper

    def compute(
        self,
        metric: MetricSpec,
        positions: dict[str, Position],
        returns_data: list[float],
        context: dict[str, Any],
    ) -> MetricResult:
        fn = self.get(metric.type.value)
        if fn is None:
            raise CalculatorNotFoundError(
                f"No calculator registered for '{metric.type.value}'",
                context={"metric_name": metric.name, "metric_type": metric.type.value},
            )
        return fn(metric, positions, returns_data, context)
