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 mean_by(data: Iterable[T], fn: Callable[[T], float | int], /) -> float: ...


@overload
def mean_by(fn: Callable[[T], float | int], /) -> Callable[[Iterable[T]], float]: ...


@make_data_last
def mean_by(iterable: Iterable[T], callbackfn: Callable[[T], float | int], /) -> float:
    """
    Given an iterable and a function returns the mean result of applying the function to the elements of the iterable.

    Parameters
    ----------
    iterable : iterable
        Iterable to sum (positional-only).
    callbackfn: Callable[[T], float | int]
        The function to apply to each element of the iterable (positional-only).

    Returns
    -------
    float
        The mean.

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

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

    """
    sum_ = 0
    len_ = 0
    for item in iterable:
        sum_ += callbackfn(item)
        len_ += 1
    return sum_ / len_ if len_ > 0 else 0
