from typing import Optional
from typing import Callable
import functools
import warnings


def deprecated_function(
    last_version: Optional[str] = None,
    msg: Optional[str] = None,
    stacklevel: int = 2,
) -> Callable:
    """A function or method decorator to show deprecation warning.

    Args:
        last_version: The Qiskit Experiment version that this function is removed.
        msg: Extra message, for example, to indicate alternative approach.
        stacklevel: Stacklevel of this warning. See Python Warnings documentation for details.

    Examples:

        .. code-block::

            @deprecated_function(last_version="0.3", msg="Use new_function instead.")
            def old_function(*args, **kwargs):
                pass

            def new_function(*args, **kwargs):
                pass

    Returns:
        Deprecated function or method.
    """

    def deprecated_wrapper(func: Callable):
        @functools.wraps(func)
        def _wrap(*args, **kwargs):
            namespace = func.__qualname__.split(".")
            if len(namespace) == 1:
                message = f"The function '{func.__name__}' has been deprecated and "
            else:
                cls_name, meth_name = namespace
                message = f"The method '{meth_name}' of '{cls_name}' class has been deprecated and "
            if last_version:
                message += f"will be removed after Qiskit Experiments {last_version}. "
            else:
                message += "will be removed in future release. "
            if msg:
                message += msg
            warnings.warn(message, DeprecationWarning, stacklevel=stacklevel)
            return func(*args, **kwargs)

        return _wrap

    return deprecated_wrapper