# This file is part of utils.
#
# Developed for the LSST Data Management System.
# This product includes software developed by the LSST Project
# (https://www.lsst.org).
# See the COPYRIGHT file at the top-level directory of this distribution
# for details of code ownership.
#
# Use of this source code is governed by a 3-clause BSD-style
# license that can be found in the LICENSE file.

from __future__ import annotations

__all__ = ["deprecate_pybind11", "suppress_deprecations"]

import functools
import unittest.mock
import warnings
from collections.abc import Iterator
from contextlib import contextmanager
from typing import Any

import deprecated.sphinx


def deprecate_pybind11(obj: Any, reason: str, version: str, category: type[Warning] = FutureWarning) -> Any:
    """Deprecate a pybind11-wrapped C++ interface function, method or class.

    This needs to use a pass-through Python wrapper so that
    `~deprecated.sphinx.deprecated` can update its docstring; pybind11
    docstrings are native and cannot be modified.

    Note that this is not a decorator; its output must be assigned to
    replace the method being deprecated.

    Parameters
    ----------
    obj : function, method, or class
        The function, method, or class to deprecate.
    reason : `str`
        Reason for deprecation, passed to `~deprecated.sphinx.deprecated`.
    version : 'str'
        Next major version in which the interface will be deprecated,
        passed to `~deprecated.sphinx.deprecated`.
    category : `Warning`
        Warning category, passed to `~deprecated.sphinx.deprecated`.

    Returns
    -------
    obj : function, method, or class
        Wrapped function, method, or class.

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

       ExposureF.getCalib = deprecate_pybind11(ExposureF.getCalib,
               reason="Replaced by getPhotoCalib. (Will be removed in 18.0)",
               version="17.0", category=FutureWarning))
    """

    @functools.wraps(obj)
    def internal(*args: Any, **kwargs: Any) -> Any:
        return obj(*args, **kwargs)

    # Typeshed seems to have an incorrect definition for the deprecated
    # function and believes the category should be
    # Optional[Type[DeprecationWarning]] but this is wrong on two counts:
    # 1. None is not actually allowed.
    # 2. FutureWarning is explicitly allowed but that is not a subclass
    #    of DeprecationWarning.
    # This has been fixed upstream at
    # https://github.com/python/typeshed/pull/593
    # and will pass once that is released.
    return deprecated.sphinx.deprecated(reason=reason, version=version, category=category)(internal)


@contextmanager
def suppress_deprecations(category: type[Warning] = FutureWarning) -> Iterator[None]:
    """Suppress warnings generated by `deprecated.sphinx.deprecated`.

    Naively, one might attempt to suppress these warnings by using
    `~warnings.catch_warnings`. However, `~deprecated.sphinx.deprecated`
    attempts to install its own filter, overriding that. This convenience
    method works around this and properly suppresses the warnings by providing
    a mock `~warnings.simplefilter` for `~deprecated.sphinx.deprecated` to
    call.

    Parameters
    ----------
    category : `Warning` or subclass
        The category of warning to suppress.
    """
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", category)
        with unittest.mock.patch.object(warnings, "simplefilter"):
            yield
