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

from remedapy.decorator import make_data_last

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


@overload
def sum_by(data: Iterable[T], fn: Callable[[T], TNum], /) -> TNum: ...


@overload
def sum_by(fn: Callable[[T], TNum], /) -> Callable[[Iterable[T]], TNum]: ...


@make_data_last
def sum_by(iterable: Iterable[T], fn: Callable[[T], TNum], /) -> TNum:
    """
    Given an iterable and a function, returns the sum of the results of applying the function to each element.

    Parameters
    ----------
    iterable : Iterable[T]
        Iterable to sum (positional-only).
    fn : Callable[[T], TNum]
        Function to apply to each element (positional-only).

    Returns
    -------
    int | float
        Sum of the results of applying the function to each element.

    Examples
    --------
    Data first:
    >>> R.sum_by([{'a': 5}, {'a': 1}, {'a': 3}], R.prop('a'))
    9

    Data last:
    >>> R.pipe([{'a': 5}, {'a': 1}, {'a': 3}], R.sum_by(R.prop('a')))
    9

    """
    sum_ = 0
    for item in iterable:
        sum_ += fn(item)
    return sum_
