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

from .decorator import make_data_last

T = TypeVar('T')


@overload
def splice(items: Iterable[T], start: int, delete_count: int, replacement: Iterable[T], /) -> Iterable[T]: ...


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


@make_data_last
def splice(iterable: Iterable[T], start: int, delete_count: int, replacement: Iterable[T], /) -> Iterable[T]:
    """
    Removes elements from an iterable and inserts new elements in their place.

    Start index will be removed.

    Parameters
    ----------
    iterable : Iterable[T]
        Input iterable (positional-only).
    start : int
        Removal start index (positional-only).
    delete_count : int
        Number of elements to remove (positional-only).
    replacement : Iterable[T]
        Iterable of elements to insert (positional-only).

    Returns
    -------
    Iterable[T]
        Iterable with removed elements and inserted new elements.

    See Also
    --------
    slice

    Examples
    --------
    Data first:
    >>> list(R.splice([1, 2, 3, 4, 5, 6, 7, 8], 2, 3, []))
    [1, 2, 6, 7, 8]
    >>> list(R.splice([1, 2, 3, 4, 5, 6, 7, 8], 2, 3, [9, 10]))
    [1, 2, 9, 10, 6, 7, 8]

    Data last:
    >>> R.pipe([1, 2, 3, 4, 5, 6, 7, 8], R.splice(2, 3, []), list)
    [1, 2, 6, 7, 8]
    >>> R.pipe([1, 2, 3, 4, 5, 6, 7, 8], R.splice(2, 3, [9, 10]), list)
    [1, 2, 9, 10, 6, 7, 8]
    >>> R.pipe((x for x in [1, 2, 3, 4, 5, 6, 7, 8]), R.splice(2, 3, []), list)
    [1, 2, 6, 7, 8]
    >>> R.pipe((x for x in [1, 2, 3, 4, 5, 6, 7, 8]), R.splice(2, 3, [9, 10]), list)
    [1, 2, 9, 10, 6, 7, 8]

    """
    iterable = iter(iterable)
    i = 0
    while i < start:
        yield next(iterable)
        i += 1
    yield from replacement
    for _ in range(delete_count):
        next(iterable)
    yield from iterable
