import docutils.nodes
import sphinx.addnodes
import sphinx.application
import sphinx.config
from sphinx.locale import _

from .. import html_translator_mixin

# Add "highlight" class in order for pygments syntax highlighting CSS rules to
# apply to signatures.
if "highlight" not in sphinx.addnodes.desc_signature.classes:
    sphinx.addnodes.desc_signature.classes.append("highlight")


@html_translator_mixin.override
def visit_desc(
    self: html_translator_mixin.HTMLTranslatorMixin,
    node: sphinx.addnodes.desc,
    super_func: html_translator_mixin.BaseVisitCallback[sphinx.addnodes.desc],
) -> None:
    # Object description node

    # These are converted to `<dl>` elements with the domain and objtype
    # as classes.

    # Augment the list of classes with `objdesc` to make it easier to
    # style these without resorting to hacks.
    node["classes"].append("objdesc")
    super_func(self, node)


@html_translator_mixin.override
def visit_desc_type(
    self: html_translator_mixin.HTMLTranslatorMixin,
    node: sphinx.addnodes.desc_type,
    super_func: html_translator_mixin.BaseVisitCallback[sphinx.addnodes.desc_type],
) -> None:
    self.body.append(self.starttag(node, tagname="span", suffix="", CLASS="desctype"))


@html_translator_mixin.override
def depart_desc_type(
    self: html_translator_mixin.HTMLTranslatorMixin,
    node: sphinx.addnodes.desc_type,
    super_func: html_translator_mixin.BaseVisitCallback[sphinx.addnodes.desc_type],
) -> None:
    self.body.append("</span>")


@html_translator_mixin.override
def visit_caption(
    self: html_translator_mixin.HTMLTranslatorMixin,
    node: docutils.nodes.caption,
    super_func: html_translator_mixin.BaseVisitCallback[docutils.nodes.caption],
) -> None:
    attributes = {"class": "caption-text"}
    if isinstance(node.parent, docutils.nodes.container) and node.parent.get(
        "literal_block"
    ):
        # add highlight class to caption's div container.
        # This is needed to trigger mkdocs-material CSS rule `.highlight .filename`
        self.body.append('<div class="code-block-caption highlight">')
        # append a CSS class to trigger mkdocs-material theme's caption CSS style
        attributes["class"] += " filename"
    else:
        super_func(self, node)
    self.add_fignumber(node.parent)
    self.body.append(self.starttag(node, "span", **attributes))


@html_translator_mixin.override
def depart_caption(
    self: html_translator_mixin.HTMLTranslatorMixin,
    node: docutils.nodes.caption,
    super_func: html_translator_mixin.BaseVisitCallback[docutils.nodes.caption],
) -> None:
    if not isinstance(node.parent, docutils.nodes.container) and not node.parent.get(
        "literal_block"
    ):
        # only append ending tag if parent is not a literal-block.
        # Because all elements in the caption should be within a span element
        self.body.append("</span>")

    # append permalink if available
    if isinstance(node.parent, docutils.nodes.container) and node.parent.get(
        "literal_block"
    ):
        self.add_permalink_ref(node.parent, _("Permalink to this code"))
        self.body.append("</span>")  # done; add closing tag
    elif isinstance(node.parent, docutils.nodes.figure):
        self.add_permalink_ref(node.parent, _("Permalink to this image"))
    elif node.parent.get("toctree"):
        self.add_permalink_ref(node.parent.parent, _("Permalink to this toctree"))

    if isinstance(node.parent, docutils.nodes.container) and node.parent.get(
        "literal_block"
    ):
        self.body.append("</div>\n")
    elif not isinstance(node.parent, docutils.nodes.figure):
        # calling super_func() on a figure results in 2 permalinks in caption
        super_func(self, node)


# `desc_inline` nodes are generated by the `cpp:expr` role.
#
# Wrap it in a `<code>` element with the "highlight" class to ensure it
# displays properly as an inline code literal.
@html_translator_mixin.override
def visit_desc_inline(
    self: html_translator_mixin.HTMLTranslatorMixin,
    node: sphinx.addnodes.desc_inline,
    super_func: html_translator_mixin.BaseVisitCallback[sphinx.addnodes.desc_inline],
) -> None:
    self.body.append(self.starttag(node, tagname="code", suffix="", CLASS="highlight"))


@html_translator_mixin.override
def depart_desc_inline(
    self: html_translator_mixin.HTMLTranslatorMixin,
    node: sphinx.addnodes.desc_inline,
    super_func: html_translator_mixin.BaseVisitCallback[sphinx.addnodes.desc_inline],
) -> None:
    self.body.append("</code>")


@html_translator_mixin.override
def visit_productionlist(
    self: html_translator_mixin.HTMLTranslatorMixin,
    node: sphinx.addnodes.productionlist,
    super_func: html_translator_mixin.BaseVisitCallback[sphinx.addnodes.productionlist],
):
    # we only need to patch in a code element around the production list
    before_len = len(self.body)
    try:
        super_func(self, node)
    except docutils.nodes.SkipNode:
        pass
    # be sure that there wasn't an incompatible change to sphinx
    assert self.body[before_len].endswith("<pre>\n") and self.body[-1].startswith(
        "</pre>"
    )
    self.body[before_len] += "<code>"
    self.body[-1] = "</code>" + self.body[-1]
    raise docutils.nodes.SkipNode()


@html_translator_mixin.init
def init_translator(self: html_translator_mixin.HTMLTranslatorMixin) -> None:

    # Ensure pygments uses <code> elements, for compatibility with the
    # mkdocs-material CSS which expects that.
    self.highlighter.formatter_args.update(wrapcode=True)

    # Ensure classes like `s` (used for string literals in code
    # highlighting) aren't converted to `<s>` elements (strikethrough).
    # Sphinx already overrides this, but for some reason due to
    # `__init__` invocation order it gets overridden.
    self.supported_inline_tags = set()


def _config_inited(
    app: sphinx.application.Sphinx, config: sphinx.config.Config
) -> None:
    # make code-blocks' line numbers be a separate column of a 1-row table
    config["html_codeblock_linenos_style"] = "table"  # default is "inline"


def setup(app: sphinx.application.Sphinx):
    app.connect("config-inited", _config_inited)

    return {
        "parallel_read_safe": True,
        "parallel_write_safe": True,
    }
