import warnings

import torch

from .types import ArrayLike, DeviceLike, Scalar


def infer_device(
    device: DeviceLike | None, *values: ArrayLike | Scalar | None
) -> torch.device:
    if device is not None:
        return torch.device(device)
    for value in values:
        if torch.is_tensor(value):
            return value.device
    return torch.device("cpu")


def infer_dtype(dtype: torch.dtype | None, *values: ArrayLike | None) -> torch.dtype:
    if dtype is not None:
        return dtype
    for value in values:
        if torch.is_tensor(value) and value.dtype.is_floating_point:
            return value.dtype
    return torch.float32


def to_tensor(
    value: ArrayLike | Scalar, *, device: torch.device, dtype: torch.dtype
) -> torch.Tensor:
    if torch.is_tensor(value):
        return value.to(device=device, dtype=dtype)
    return torch.as_tensor(value, device=device, dtype=dtype)


def require_1d(name: str, value: torch.Tensor) -> None:
    if value.ndim != 1:
        raise ValueError(f"{name} must be 1D. Got shape {tuple(value.shape)}.")
    if value.numel() < 2:
        raise ValueError(f"{name} must have at least 2 time points.")
    if not torch.isfinite(value).all():
        raise ValueError(f"{name} contains non-finite values.")
    if torch.any(value[1:] <= value[:-1]):
        raise ValueError(f"{name} must be strictly increasing.")


def normalize_tacs_shape(tacs: torch.Tensor) -> torch.Tensor:
    if tacs.ndim == 1:
        return tacs.unsqueeze(-1)
    if tacs.ndim != 2:
        raise ValueError(f"tacs must be 1D or 2D. Got shape {tuple(tacs.shape)}.")
    return tacs


def convert_time_to_minutes(
    t: torch.Tensor, *, time_unit: str, warn_on_long_minutes: bool = False
) -> torch.Tensor:
    unit = time_unit.lower()
    if unit in {"min", "minute", "minutes", "m"}:
        if warn_on_long_minutes and t.max().item() > 60.0:
            warnings.warn(
                "time_unit='min' and max(time) > 60. assuming minutes and not "
                "rescaling. If your input is seconds, pass time_unit='s'.",
                stacklevel=2,
            )
        return t
    if unit in {"s", "sec", "second", "seconds"}:
        return t / 60.0
    raise ValueError("time_unit must be either 'min' or 's'.")
