__all__ = ["Dims", "Dtype", "attrs", "dims", "dtype", "name", "node", "parse", "use"]

# standard library
from collections.abc import Hashable, Iterable
from typing import Annotated, Any, Literal, TypeVar

# dependencies
import pandas as pd
from typespecs import ITSELF, Spec, SpecFrame, from_annotated
from typing_extensions import get_args, get_origin

# type hints
T = TypeVar("T")


Dims = Annotated[T, Spec(xarray_dims=ITSELF)]
"""Type hint for xarray dimensions."""


Dtype = Annotated[T, Spec(xarray_dtype=ITSELF)]
"""Type hint for xarray data type."""


Use = Literal["attr", "attrs", "coord", "coords", "data", "vars"]
"""Type hint for xarray use."""


def attrs(attrs: dict[Any, Any] | None, /) -> Spec:
    """Returns a type specification for xarray attributes."""
    return Spec(xarray_attrs=attrs)


def dims(dims: Iterable[str] | str | None, /) -> Spec:
    """Returns a type specification for xarray dimensions."""
    return Spec(xarray_dims=dims)


def dtype(dtype: Any | None, /) -> Spec:
    """Returns a type specification for xarray data type."""
    return Spec(xarray_dtype=dtype)


def name(name: Hashable | None, /) -> Spec:
    """Returns a type specification for xarray name."""
    return Spec(xarray_name=name)


def node(node: str | None, /) -> Spec:
    """Returns a type specification for xarray node."""
    return Spec(xarray_node=node)


def use(use: Use, /) -> Spec:
    """Returns a type specification for xarray use."""
    return Spec(xarray_use=use)


def parse(obj: Any, /) -> SpecFrame:
    """Returns a specification DataFrame for xarray."""
    specs = from_annotated(
        obj,
        default={
            "xarray_attrs": None,
            "xarray_dims": None,
            "xarray_dtype": None,
            "xarray_name": None,
            "xarray_node": "/",
            "xarray_use": pd.NA,
        },
    )

    index = specs.index.to_series()
    specs["xarray_dims"] = specs["xarray_dims"].apply(to_dims)
    specs["xarray_name"] = specs["xarray_name"].fillna(index)
    return specs


def to_dims(obj: Any, /) -> tuple[str, ...] | None:
    """Convert a dimensions-like object to dimensions."""
    if obj is None:
        return None

    try:
        return (to_str(obj),)
    except TypeError:
        return tuple(map(to_str, to_tuple(obj)))


def to_str(obj: Any, /) -> str:
    """Convert a string-like object to a string."""
    if get_origin(obj) is Literal:
        return str(get_args(obj)[0])

    if isinstance(obj, str):
        return str(obj)

    raise TypeError(f"Cannot convert {obj!r} to string.")


def to_tuple(obj: Any, /) -> tuple[Any, ...]:
    """Convert a tuple-like object to a tuple."""
    if get_origin(obj) is tuple:
        return tuple(get_args(obj))

    if isinstance(obj, Iterable):
        return tuple(obj)  # type: ignore

    raise TypeError(f"Cannot convert {obj!r} to tuple.")
