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

T = TypeVar('T')


@overload
def slice(s: Iterable[T], start: int, end: int, /) -> Iterable[T]: ...


@overload
def slice(s: Iterable[T], start: int, /) -> Iterable[T]: ...


@overload
def slice(start: int, end: int, /) -> Callable[[Iterable[T]], Iterable[T]]: ...


@overload
def slice(start: int, /) -> Callable[[Iterable[T]], Iterable[T]]: ...


@singledispatch
def slice(iterable: Iterable[T], start: int, end: int | None = None, /) -> Iterable[T]:
    """
    Given an iterable and start and end index returns a string from the start index to the end index.

    Works like list[start:end].

    Start index is inclusive, end index is exclusive.

    End is optional if not provided returns the iterable from start to the end of the iterable given.

    Parameters
    ----------
    iterable : Iterable[T]
        Input iterable (positional-only).
    start : int
        Start index (positional-only).
    end : int, optional
        End index (positional-only).

    Returns
    -------
    Iterable[T]
        Iterable from the start index to the end index.

    See Also
    --------
    slice_string
    splice

    Examples
    --------
    Data first:
    >>> list(R.slice(range(5), 2))
    [2, 3, 4]
    >>> list(R.slice([10, 20, 30, 40, 50], 2, 5))
    [30, 40, 50]
    >>> ''.join(R.slice('abcdefghijkl', 1))
    'bcdefghijkl'
    >>> ''.join(R.slice('abcdefghijkl', 4, 7))
    'efg'

    Data last:
    >>> list(R.slice(2)(range(5)))
    [2, 3, 4]
    >>> list(R.slice(2, 5)([10, 20, 30, 40, 50]))
    [30, 40, 50]
    >>> ''.join(R.slice(1)('abcdefghijkl'))
    'bcdefghijkl'
    >>> ''.join(R.slice(4, 7)('abcdefghijkl'))
    'efg'

    """
    iterable_ = iter(iterable)
    for _ in range(start):
        next(iterable_)
    if end:
        for _ in range(end - start):
            yield next(iterable_)
    else:
        yield from iterable_


@slice.register  # pyright: ignore[reportFunctionMemberAccess]
def _(start: int, end: int | None = None, /) -> Callable[[Iterable[T]], Iterable[T]]:
    return lambda s: slice(s, start, end)  # pyright: ignore[reportArgumentType]
