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

from .decorator import make_data_last

T = TypeVar('T')


@overload
def find(data: Iterable[T], predicate: Callable[[T], bool], /) -> T | None: ...


@overload
def find(predicate: Callable[[T], bool], /) -> Callable[[Iterable[T]], T | None]: ...


@make_data_last
def find(iterable: Iterable[T], predicate: Callable[[T], bool], /):
    """
    Returns the first element of the iterable that satisfies the predicate.

    If no element does returns None.

    Parameters
    ----------
    iterable : Iterable[T]
        Input iterable (positional-only).
    predicate: Callable[[T], bool]
        Predicate to check the elements of the iterable (positional-only).

    Returns
    -------
    T | None
        First element of the iterable that satisfies the predicate or None.

    Examples
    --------
    Data first:
    >>> R.find([1, 3, 4, 6], R.is_even)
    4
    >>> R.find([1, 3, 4, 6], lambda x: x % 2 == 0)
    4
    >>> R.find([1, 3, 4, 6], lambda x: 1 < x < 4)
    3
    >>> R.find([1, 3, 4, 6], lambda x: x > 8)

    Data last:
    >>> R.find(R.is_even)([1, 3, 4, 6])
    4
    >>> R.pipe([1, 2, 3, 4, 3, 8, 1], R.find(R.is_even))
    2

    """
    for item in iterable:
        if predicate(item):
            return item
    return None
