from typing import Callable
from typing import Any


def get_func_name(
    func: Callable[..., Any],
    prepend_module: bool = False,
    repr_fallback: bool = False,
) -> str:
    """Extract and return the name of **func**.

    A total of three attempts are performed at retrieving the passed functions name:

    1. Return the functions qualified name (:attr:`~definition.__qualname__`).
    2. Return the functions name (:attr:`~definition.__name__`).
    3. Return the (called) name of the functions type.

    Examples
    --------
    .. code:: python

        >>> from functools import partial
        >>> from nanoutils import get_func_name

        >>> def func1():
        ...     pass

        >>> class Class():
        ...     def func2(self):
        ...         pass

        >>> func3 = partial(len)

        >>> get_func_name(func1)
        'func1'

        >>> get_func_name(func1, prepend_module=True)  # doctest: +SKIP
        '__main__.func1'

        >>> get_func_name(Class.func2)
        'Class.func2'

        >>> get_func_name(func3)
        'partial(...)'

        >>> get_func_name(func3, repr_fallback=True)
        'functools.partial(<built-in function len>)'


    Parameters
    ----------
    func : :class:`~collections.abc.Callable`
        A callable object.
    prepend_module : :class:`bool`
        If :data:`True` prepend the objects module (:attr:`~definition.__module__`),
        if available, to the to-be returned string.
    repr_fallback : :class:`bool`
        By default, when the passed function has neither a :attr:`~definition.__qualname__` or
        :attr:`~definition.__name__` attribute the (called) name of the functions class is returned.
        If :data:`True` then use :func:`repr` instead.

    Returns
    -------
    :class:`str`
        A string representation of the name of **func**.

    """
    try:
        name: str = getattr(func, '__qualname__', func.__name__)
    except AttributeError as ex:
        if not callable(func):
            raise TypeError("'func' expected a callable; "
                            f"observed type: {func.__class__.__name__!r}") from ex
        if repr_fallback:
            name = repr(func)
        else:
            name = f'{func.__class__.__name__}(...)'

    if prepend_module:
        try:
            return f'{func.__module__}.{name}'
        except AttributeError:
            pass
    return name