"""Selecciona el mejor torch device disponible segun preferencia."""

from __future__ import annotations

import warnings


def _cuda_available() -> bool:
    """Retorna True si torch esta instalado y CUDA disponible."""
    try:
        import torch
        return torch.cuda.is_available()
    except ImportError:
        return False


def _mps_available() -> bool:
    """Retorna True si torch esta instalado y MPS (Apple Silicon) disponible."""
    try:
        import torch
        return (
            hasattr(torch.backends, "mps")
            and torch.backends.mps.is_available()
        )
    except ImportError:
        return False


def _cuda_device_count() -> int:
    """Retorna el numero de dispositivos CUDA disponibles."""
    try:
        import torch
        return torch.cuda.device_count() if torch.cuda.is_available() else 0
    except ImportError:
        return 0


def torch_device_select(preference: str = "auto") -> str:
    """Selecciona el torch device optimo segun preferencia y disponibilidad.

    Con preference='auto': elige CUDA si disponible, luego MPS (Apple M1/M2),
    luego CPU. Para preferencias explicitas, valida disponibilidad y hace
    fallback a CPU con advertencia si el device solicitado no esta disponible.

    Args:
        preference: 'auto' | 'cuda' | 'cuda:N' | 'mps' | 'cpu'.
            'auto': detecta automaticamente el mejor device.
            'cuda': usa cuda:0 si disponible, fallback a cpu.
            'cuda:N': usa el dispositivo N si existe, fallback a cpu.
            'mps': usa MPS si disponible (Mac Apple Silicon), fallback a cpu.
            'cpu': siempre retorna 'cpu'.

    Returns:
        String de device para torch: 'cuda:0', 'cuda:N', 'mps' o 'cpu'.
    """
    if preference == "cpu":
        return "cpu"

    if preference == "auto":
        if _cuda_available():
            return "cuda:0"
        if _mps_available():
            return "mps"
        return "cpu"

    if preference == "mps":
        if _mps_available():
            return "mps"
        warnings.warn(
            "MPS no esta disponible en este sistema. Usando 'cpu'.",
            stacklevel=2,
        )
        return "cpu"

    if preference == "cuda":
        if _cuda_available():
            return "cuda:0"
        warnings.warn(
            "CUDA no esta disponible en este sistema. Usando 'cpu'.",
            stacklevel=2,
        )
        return "cpu"

    if preference.startswith("cuda:"):
        try:
            device_idx = int(preference.split(":")[1])
        except (IndexError, ValueError):
            warnings.warn(
                f"Formato de device no valido: '{preference}'. Usando 'cpu'.",
                stacklevel=2,
            )
            return "cpu"

        count = _cuda_device_count()
        if _cuda_available() and device_idx < count:
            return preference
        warnings.warn(
            f"Device '{preference}' no disponible "
            f"(cuda_count={count}). Usando 'cpu'.",
            stacklevel=2,
        )
        return "cpu"

    warnings.warn(
        f"Preferencia desconocida: '{preference}'. Usando 'cpu'.",
        stacklevel=2,
    )
    return "cpu"
