from collections.abc import Callable, Iterable
from typing import TypeVar, overload

from .decorator import make_data_last

T = TypeVar('T')
TNum = TypeVar('TNum', int, float)


@overload
def range(end: int, /, *, step: int = 1) -> Callable[[TNum], Iterable[TNum]]: ...


@overload
def range(start: int, end: int, /, *, step: int = 1) -> Iterable[int]: ...


@overload
def range(end: int | float, /, *, step: int | float = 1) -> Callable[[float], Iterable[float]]: ...


@overload
def range(start: int | float, end: int | float, /, *, step: int | float = 1) -> Iterable[float]: ...


@make_data_last
def range(start: int | float, end: int | float, /, *, step: int | float = 1) -> Iterable[int | float]:
    """
    Yields numbers from start to end with given step.

    Start is inclusive, end is exclusive.

    Parameters
    ----------
    start: int | float
        Start of the range (positional-only).
    end: int | float
        End of the range (positional-only).
    step: int | float
        Step of the range(keyword-only, optional).

    Returns
    -------
    Iterable[int | float]
        Iterable of numbers from start to end with given step.

    Examples
    --------
    Data first:
    >>> list(R.range(1, 5))
    [1, 2, 3, 4]
    >>> list(R.range(1.5, 7.8, step=1.2))
    [1.5, 2.7, 3.9..., 5.1..., 6.3..., 7.5...]

    Data last:
    >>> list(R.range(5)(1))
    [1, 2, 3, 4]
    >>> R.pipe(1, R.range(5), list)
    [1, 2, 3, 4]
    >>> R.pipe(1.5, R.range(7.8, step=1.2), R.map(R.round(1)), list)
    [1.5, 2.7, 3.9, 5.1, 6.3, 7.5]

    """
    # TODO: support giving start and end later
    current = start
    while current < end:
        yield current
        current += step
