"""
Backend registry / factory。

built-in backend は lazy import するため、CuPy や dpnp が未インストールでも
NumPy backend だけを問題なく利用できる。
"""

from __future__ import annotations

from collections.abc import Callable
from importlib import import_module
from typing import Any

from .base import Backend


Factory = Callable[..., Backend]


def _load_numpy(**kwargs) -> Backend:
    from .backends.numpy_backend import NumpyBackend
    return NumpyBackend(**kwargs)


def _load_cupy(**kwargs) -> Backend:
    from .backends.cupy_backend import CupyBackend
    return CupyBackend(**kwargs)


def _load_dpnp(**kwargs) -> Backend:
    from .backends.dpnp_backend import DpnpBackend
    return DpnpBackend(**kwargs)


_BACKENDS: dict[str, Factory] = {
    "numpy": _load_numpy,
    "cupy": _load_cupy,
    "dpnp": _load_dpnp,
}

# 以前の cpu/cuda 指定から移行しやすくするための alias。
_ALIASES = {
    "cpu": "numpy",
    "cuda": "cupy",
}


def register_backend(
    name: str,
    factory: Factory,
    *,
    replace: bool = False,
) -> None:
    """
    独自 backend を registry に追加する。

    Example
    -------
        register_backend("my_backend", MyBackend)
        bk = get_backend("my_backend")
    """
    key = name.strip().lower()

    if not key:
        raise ValueError("backend name must not be empty")

    if key in _BACKENDS and not replace:
        raise ValueError(f"backend {key!r} is already registered")

    _BACKENDS[key] = factory


def backend_names(*, include_aliases: bool = False) -> tuple[str, ...]:
    names = sorted(_BACKENDS)
    if include_aliases:
        names += sorted(_ALIASES)
    return tuple(names)


def get_backend(name: str = "numpy", **kwargs: Any) -> Backend:
    """
    Backend instance を生成する。

    Examples
    --------
        get_backend("numpy", workers=-1)
        get_backend("cupy", device_id=0)
        get_backend("dpnp", device="gpu")
    """
    key = name.strip().lower()
    key = _ALIASES.get(key, key)

    try:
        factory = _BACKENDS[key]
    except KeyError as exc:
        valid = ", ".join(backend_names(include_aliases=True))
        raise ValueError(
            f"Unknown backend {name!r}. Available names: {valid}"
        ) from exc

    return factory(**kwargs)


def available_backends() -> dict[str, bool]:
    """
    built-in backend が現在の環境で初期化できるかを簡易 probe する。

    dpnp は default 'gpu' selector、CuPy は device 0 を probe する。
    """
    result: dict[str, bool] = {}

    for name in backend_names():
        try:
            _BACKENDS[name]()
            result[name] = True
        except Exception:
            result[name] = False

    return result
