"""This is a copy of sklearn/utils/_available_if.py. It can be removed when
we support scikit-learn >= 1.1.
"""
# mypy: ignore-errors

from functools import update_wrapper, wraps
from types import MethodType

import sklearn
from sklearn.utils import parse_version

sklearn_version = parse_version(sklearn.__version__)

if sklearn_version < parse_version("1.1"):

    class _AvailableIfDescriptor:
        """Implements a conditional property using the descriptor protocol.

        Using this class to create a decorator will raise an ``AttributeError``
        if check(self) returns a falsey value. Note that if check raises an error
        this will also result in hasattr returning false.

        See https://docs.python.org/3/howto/descriptor.html for an explanation of
        descriptors.
        """

        def __init__(self, fn, check, attribute_name):
            self.fn = fn
            self.check = check
            self.attribute_name = attribute_name

            # update the docstring of the descriptor
            update_wrapper(self, fn)

        def __get__(self, obj, owner=None):
            attr_err = AttributeError(
                f"This {owner.__name__!r} has no attribute {self.attribute_name!r}"
            )
            if obj is not None:
                # delegate only on instances, not the classes.
                # this is to allow access to the docstrings.
                if not self.check(obj):
                    raise attr_err
                out = MethodType(self.fn, obj)

            else:
                # This makes it possible to use the decorated method as an
                # unbound method, for instance when monkeypatching.
                @wraps(self.fn)
                def out(*args, **kwargs):
                    if not self.check(args[0]):
                        raise attr_err
                    return self.fn(*args, **kwargs)

            return out

    def available_if(check):
        """An attribute that is available only if check returns a truthy value.

        Parameters
        ----------
        check : callable
            When passed the object with the decorated method, this should return
            a truthy value if the attribute is available, and either return False
            or raise an AttributeError if not available.

        Returns
        -------
        callable
            Callable makes the decorated method available if `check` returns
            a truthy value, otherwise the decorated method is unavailable.

        Examples
        --------
        >>> from sklearn.utils.metaestimators import available_if
        >>> class HelloIfEven:
        ...    def __init__(self, x):
        ...        self.x = x
        ...
        ...    def _x_is_even(self):
        ...        return self.x % 2 == 0
        ...
        ...    @available_if(_x_is_even)
        ...    def say_hello(self):
        ...        print("Hello")
        ...
        >>> obj = HelloIfEven(1)
        >>> hasattr(obj, "say_hello")
        False
        >>> obj.x = 2
        >>> hasattr(obj, "say_hello")
        True
        >>> obj.say_hello()
        Hello
        """
        return lambda fn: _AvailableIfDescriptor(fn, check, attribute_name=fn.__name__)

else:
    from sklearn.utils.metaestimators import available_if  # noqa
