"""Extension that adds `default-literal-role` directive."""

import contextlib
from typing import List

import docutils.nodes
from docutils.parsers.rst.states import Inliner
import sphinx.application
import sphinx.util.docutils
from sphinx.util.docutils import CustomReSTDispatcher

_LITERAL_KEY = "``"


def set_default_literal_role(role: str):
    def literal(self: Inliner, match, lineno: int):
        def nodeclass(rawsource: str, text: str):
            return self.interpreted(rawsource, text, role, lineno)

        nodeclass.__name__ = "literal"

        before, inlines, remaining, sysmessages, endstring = self.inline_obj(
            match, lineno, self.patterns.literal, nodeclass  # type: ignore[arg-type]
        )

        if len(inlines) == 1 and isinstance(inlines[0], tuple):
            inlines, extra_messages = inlines[0]
            sysmessages = sysmessages + extra_messages

        return before, inlines, remaining, sysmessages

    Inliner.dispatch[_LITERAL_KEY] = literal  # type: ignore[assignment]


@contextlib.contextmanager
def save_default_literal_role():
    orig_literal = Inliner.dispatch[_LITERAL_KEY]

    try:
        yield
    finally:
        Inliner.dispatch[_LITERAL_KEY] = orig_literal


@contextlib.contextmanager
def overridden_default_literal_role(role: str):
    """Overrides the role generated by double backticks literal syntax.

    Normally, double backticks:

        ``abc``

    is equivalent to the literal role:

        :literal:`abc`

    This context manager globally overrides the role to instead be the specified
    `role`.
    """

    with save_default_literal_role():
        set_default_literal_role(role)
        yield


class DefaultLiteralRoleDirective(sphinx.util.docutils.SphinxDirective):

    has_content = False
    required_arguments = 0
    optional_arguments = 1
    final_argument_whitespace = False

    def run(self) -> List[docutils.nodes.Node]:
        if not self.arguments:
            Inliner.dispatch[_LITERAL_KEY] = Inliner.literal  # type: ignore[assignment]
        else:
            set_default_literal_role(self.arguments[0])
        return []


def _monkey_patch_custom_rest_dispatcher():
    """Monkey patches `CustomReSTDispatcher` to also retore the default literal
    role.

    Note:

    The `sphinx_domains` context manager that wraps all document reading
    inherits from CustomReSTDispatcher.  Therefore, the modified
    `CustomReSTDispatcher` logic runs before and after each document is read.
    """

    orig_enable = CustomReSTDispatcher.enable

    def enable(self):
        orig_enable(self)
        setattr(self, "_literal_func", Inliner.dispatch[_LITERAL_KEY])

    CustomReSTDispatcher.enable = enable

    orig_disable = CustomReSTDispatcher.disable

    def disable(self):
        Inliner.dispatch[_LITERAL_KEY] = getattr(self, "_literal_func")
        orig_disable(self)

    CustomReSTDispatcher.disable = disable


_monkey_patch_custom_rest_dispatcher()


def setup(app: sphinx.application.Sphinx):
    app.add_directive("default-literal-role", DefaultLiteralRoleDirective)
    return {
        "parallel_read_safe": True,
        "parallel_write_safe": True,
    }
