from __future__ import annotations

import re
import sys
from dataclasses import dataclass
from inspect import isclass
from pathlib import Path
from textwrap import dedent
from typing import (  # no type comments
    TYPE_CHECKING,
    Any,
    Literal,
    NewType,
    Optional,
    TypeVar,
    Union,
    overload,
)

import pytest
from conftest import normalize_sphinx_text

if TYPE_CHECKING:
    from collections.abc import AsyncGenerator, Callable
    from io import StringIO
    from mailbox import Mailbox
    from types import CodeType, ModuleType

    from sphinx.testing.util import SphinxTestApp

T = TypeVar("T")
W = NewType("W", str)


@dataclass
class WarningInfo:
    """Properties and assertion methods for warnings."""

    regexp: str
    type: str

    def assert_regexp(self, message: str) -> None:
        regexp = self.regexp
        msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {message!r}"
        assert re.search(regexp, message), msg

    def assert_type(self, message: str) -> None:
        expected = f"[{self.type}]"
        msg = f"Warning did not contain type and subtype.\n Expected: {expected}\n Input: {message}"
        assert expected in message, msg

    def assert_warning(self, message: str) -> None:
        self.assert_regexp(message)
        self.assert_type(message)


def expected(expected: str, **options: Any) -> Callable[[T], T]:
    def dec(val: T) -> T:
        val.EXPECTED = expected  # ty: ignore[unresolved-attribute]
        val.OPTIONS = options  # ty: ignore[unresolved-attribute]
        return val

    return dec


def warns(info: WarningInfo) -> Callable[[T], T]:
    def dec(val: T) -> T:
        val.WARNING = info  # ty: ignore[unresolved-attribute]
        return val

    return dec


@expected("mod.get_local_function()")
def get_local_function():  # noqa: ANN201
    def wrapper(self) -> str:  # noqa: ANN001
        """
        Wrapper
        """

    return wrapper


@warns(WarningInfo(regexp="Cannot handle as a local function", type="sphinx_autodoc_typehints.local_function"))
@expected(
    """\
class mod.Class(x, y, z=None)

   Initializer docstring.

   Parameters:
      * **x** ("bool") -- foo

      * **y** ("int") -- bar

      * **z** ("str" | "None") -- baz

   class InnerClass

      Inner class.

      inner_method(x)

         Inner method.

         Parameters:
            **x** ("bool") -- foo

         Return type:
            "str"

   classmethod a_classmethod(x, y, z=None)

      Classmethod docstring.

      Parameters:
         * **x** ("bool") -- foo

         * **y** ("int") -- bar

         * **z** ("str" | "None") -- baz

      Return type:
         "str"

   a_method(x, y, z=None)

      Method docstring.

      Parameters:
         * **x** ("bool") -- foo

         * **y** ("int") -- bar

         * **z** ("str" | "None") -- baz

      Return type:
         "str"

   property a_property: str

      Property docstring

   static a_staticmethod(x, y, z=None)

      Staticmethod docstring.

      Parameters:
         * **x** ("bool") -- foo

         * **y** ("int") -- bar

         * **z** ("str" | "None") -- baz

      Return type:
         "str"

   locally_defined_callable_field() -> str

      Wrapper

      Return type:
         "str"
    """,
)
class Class:
    """
    Initializer docstring.

    :param x: foo
    :param y: bar
    :param z: baz
    """

    def __init__(self, x: bool, y: int, z: Optional[str] = None) -> None: ...

    def a_method(self, x: bool, y: int, z: Optional[str] = None) -> str:
        """
        Method docstring.

        :param x: foo
        :param y: bar
        :param z: baz
        """

    def _private_method(self, x: str) -> str:
        """
        Private method docstring.

        :param x: foo
        """

    def __dunder_method(self, x: str) -> str:
        """
        Dunder method docstring.

        :param x: foo
        """

    def __magic_custom_method__(self, x: str) -> str:  # noqa: PLW3201
        """
        Magic dunder method docstring.

        :param x: foo
        """

    @classmethod
    def a_classmethod(cls, x: bool, y: int, z: Optional[str] = None) -> str:
        """
        Classmethod docstring.

        :param x: foo
        :param y: bar
        :param z: baz
        """

    @staticmethod
    def a_staticmethod(x: bool, y: int, z: Optional[str] = None) -> str:
        """
        Staticmethod docstring.

        :param x: foo
        :param y: bar
        :param z: baz
        """

    @property
    def a_property(self) -> str:
        """
        Property docstring
        """

    class InnerClass:
        """
        Inner class.
        """

        def inner_method(self, x: bool) -> str:
            """
            Inner method.

            :param x: foo
            """

        def __dunder_inner_method(self, x: bool) -> str:
            """
            Dunder inner method.

            :param x: foo
            """

    locally_defined_callable_field = get_local_function()


@expected(
    """\
exception mod.DummyException(message)

   Exception docstring

   Parameters:
      **message** ("str") -- blah
""",
)
class DummyException(Exception):  # noqa: N818
    """
    Exception docstring

    :param message: blah
    """

    def __init__(self, message: str) -> None: ...


@expected(
    """\
mod.function(x, y, z_=None)

   Function docstring.

   Parameters:
      * **x** ("bool") -- foo

      * **y** ("int") -- bar

      * **z_** ("str" | "None") -- baz

   Returns:
      something

   Return type:
      "str"
""",
)
def function(x: bool, y: int, z_: Optional[str] = None) -> str:
    """
    Function docstring.

    :param x: foo
    :param y: bar
    :param z\\_: baz
    :return: something
    :rtype: bytes
    """


@expected(
    """\
mod.function_with_trailing_underscore_numpy(a, lambda_=None)

   Do nothing.

   Parameters:
      * **a** ("int") -- Description for a.

      * **lambda_** ("float" | "None", default: "None") -- Description
        for lambda.

   Return type:
      "None"
""",
    typehints_defaults="comma",
)
def function_with_trailing_underscore_numpy(a: int, lambda_: float | None = None) -> None:
    """Do nothing.

    Parameters
    ----------
    a
        Description for a.
    lambda_
        Description for lambda.
    """


@expected(
    """\
mod.function_with_trailing_underscore_google(a, lambda_=None)

   Do nothing.

   Parameters:
      * **a** ("int") -- Description for a.

      * **lambda_** ("float" | "None", default: "None") -- Description
        for lambda.

   Return type:
      "None"
""",
    typehints_defaults="comma",
)
def function_with_trailing_underscore_google(a: int, lambda_: float | None = None) -> None:
    """Do nothing.

    Args:
        a: Description for a.
        lambda_: Description for lambda.
    """


@expected(
    """\
mod.function_with_starred_documentation_param_names(*args, **kwargs)

   Function docstring.

   Usage:

      print(1)

   Parameters:
      * ***args** ("int") -- foo

      * ****kwargs** ("str") -- bar
""",
)
def function_with_starred_documentation_param_names(*args: int, **kwargs: str):  # noqa: ANN201
    r"""
    Function docstring.

    Usage::

        print(1)

    :param \*args: foo
    :param \**kwargs: bar
    """


@expected(
    """\
mod.function_with_escaped_default(x='\\\\x08')

   Function docstring.

   Parameters:
      **x** ("str") -- foo
""",
)
def function_with_escaped_default(x: str = "\b"):  # noqa: ANN201
    """
    Function docstring.

    :param x: foo
    """


@expected(
    """\
mod.function_with_multiline_param_defaults(x=5, y=10, z=15)

   Function docstring.

   Parameters:
      * **x** ("int") -- optional specifier line 2 (default: "5")

      * **y** ("int") --

        another optional line 4

        second paragraph for y (default: "10")

      * **z** ("int") -- yet another optional s line 6 (default: "15")

   Returns:
      something

   Return type:
      "str"

""",
    typehints_defaults="braces-after",
)
def function_with_multiline_param_defaults(x: int = 5, y: int = 10, z: int = 15) -> str:
    """
    Function docstring.

    :param x: optional specifier
              line 2
    :param y: another optional
              line 4

              second paragraph for y

    :param z: yet another optional s
              line 6

    :return: something
    :rtype: bytes
    """


ArrayLike = Literal["test"]

ALIAS_OPTIONS = {"autodoc_type_aliases": {"ArrayLike": "Array", "AliasedClass": '"Class Alias"'}}


class _SchemaMeta(type):  # noqa: PLW1641
    def __eq__(cls, other: object) -> bool:
        return True


class Schema(metaclass=_SchemaMeta):
    pass


class AliasedClass: ...


@expected(
    """
mod.function_with_eq_metaclass(s)

   Do something.

   Parameters:
      **s** ("Schema") -- Some schema.

   Return type:
      "Schema"
""",
    **ALIAS_OPTIONS,
)
def function_with_eq_metaclass(s: Schema) -> Schema:
    """
    Do something.

    Args:
        s: Some schema.
    """


@expected(
    """
mod.function_with_aliased_class(s)

   Do something.

   Parameters:
      **s** ("Class Alias") -- Some schema.

   Return type:
      "Class Alias"
""",
    **ALIAS_OPTIONS,
)
def function_with_aliased_class(s: AliasedClass) -> AliasedClass:
    """
    Do something.

    Args:
        s: Some schema.
    """


@expected(
    """\
mod.function_with_type_aliases(x, y)

   Function docstring.

   Parameters:
      * **x** (Array | "None") -- foo

      * **y** ("Schema") -- boo

   Returns:
      something

   Return type:
      "str"

""",
    **ALIAS_OPTIONS,
)
def function_with_type_aliases(x: ArrayLike | None, y: Schema) -> str:
    """
    Function docstring.

    :param x: foo
    :param y: boo
    :return: something
    :rtype: bytes
    """


@warns(
    WarningInfo(
        regexp="Cannot resolve forward reference in type annotations", type="sphinx_autodoc_typehints.forward_reference"
    )
)
@expected(
    """\
mod.function_with_unresolvable_annotation(x)

   Function docstring.

   Parameters:
      **x** (a.b.c) -- foo
""",
)
def function_with_unresolvable_annotation(x: a.b.c):  # noqa: ANN201, F821  # ty: ignore[unresolved-reference]
    """
    Function docstring.

    :arg x: foo
    """


@expected(
    """\
mod.function_with_typehint_comment(x, y)

   Function docstring.

   Parameters:
      * **x** ("int") -- foo

      * **y** ("str") -- bar

   Return type:
      "None"
""",
)
def function_with_typehint_comment(  # noqa: ANN201
    x,  # type: int  # noqa: ANN001
    y,  # type: str  # noqa: ANN001
):
    # type: (...) -> None
    """
    Function docstring.

    :parameter x: foo
    :parameter y: bar
    """


@expected(
    """\
class mod.ClassWithTypehints(x)

   Class docstring.

   Parameters:
      **x** ("int") -- foo

   foo(x)

      Method docstring.

      Parameters:
         **x** ("str") -- foo

      Return type:
         "int"

   method_without_typehint(x)

      Method docstring.
""",
)
class ClassWithTypehints:
    """
    Class docstring.

    :param x: foo
    """

    def __init__(
        self,
        x,  # type: int  # noqa: ANN001
    ) -> None:
        # type: (...) -> None
        ...

    def foo(  # noqa: ANN201
        self,
        x,  # type: str  # noqa: ANN001
    ):
        # type: (...) -> int
        """
        Method docstring.

        :arg x: foo
        """

    def method_without_typehint(self, x):  # noqa: ANN001, ANN201
        """
        Method docstring.
        """


@expected(
    """\
mod.function_with_typehint_comment_not_inline(x=None, *y, z, **kwargs)

   Function docstring.

   Parameters:
      * **x** ("str" | "bytes" | "None") -- foo

      * **y** ("str") -- bar

      * **z** ("bytes") -- baz

      * **kwargs** ("int") -- some kwargs

   Return type:
      "None"
""",
)
def function_with_typehint_comment_not_inline(x=None, *y, z, **kwargs):  # noqa: ANN001, ANN002, ANN003, ANN201
    # type: (Union[str, bytes, None], *str, bytes, **int) -> None
    """
    Function docstring.

    :arg x: foo
    :argument y: bar
    :parameter z: baz
    :parameter kwargs: some kwargs
    """


@expected(
    """\
class mod.ClassWithTypehintsNotInline(x=None)

   Class docstring.

   Parameters:
      **x** ("Callable"[["int", "bytes"], "int"] | "None") -- foo

   foo(x=1)

      Method docstring.

      Parameters:
         **x** ("Callable"[["int", "bytes"], "int"]) -- foo

      Return type:
         "int"

   classmethod mk(x=None)

      Method docstring.

      Parameters:
         **x** ("Callable"[["int", "bytes"], "int"] | "None") -- foo

      Return type:
         "ClassWithTypehintsNotInline"
""",
)
class ClassWithTypehintsNotInline:
    """
    Class docstring.

    :param x: foo
    """

    def __init__(self, x=None) -> None:  # type: (Optional[Callable[[int, bytes], int]]) -> None  # noqa: ANN001
        ...

    def foo(self, x=1):  # type: (Callable[[int, bytes], int]) -> int  # noqa: ANN001, ANN201
        """
        Method docstring.

        :param x: foo
        """

    @classmethod
    def mk(  # noqa: ANN206
        cls,
        x=None,  # noqa: ANN001
    ):  # type: (Optional[Callable[[int, bytes], int]]) -> ClassWithTypehintsNotInline
        """
        Method docstring.

        :param x: foo
        """


@expected(
    """\
mod.undocumented_function(x)

   Hi

   Return type:
      "str"
""",
)
def undocumented_function(x: int) -> str:
    """Hi"""


@expected(
    """\
class mod.DataClass(x)

   Class docstring.
""",
)
@dataclass
class DataClass:
    """Class docstring."""

    x: int


@expected(
    """\
class mod.Decorator(func)

   Initializer docstring.

   Parameters:
      **func** ("Callable"[["int", "str"], "str"]) -- function
""",
)
class Decorator:
    """
    Initializer docstring.

    :param func: function
    """

    def __init__(self, func: Callable[[int, str], str]) -> None: ...


@expected(
    """\
mod.mocked_import(x)

   A docstring.

   Parameters:
      **x** ("Mailbox") -- function
""",
)
def mocked_import(x: Mailbox):  # noqa: ANN201
    """
    A docstring.

    :param x: function
    """


@expected(
    """\
mod.func_with_examples()

   A docstring.

   Return type:
      "int"

   -[ Examples ]-

   Here are a couple of examples of how to use this function.
""",
)
def func_with_examples() -> int:
    """
    A docstring.

    .. rubric:: Examples

    Here are a couple of examples of how to use this function.
    """


@overload
def func_with_overload(a: int, b: int) -> None: ...


@overload
def func_with_overload(a: str, b: str) -> None: ...


@expected(
    """\
mod.func_with_overload(a, b)

   Overloads:
      * **a** (int), **b** (int) → None

      * **a** (str), **b** (str) → None

   f does the thing. The arguments can either be ints or strings but
   they must both have the same type.

   Parameters:
      * **a** ("int" | "str") -- The first thing

      * **b** ("int" | "str") -- The second thing
""",
)
def func_with_overload(a: Union[int, str], b: Union[int, str]) -> None:
    """
    f does the thing. The arguments can either be ints or strings but they must
    both have the same type.

    Parameters
    ----------
    a:
        The first thing
    b:
        The second thing
    """


@overload
def overload_with_complex_types(x: list[int]) -> dict[str, int]: ...


@overload
def overload_with_complex_types(x: dict[str, int]) -> list[int]: ...


@expected(
    """\
mod.overload_with_complex_types(x)

   Overloads:
      * **x** (list[int]) → dict[str, int]

      * **x** (dict[str, int]) → list[int]

   Function with overloaded complex types.

   Parameters:
      **x** ("list"["int"] | "dict"["str", "int"]) -- Input value
""",
)
def overload_with_complex_types(x: list[int] | dict[str, int]) -> dict[str, int] | list[int]:
    """Function with overloaded complex types.

    Parameters:
        x: Input value
    """


@overload
def func_with_overload_no_overloads(a: int, b: int) -> None: ...


@overload
def func_with_overload_no_overloads(a: str, b: str) -> None: ...


@expected(
    """\
mod.func_with_overload_no_overloads(a, b)

   Accepts int or str pairs, see docs for details.

   Parameters:
      * **a** ("int" | "str") -- The first thing

      * **b** ("int" | "str") -- The second thing

   Return type:
      "None"
""",
)
def func_with_overload_no_overloads(a: Union[int, str], b: Union[int, str]) -> None:
    """:no-overloads:

    Accepts int or str pairs, see docs for details.

    Parameters
    ----------
    a:
        The first thing
    b:
        The second thing
    """


@expected(
    """\
mod.func_literals_long_format(a, b)

   A docstring.

   Parameters:
      * **a** ("Literal"["'arg1'", "'arg2'"]) -- Argument that can
        take either of two literal values.

      * **b** ("Literal"["'arg1'", "'arg2'"]) -- Argument that can
        take either of two literal values.

   Return type:
      "None"
""",
)
def func_literals_long_format(a: Literal["arg1", "arg2"], b: Literal["arg1", "arg2"]) -> None:
    """
    A docstring.

    :param a: Argument that can take either of two literal values.
    :param b: Argument that can take either of two literal values.
    """


@expected(
    """\
mod.func_literals_short_format(a, b)

   A docstring.

   Parameters:
      * **a** ("'arg1'" | "'arg2'") -- Argument that can take either
        of two literal values.

      * **b** ("'arg1'" | "'arg2'") -- Argument that can take either
        of two literal values.

   Return type:
      "None"
""",
    python_display_short_literal_types=True,
)
def func_literals_short_format(a: Literal["arg1", "arg2"], b: Literal["arg1", "arg2"]) -> None:
    """
    A docstring.

    :param a: Argument that can take either of two literal values.
    :param b: Argument that can take either of two literal values.
    """


@expected(
    """\
class mod.TestClassAttributeDocs

   A class

   code: CodeType | None

      An attribute
""",
)
class TestClassAttributeDocs:
    """A class"""

    code: Optional[CodeType]
    """An attribute"""


@expected(
    """\
mod.func_with_examples_and_returns_after()

   f does the thing.

   -[ Examples ]-

   Here is an example

   Return type:
      "int"

   Returns:
      The index of the widget
""",
)
def func_with_examples_and_returns_after() -> int:
    """
    f does the thing.

    Examples
    --------

    Here is an example

    :returns: The index of the widget
    """


@expected(
    """\
mod.func_with_parameters_and_stuff_after(a, b)

   A func

   Parameters:
      * **a** ("int") -- a tells us something

      * **b** ("int") -- b tells us something

   Return type:
      "int"

   More info about the function here.
""",
)
def func_with_parameters_and_stuff_after(a: int, b: int) -> int:
    """A func

    :param a: a tells us something
    :param b: b tells us something

    More info about the function here.
    """


@expected(
    """\
mod.func_with_rtype_in_weird_spot(a, b)

   A func

   Parameters:
      * **a** ("int") -- a tells us something

      * **b** ("int") -- b tells us something

   -[ Examples ]-

   Here is an example

   Returns:
      The index of the widget

   More info about the function here.

   Return type:
      "int"
""",
)
def func_with_rtype_in_weird_spot(a: int, b: int) -> int:
    """A func

    :param a: a tells us something
    :param b: b tells us something

    Examples
    --------

    Here is an example

    :returns: The index of the widget

    More info about the function here.

    :rtype: int
    """


@expected(
    """\
mod.empty_line_between_parameters(a, b)

   A func

   Parameters:
      * **a** ("int") --

        One of the following possibilities:

        * a

        * b

        * c

      * **b** ("int") --

        Whatever else we have to say.

        There is more of it And here too

   Return type:
      "int"

   More stuff here.
""",
)
def empty_line_between_parameters(a: int, b: int) -> int:
    """A func

    :param a: One of the following possibilities:

             - a

             - b

             - c

    :param b: Whatever else we have to say.

              There is more of it And here too

    More stuff here.
    """


@expected(
    """\
mod.func_with_code_block()

   A docstring.

   You would say:

      print("some python code here")

   Return type:
      "int"

   -[ Examples ]-

   Here are a couple of examples of how to use this function.
""",
)
def func_with_code_block() -> int:
    """
    A docstring.

    You would say:

    .. code-block::

        print("some python code here")


    .. rubric:: Examples

    Here are a couple of examples of how to use this function.
    """


@expected(
    """
    mod.func_with_definition_list()

       Some text and then a definition list.

       Return type:
          "int"

       abc
          x

       xyz
          something
    """,
)
def func_with_definition_list() -> int:
    """Some text and then a definition list.

    abc
        x

    xyz
        something
    """
    # See https://github.com/tox-dev/sphinx-autodoc-typehints/issues/302


@expected(
    """\
mod.decorator_2(f)

   Run the decorated function with *asyncio.run*.

   Parameters:
      **f** ("Any") -- The function to wrap.

   Return type:
      "Any"

   -[ Examples ]-

      A
""",
)
def decorator_2(f: Any) -> Any:
    """Run the decorated function with `asyncio.run`.

    Parameters
    ----------
    f
        The function to wrap.

    Examples
    --------

    .. code-block:: python

        A
    """


@expected(
    """
    class mod.ParamAndAttributeHaveSameName(blah)

       A Class

       Parameters:
          **blah** ("CodeType") -- Description of parameter blah

       blah: ModuleType

          Description of attribute blah

    """,
)
class ParamAndAttributeHaveSameName:
    """
    A Class

    Parameters
    ----------
    blah:
        Description of parameter blah
    """

    def __init__(self, blah: CodeType) -> None: ...

    blah: ModuleType
    """Description of attribute blah"""


@expected(
    """
    mod.napoleon_returns()

       A function.

       Return type:
          "CodeType"

       Returns:
          The info about the whatever.
    """,
)
def napoleon_returns() -> CodeType:
    """
    A function.

    Returns
    -------
        The info about the whatever.
    """


@expected(
    """
    mod.google_docstrings(arg1, arg2)

       Summary line.

       Extended description of function.

       Parameters:
          * **arg1** ("CodeType") -- Description of arg1

          * **arg2** ("ModuleType") -- Description of arg2

       Return type:
          "CodeType"

       Returns:
          Description of return value

    """,
)
def google_docstrings(arg1: CodeType, arg2: ModuleType) -> CodeType:
    """Summary line.

    Extended description of function.

    Args:
        arg1: Description of arg1
        arg2: Description of arg2

    Returns:
        Description of return value
    """


@expected(
    """
    mod.docstring_with_multiline_note_after_params(param)

       Do something.

       Parameters:
          **param** ("int") -- A parameter.

       Return type:
          "None"

       Note:

         Some notes. More notes

    """,
)
def docstring_with_multiline_note_after_params(param: int) -> None:
    """Do something.

    Args:
        param: A parameter.

    Note:

        Some notes.
        More notes
    """


@expected(
    """
    mod.docstring_with_bullet_list_after_params(param)

       Do something.

       Parameters:
          **param** ("int") -- A parameter.

       Return type:
          "None"

       * A: B

       * C: D

    """,
)
def docstring_with_bullet_list_after_params(param: int) -> None:
    """Do something.

    Args:
        param: A parameter.

    * A: B
    * C: D
    """


@expected(
    """
    mod.docstring_with_definition_list_after_params(param)

       Do something.

       Parameters:
          **param** ("int") -- A parameter.

       Return type:
          "None"

       Term
          A description

          maybe multiple lines

       Next Term
          Something about it

    """,
)
def docstring_with_definition_list_after_params(param: int) -> None:
    """Do something.

    Args:
        param: A parameter.

    Term
        A description

        maybe multiple lines

    Next Term
        Something about it
    """


@expected(
    """
    mod.docstring_with_enum_list_after_params(param)

       Do something.

       Parameters:
          **param** ("int") -- A parameter.

       Return type:
          "None"

       1. A: B

       2. C: D

    """,
)
def docstring_with_enum_list_after_params(param: int) -> None:
    """Do something.

    Args:
        param: A parameter.

    1. A: B
    2. C: D
    """


@warns(WarningInfo(regexp="Definition list ends without a blank line", type="docutils"))
@expected(
    """
    mod.docstring_with_definition_list_after_params_no_blank_line(param)

       Do something.

       Parameters:
          **param** ("int") -- A parameter.

       Return type:
          "None"

       Term
          A description

          maybe multiple lines

       Next Term
          Something about it

       -[ Example ]-
    """,
)
def docstring_with_definition_list_after_params_no_blank_line(param: int) -> None:
    """Do something.

    Args:
        param: A parameter.

    Term
        A description

        maybe multiple lines

    Next Term
        Something about it
    .. rubric:: Example
    """


@expected(
    """
    mod.has_typevar(param)

       Do something.

       Parameters:
          **param** ("TypeVar"("T")) -- A parameter.

       Return type:
          "TypeVar"("T")

    """,
)
def has_typevar[T](param: T) -> T:
    """Do something.

    Args:
        param: A parameter.
    """


@expected(
    """
    mod.has_newtype(param)

       Do something.

       Parameters:
          **param** ("W" ("str")) -- A parameter.

       Return type:
          "W" ("str")

    """,
)
def has_newtype(param: W) -> W:
    """Do something.

    Args:
        param: A parameter.
    """


AUTO_FUNCTION = ".. autofunction:: mod.{}"
AUTO_CLASS = """\
.. autoclass:: mod.{}
    :members:
"""
AUTO_EXCEPTION = """\
.. autoexception:: mod.{}
   :members:
"""


@expected(
    """
    mod.typehints_use_signature(a: AsyncGenerator) -> AsyncGenerator

       Do something.

       Parameters:
          **a** ("AsyncGenerator") -- blah

       Return type:
          "AsyncGenerator"

    """,
    typehints_use_signature=True,
    typehints_use_signature_return=True,
)
def typehints_use_signature(a: AsyncGenerator) -> AsyncGenerator:
    """Do something.

    Args:
        a: blah
    """


@expected(
    """
    mod.typehints_no_rtype_none()

       Do something.

    """,
    typehints_document_rtype_none=False,
)
def typehints_no_rtype_none() -> None:
    """Do something."""


prolog = """
.. |test_node_start| replace:: {test_node_start}
""".format(test_node_start="test_start")


@expected(
    """
    mod.docstring_with_multiline_note_after_params_prolog_replace(param)

       Do something.

       Parameters:
          **param** ("int") -- A parameter.

       Return type:
          "None"

       Note:

         Some notes. test_start More notes

    """,
    rst_prolog=prolog,
)
def docstring_with_multiline_note_after_params_prolog_replace(param: int) -> None:
    """Do something.

    Args:
        param: A parameter.

    Note:

        Some notes. |test_node_start|
        More notes
    """


epilog = """
.. |test_node_end| replace:: {test_node_end}
""".format(test_node_end="test_end")


@expected(
    """
    mod.docstring_with_multiline_note_after_params_epilog_replace(param)

       Do something.

       Parameters:
          **param** ("int") -- A parameter.

       Return type:
          "None"

       Note:

         Some notes. test_end More notes

    """,
    rst_epilog=epilog,
)
def docstring_with_multiline_note_after_params_epilog_replace(param: int) -> None:
    """Do something.

    Args:
        param: A parameter.

    Note:

        Some notes. |test_node_end|
        More notes
    """


@expected(
    """
    mod.docstring_with_see_also()

       Test

       See also: more info at <https://example.com>`_.

       Return type:
          "str"

    """
)
def docstring_with_see_also() -> str:
    """
    Test

    .. seealso:: more info at <https://example.com>`_.
    """


@expected(
    """
    mod.has_doctest1()

       Test that we place the return type correctly when the function has
       a doctest.

       Return type:
          "None"

       >>> this is a fake doctest
       a
       >>> more doctest
       b
    """
)
def has_doctest1() -> None:
    r"""Test that we place the return type correctly when the function has a doctest.

    >>> this is a fake doctest
    a
    >>> more doctest
    b
    """


Unformatted = TypeVar("Unformatted")


@expected(
    """
    mod.typehints_formatter_applied_to_signature(param: Formatted) -> Formatted

       Do nothing

       Parameters:
          **param** (Formatted) -- A parameter

       Return type:
          Formatted

       Returns:
          The return value
    """,
    typehints_use_signature=True,
    typehints_use_signature_return=True,
    typehints_formatter=lambda _, __=None: "Formatted",
)
def typehints_formatter_applied_to_signature[Unformatted](param: Unformatted) -> Unformatted:
    """
    Do nothing

    Args:
        param: A parameter

    Returns:
        The return value
    """


# Config settings for each test run.
# Config Name: Sphinx Options as Dict.
configs = {
    "default_conf": {},
    "prolog_conf": {"rst_prolog": prolog},
    "epilog_conf": {
        "rst_epilog": epilog,
    },
    "bothlog_conf": {
        "rst_prolog": prolog,
        "rst_epilog": epilog,
    },
}


@pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")])
@pytest.mark.parametrize("conf_run", ["default_conf", "prolog_conf", "epilog_conf", "bothlog_conf"])
@pytest.mark.sphinx("text", testroot="integration")
def test_integration(
    app: SphinxTestApp, status: StringIO, warning: StringIO, monkeypatch: pytest.MonkeyPatch, val: Any, conf_run: str
) -> None:
    if isclass(val) and issubclass(val, BaseException):
        template = AUTO_EXCEPTION
    elif isclass(val):
        template = AUTO_CLASS
    else:
        template = AUTO_FUNCTION

    (Path(app.srcdir) / "index.rst").write_text(template.format(val.__name__))
    app.config.__dict__.update(configs[conf_run])
    app.config.__dict__.update(val.OPTIONS)
    app.config.always_use_bars_union = True
    monkeypatch.setitem(sys.modules, "mod", sys.modules[__name__])
    app.build()
    assert "build succeeded" in status.getvalue()  # Build succeeded

    warning_info: Union[WarningInfo, None] = getattr(val, "WARNING", None)
    value = warning.getvalue().strip()
    if warning_info:
        warning_info.assert_warning(value)
    else:
        assert not value

    result = normalize_sphinx_text((Path(app.srcdir) / "_build/text/index.txt").read_text())

    expected = dedent(normalize_sphinx_text(val.EXPECTED)).strip()
    assert result.strip() == expected
