"""Sphinx only cross-document gluing.

Note, we restrict this to a only a subset of mime-types and data -> nodes transforms,
since adding these nodes in a post-transform will not apply any transforms to them.
"""
from __future__ import annotations

from functools import lru_cache
import json
from pathlib import Path
from typing import Any, Sequence

from docutils import nodes
from sphinx.transforms.post_transforms import SphinxPostTransform
from sphinx.util import logging as sphinx_logging

from myst_nb._compat import findall
from myst_nb.core.loggers import DEFAULT_LOG_TYPE
from myst_nb.core.render import get_mime_priority
from myst_nb.core.variables import format_plain_text

from .utils import PendingGlueReference

SPHINX_LOGGER = sphinx_logging.getLogger(__name__)


@lru_cache(maxsize=3)
def read_glue_cache(folder: str, docname: str) -> dict[str, Any]:
    """Read a glue cache from the build folder, for a particular document."""
    docpath = docname.split("/")
    path = Path(folder).joinpath(*docpath[:-1]).joinpath(f"{docpath[-1]}.glue.json")
    if not path.exists():
        return {}
    with path.open("r") as f:
        return json.load(f)


class ReplacePendingGlueReferences(SphinxPostTransform):
    """Sphinx only cross-document gluing.

    Note, we restrict this to a only a subset of mime-types and data -> nodes transforms,
    since adding these nodes in a post-transform will not apply any transforms to them.
    """

    default_priority = 5

    def apply(self, **kwargs):
        """Apply the transform."""
        cache_folder = self.env.mystnb_config.output_folder  # type: ignore
        bname = self.app.builder.name  # type: ignore
        priority_list = get_mime_priority(
            bname, self.config["nb_mime_priority_overrides"]
        )
        node: PendingGlueReference
        for node in list(findall(self.document)(PendingGlueReference)):
            data = read_glue_cache(cache_folder, node.refdoc)
            if node.key not in data:
                SPHINX_LOGGER.warning(
                    f"Glue reference {node.key!r} not found in doc {node.refdoc!r} "
                    f"[{DEFAULT_LOG_TYPE}.glue_ref]",
                    type=DEFAULT_LOG_TYPE,
                    subtype="glue_ref",
                    location=node,
                )
                node.parent.remove(node)
                continue
            output = data[node.key]
            if node.gtype == "text":
                _nodes = generate_text_nodes(node, output)
            else:
                _nodes = generate_any_nodes(node, output, priority_list)

            if _nodes:
                node.replace_self(_nodes)
            else:
                node.parent.remove(node)


def ref_warning(msg: str, node) -> None:
    """Log a warning for a reference."""
    SPHINX_LOGGER.warning(
        f"{msg} [{DEFAULT_LOG_TYPE}.glue_ref]",
        type=DEFAULT_LOG_TYPE,
        subtype="glue_ref",
        location=node,
    )


def generate_any_nodes(
    node: PendingGlueReference, output: dict[str, Any], priority_list: Sequence[str]
) -> list[nodes.Element]:
    """Generate nodes for a cell, according to the highest priority mime type."""
    data = output["data"]
    for mime_type in priority_list:
        if mime_type not in data:
            continue
        if mime_type == "text/plain":
            if node.inline:
                return [nodes.literal(data[mime_type], data[mime_type])]
            else:
                return [nodes.literal_block(data[mime_type], data[mime_type])]
        if mime_type == "text/html":
            return [
                nodes.raw(
                    text=data[mime_type], format="html", classes=["output", "text_html"]
                )
            ]
    ref_warning(
        f"No allowed mime type found in {node.key!r}: {list(output['data'])}", node
    )
    return []


def generate_text_nodes(node: PendingGlueReference, output: dict[str, Any]):
    """Generate nodes for a cell, for formatted text/plain."""
    data = output["data"]
    if "text/plain" not in data:
        ref_warning(f"No text/plain found in {node.key!r}", node)
        return []
    try:
        text = format_plain_text(data["text/plain"], node["fmt_spec"])
    except Exception as exc:
        ref_warning(f"Failed to format text/plain: {exc}", node)
        return []
    return [nodes.inline(text, text, classes=["pasted-text"])]
