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

from remedapy.decorator import make_data_last

T = TypeVar('T')


@overload
def difference(
    data: Iterable[T],
    other: Iterable[T],
    /,
) -> Iterable[T]: ...


@overload
def difference(
    other: Iterable[T],
    /,
) -> Callable[[Iterable[T]], Iterable[T]]: ...


def difference_helper(
    data: Iterable[T],
    other: Iterable[T],
    /,
) -> Iterable[T]:
    to_omit = Counter(other)
    for i in data:
        if to_omit[i] > 0:
            to_omit[i] -= 1
        else:
            yield i


@make_data_last
def difference(
    iterable: Iterable[T],
    other: Iterable[T],
    /,
) -> Iterable[T] | Callable[[Iterable[T]], Iterable[T]]:
    """
    Given two iterables, yields elements of the first iterable that do not appear in the other iterable.

    The inputs are treated as multi-sets/bags (multiple copies of items are treated as unique items).
    The order of elements is maintained.

    Parameters
    ----------
    iterable : Iterable[T]
        First iterable (positional-only).
    other: Iterable[T]
        Second iterable (positional-only).

    Returns
    -------
    Iterable[T]
        Iterable of elements of the first iterable that do not appear in the other iterable.

    Examples
    --------
    Data first:
    >>> list(R.difference([1, 2, 3, 4], [2, 5, 3]))
    [1, 4]
    >>> list(R.difference([1, 1, 2, 2], [1]))
    [1, 2, 2]

    Data last:
    >>> R.pipe([1, 2, 3, 4], R.difference([2, 5, 3]), list)
    [1, 4]
    >>> R.pipe([1, 1, 2, 2], R.difference([1]), list)
    [1, 2, 2]

    """
    return difference_helper(iterable, other)
