"""Tests the Python Domain"""

from __future__ import annotations

import pytest
from docutils import nodes

from sphinx import addnodes
from sphinx.addnodes import (
    desc,
    desc_addname,
    desc_annotation,
    desc_content,
    desc_name,
    desc_optional,
    desc_parameter,
    desc_parameterlist,
    desc_returns,
    desc_sig_keyword,
    desc_sig_name,
    desc_sig_operator,
    desc_sig_punctuation,
    desc_sig_space,
    desc_signature,
    pending_xref,
)
from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node


def test_pyfunction(app):
    text = (".. py:function:: func1\n"
            ".. py:module:: example\n"
            ".. py:function:: func2\n"
            "   :async:\n")
    domain = app.env.get_domain('py')
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree, (addnodes.index,
                          [desc, ([desc_signature, ([desc_name, "func1"],
                                                    [desc_parameterlist, ()])],
                                  [desc_content, ()])],
                          addnodes.index,
                          addnodes.index,
                          nodes.target,
                          [desc, ([desc_signature, ([desc_annotation, ([desc_sig_keyword, 'async'],
                                                                       desc_sig_space)],
                                                    [desc_addname, "example."],
                                                    [desc_name, "func2"],
                                                    [desc_parameterlist, ()])],
                                  [desc_content, ()])]))
    assert_node(doctree[0], addnodes.index,
                entries=[('pair', 'built-in function; func1()', 'func1', '', None)])
    assert_node(doctree[2], addnodes.index,
                entries=[('pair', 'module; example', 'module-example', '', None)])
    assert_node(doctree[3], addnodes.index,
                entries=[('single', 'func2() (in module example)', 'example.func2', '', None)])

    assert 'func1' in domain.objects
    assert domain.objects['func1'] == ('index', 'func1', 'function', False)
    assert 'example.func2' in domain.objects
    assert domain.objects['example.func2'] == ('index', 'example.func2', 'function', False)


def test_pyfunction_signature(app):
    text = ".. py:function:: hello(name: str) -> str"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree, (addnodes.index,
                          [desc, ([desc_signature, ([desc_name, "hello"],
                                                    desc_parameterlist,
                                                    [desc_returns, pending_xref, "str"])],
                                  desc_content)]))
    assert_node(doctree[1], addnodes.desc, desctype="function",
                domain="py", objtype="function", no_index=False)
    assert_node(doctree[1][0][1],
                [desc_parameterlist, desc_parameter, ([desc_sig_name, "name"],
                                                      [desc_sig_punctuation, ":"],
                                                      desc_sig_space,
                                                      [nodes.inline, pending_xref, "str"])])


def test_pyfunction_signature_full(app):
    text = (".. py:function:: hello(a: str, b = 1, *args: str, "
            "c: bool = True, d: tuple = (1, 2), **kwargs: str) -> str")
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree, (addnodes.index,
                          [desc, ([desc_signature, ([desc_name, "hello"],
                                                    desc_parameterlist,
                                                    [desc_returns, pending_xref, "str"])],
                                  desc_content)]))
    assert_node(doctree[1], addnodes.desc, desctype="function",
                domain="py", objtype="function", no_index=False)
    assert_node(doctree[1][0][1],
                [desc_parameterlist, ([desc_parameter, ([desc_sig_name, "a"],
                                                        [desc_sig_punctuation, ":"],
                                                        desc_sig_space,
                                                        [desc_sig_name, pending_xref, "str"])],
                                      [desc_parameter, ([desc_sig_name, "b"],
                                                        [desc_sig_operator, "="],
                                                        [nodes.inline, "1"])],
                                      [desc_parameter, ([desc_sig_operator, "*"],
                                                        [desc_sig_name, "args"],
                                                        [desc_sig_punctuation, ":"],
                                                        desc_sig_space,
                                                        [desc_sig_name, pending_xref, "str"])],
                                      [desc_parameter, ([desc_sig_name, "c"],
                                                        [desc_sig_punctuation, ":"],
                                                        desc_sig_space,
                                                        [desc_sig_name, pending_xref, "bool"],
                                                        desc_sig_space,
                                                        [desc_sig_operator, "="],
                                                        desc_sig_space,
                                                        [nodes.inline, "True"])],
                                      [desc_parameter, ([desc_sig_name, "d"],
                                                        [desc_sig_punctuation, ":"],
                                                        desc_sig_space,
                                                        [desc_sig_name, pending_xref, "tuple"],
                                                        desc_sig_space,
                                                        [desc_sig_operator, "="],
                                                        desc_sig_space,
                                                        [nodes.inline, "(1, 2)"])],
                                      [desc_parameter, ([desc_sig_operator, "**"],
                                                        [desc_sig_name, "kwargs"],
                                                        [desc_sig_punctuation, ":"],
                                                        desc_sig_space,
                                                        [desc_sig_name, pending_xref, "str"])])])
    # case: separator at head
    text = ".. py:function:: hello(*, a)"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree[1][0][1],
                [desc_parameterlist, ([desc_parameter, nodes.inline, "*"],
                                      [desc_parameter, desc_sig_name, "a"])])

    # case: separator in the middle
    text = ".. py:function:: hello(a, /, b, *, c)"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree[1][0][1],
                [desc_parameterlist, ([desc_parameter, desc_sig_name, "a"],
                                      [desc_parameter, desc_sig_operator, "/"],
                                      [desc_parameter, desc_sig_name, "b"],
                                      [desc_parameter, desc_sig_operator, "*"],
                                      [desc_parameter, desc_sig_name, "c"])])

    # case: separator in the middle (2)
    text = ".. py:function:: hello(a, /, *, b)"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree[1][0][1],
                [desc_parameterlist, ([desc_parameter, desc_sig_name, "a"],
                                      [desc_parameter, desc_sig_operator, "/"],
                                      [desc_parameter, desc_sig_operator, "*"],
                                      [desc_parameter, desc_sig_name, "b"])])

    # case: separator at tail
    text = ".. py:function:: hello(a, /)"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree[1][0][1],
                [desc_parameterlist, ([desc_parameter, desc_sig_name, "a"],
                                      [desc_parameter, desc_sig_operator, "/"])])


def test_pyfunction_with_unary_operators(app):
    text = ".. py:function:: menu(egg=+1, bacon=-1, sausage=~1, spam=not spam)"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree[1][0][1],
                [desc_parameterlist, ([desc_parameter, ([desc_sig_name, "egg"],
                                                        [desc_sig_operator, "="],
                                                        [nodes.inline, "+1"])],
                                      [desc_parameter, ([desc_sig_name, "bacon"],
                                                        [desc_sig_operator, "="],
                                                        [nodes.inline, "-1"])],
                                      [desc_parameter, ([desc_sig_name, "sausage"],
                                                        [desc_sig_operator, "="],
                                                        [nodes.inline, "~1"])],
                                      [desc_parameter, ([desc_sig_name, "spam"],
                                                        [desc_sig_operator, "="],
                                                        [nodes.inline, "not spam"])])])


def test_pyfunction_with_binary_operators(app):
    text = ".. py:function:: menu(spam=2**64)"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree[1][0][1],
                [desc_parameterlist, ([desc_parameter, ([desc_sig_name, "spam"],
                                                        [desc_sig_operator, "="],
                                                        [nodes.inline, "2**64"])])])


def test_pyfunction_with_number_literals(app):
    text = ".. py:function:: hello(age=0x10, height=1_6_0)"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree[1][0][1],
                [desc_parameterlist, ([desc_parameter, ([desc_sig_name, "age"],
                                                        [desc_sig_operator, "="],
                                                        [nodes.inline, "0x10"])],
                                      [desc_parameter, ([desc_sig_name, "height"],
                                                        [desc_sig_operator, "="],
                                                        [nodes.inline, "1_6_0"])])])


def test_pyfunction_with_union_type_operator(app):
    text = ".. py:function:: hello(age: int | None)"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree[1][0][1],
                [desc_parameterlist, ([desc_parameter, ([desc_sig_name, "age"],
                                                        [desc_sig_punctuation, ":"],
                                                        desc_sig_space,
                                                        [desc_sig_name, ([pending_xref, "int"],
                                                                         desc_sig_space,
                                                                         [desc_sig_punctuation, "|"],
                                                                         desc_sig_space,
                                                                         [pending_xref, "None"])])])])


def test_optional_pyfunction_signature(app):
    text = ".. py:function:: compile(source [, filename [, symbol]]) -> ast object"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree, (addnodes.index,
                          [desc, ([desc_signature, ([desc_name, "compile"],
                                                    desc_parameterlist,
                                                    [desc_returns, pending_xref, "ast object"])],
                                  desc_content)]))
    assert_node(doctree[1], addnodes.desc, desctype="function",
                domain="py", objtype="function", no_index=False)
    assert_node(doctree[1][0][1],
                ([desc_parameter, ([desc_sig_name, "source"])],
                 [desc_optional, ([desc_parameter, ([desc_sig_name, "filename"])],
                                  [desc_optional, desc_parameter, ([desc_sig_name, "symbol"])])]))


@pytest.mark.sphinx('html', confoverrides={
    'python_maximum_signature_line_length': len("hello(name: str) -> str"),
})
def test_pyfunction_signature_with_python_maximum_signature_line_length_equal(app):
    text = ".. py:function:: hello(name: str) -> str"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree, (
        addnodes.index,
        [desc, (
            [desc_signature, (
                [desc_name, "hello"],
                desc_parameterlist,
                [desc_returns, pending_xref, "str"],
            )],
            desc_content,
        )],
    ))
    assert_node(doctree[1], addnodes.desc, desctype="function",
                domain="py", objtype="function", no_index=False)
    assert_node(doctree[1][0][1], [desc_parameterlist, desc_parameter, (
        [desc_sig_name, "name"],
        [desc_sig_punctuation, ":"],
        desc_sig_space,
        [nodes.inline, pending_xref, "str"],
    )])
    assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=False)


@pytest.mark.sphinx('html', confoverrides={
    'python_maximum_signature_line_length': len("hello(name: str) -> str"),
})
def test_pyfunction_signature_with_python_maximum_signature_line_length_force_single(app):
    text = (".. py:function:: hello(names: str) -> str\n"
            "   :single-line-parameter-list:")
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree, (
        addnodes.index,
        [desc, (
            [desc_signature, (
                [desc_name, "hello"],
                desc_parameterlist,
                [desc_returns, pending_xref, "str"],
            )],
            desc_content,
        )],
    ))
    assert_node(doctree[1], addnodes.desc, desctype="function",
                domain="py", objtype="function", no_index=False)
    assert_node(doctree[1][0][1], [desc_parameterlist, desc_parameter, (
        [desc_sig_name, "names"],
        [desc_sig_punctuation, ":"],
        desc_sig_space,
        [nodes.inline, pending_xref, "str"],
    )])
    assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=False)


@pytest.mark.sphinx('html', confoverrides={
    'python_maximum_signature_line_length': len("hello(name: str) -> str"),
})
def test_pyfunction_signature_with_python_maximum_signature_line_length_break(app):
    text = ".. py:function:: hello(names: str) -> str"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree, (
        addnodes.index,
        [desc, (
            [desc_signature, (
                [desc_name, "hello"],
                desc_parameterlist,
                [desc_returns, pending_xref, "str"],
            )],
            desc_content,
        )],
    ))
    assert_node(doctree[1], addnodes.desc, desctype="function",
                domain="py", objtype="function", no_index=False)
    assert_node(doctree[1][0][1], [desc_parameterlist, desc_parameter, (
        [desc_sig_name, "names"],
        [desc_sig_punctuation, ":"],
        desc_sig_space,
        [nodes.inline, pending_xref, "str"],
    )])
    assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=True)


@pytest.mark.sphinx('html', confoverrides={
    'maximum_signature_line_length': len("hello(name: str) -> str"),
})
def test_pyfunction_signature_with_maximum_signature_line_length_equal(app):
    text = ".. py:function:: hello(name: str) -> str"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree, (
        addnodes.index,
        [desc, (
            [desc_signature, (
                [desc_name, "hello"],
                desc_parameterlist,
                [desc_returns, pending_xref, "str"],
            )],
            desc_content,
        )],
    ))
    assert_node(doctree[1], addnodes.desc, desctype="function",
                domain="py", objtype="function", no_index=False)
    assert_node(doctree[1][0][1], [desc_parameterlist, desc_parameter, (
        [desc_sig_name, "name"],
        [desc_sig_punctuation, ":"],
        desc_sig_space,
        [nodes.inline, pending_xref, "str"],
    )])
    assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=False)


@pytest.mark.sphinx('html', confoverrides={
    'maximum_signature_line_length': len("hello(name: str) -> str"),
})
def test_pyfunction_signature_with_maximum_signature_line_length_force_single(app):
    text = (".. py:function:: hello(names: str) -> str\n"
            "   :single-line-parameter-list:")
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree, (
        addnodes.index,
        [desc, (
            [desc_signature, (
                [desc_name, "hello"],
                desc_parameterlist,
                [desc_returns, pending_xref, "str"],
            )],
            desc_content,
        )],
    ))
    assert_node(doctree[1], addnodes.desc, desctype="function",
                domain="py", objtype="function", no_index=False)
    assert_node(doctree[1][0][1], [desc_parameterlist, desc_parameter, (
        [desc_sig_name, "names"],
        [desc_sig_punctuation, ":"],
        desc_sig_space,
        [nodes.inline, pending_xref, "str"],
    )])
    assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=False)


@pytest.mark.sphinx('html', confoverrides={
    'maximum_signature_line_length': len("hello(name: str) -> str"),
})
def test_pyfunction_signature_with_maximum_signature_line_length_break(app):
    text = ".. py:function:: hello(names: str) -> str"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree, (
        addnodes.index,
        [desc, (
            [desc_signature, (
                [desc_name, "hello"],
                desc_parameterlist,
                [desc_returns, pending_xref, "str"],
            )],
            desc_content,
        )],
    ))
    assert_node(doctree[1], addnodes.desc, desctype="function",
                domain="py", objtype="function", no_index=False)
    assert_node(doctree[1][0][1], [desc_parameterlist, desc_parameter, (
        [desc_sig_name, "names"],
        [desc_sig_punctuation, ":"],
        desc_sig_space,
        [nodes.inline, pending_xref, "str"],
    )])
    assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=True)
