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

from .decorator import make_data_last

if TYPE_CHECKING:
    from _typeshed import SupportsRichComparisonT


T = TypeVar('T')


@overload
def rank_by(
    data: Iterable['T'],
    item: 'SupportsRichComparisonT',
    fn: Callable[['T'], 'SupportsRichComparisonT'],
    /,
) -> int: ...


@overload
def rank_by(
    item: 'SupportsRichComparisonT',
    fn: Callable[['T'], 'SupportsRichComparisonT'],
    /,
) -> Callable[[Iterable['T']], int]: ...


@make_data_last
def rank_by(
    data: Iterable['T'],
    item: 'SupportsRichComparisonT',
    function: Callable[['T'], 'SupportsRichComparisonT'],
    /,
) -> int:
    """
    Calculates the rank of the item in data by function.

    That means how many items in data are less than or equal to the item when mapped with the function.

    Parameters
    ----------
    data: Iterable[T]
        The data to rank.
    item: SupportsRichComparisonT
        The item to rank.
    function: Callable[T, SupportsRichComparisonT]
        The function to apply to each item in data.

    Returns
    -------
    int
        The rank of the item in data. Will be non-negative.

    Examples
    --------
    Data first:
    >>> DATA = [{'a': 5}, {'a': 1}, {'a': 3}]
    >>> R.rank_by(DATA, 0, R.prop('a'))
    0
    >>> R.rank_by(DATA, 1, R.prop('a'))
    1
    >>> R.rank_by(DATA, 2, R.prop('a'))
    1
    >>> R.rank_by(DATA, 3, R.prop('a'))
    2

    Data last:
    >>> R.pipe(DATA, R.rank_by(0, R.prop('a')))
    0
    >>> R.pipe(DATA, R.rank_by(1, R.prop('a')))
    1
    >>> R.pipe(DATA, R.rank_by(2, R.prop('a')))
    1
    >>> R.pipe(DATA, R.rank_by(3, R.prop('a')))
    2

    """
    return sum(1 for x in data if function(x) <= item)  # pyright: ignore[reportOperatorIssue]
