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

from remedapy.decorator import make_data_last

T = TypeVar('T')
Acc = TypeVar('Acc')


@overload
def fold(data: Iterable[T], callbackfn: Callable[[Acc, T], Acc], initial_value: Acc, /) -> Acc: ...


@overload
def fold(callbackfn: Callable[[Acc, T], Acc], initial_value: Acc, /) -> Callable[[Iterable[T]], Acc]: ...


@make_data_last
def fold(
    iterable: Iterable[T],
    callbackfn: Callable[[Acc, T], Acc],
    initial_value: Acc,
    /,
) -> Acc:
    """
    Applies the given function to each element of the iterable in order passing previous result to the function.

    Parameters
    ----------
    iterable: Iterable[T]
        The iterable (positional-only).
    callbackfn: Callable[[Acc, T], Acc]
        The reducer function (positional-only).
    initial_value: Acc
        The initial value (positional-only).

    Returns
    -------
    result: Acc
        The reduced value.

    Examples
    --------
    Data first:
    >>> R.fold([1, 2, 3, 4, 5], lambda a, x: a + x, 100)
    115

    Data last:
    >>> R.fold(R.add, 100)([1, 2, 3, 4, 5])
    115
    >>> R.pipe([1, 2, 3, 4, 5], R.fold(R.add, 100))
    115

    """
    for item in iterable:
        initial_value = callbackfn(initial_value, item)
    return initial_value
