"""Support for building "topic help" for pydoc."""

from __future__ import annotations

from time import asctime
from typing import TYPE_CHECKING

from sphinx.builders.text import TextBuilder
from sphinx.util import logging
from sphinx.util.display import status_iterator
from sphinx.util.docutils import new_document
from sphinx.writers.text import TextTranslator

if TYPE_CHECKING:
    from collections.abc import Sequence, Set

    from sphinx.application import Sphinx
    from sphinx.util.typing import ExtensionMetadata

logger = logging.getLogger(__name__)

_PYDOC_TOPIC_LABELS: Sequence[str] = sorted({
    "assert",
    "assignment",
    "assignment-expressions",
    "async",
    "atom-identifiers",
    "atom-literals",
    "attribute-access",
    "attribute-references",
    "augassign",
    "await",
    "binary",
    "bitwise",
    "bltin-code-objects",
    "bltin-ellipsis-object",
    "bltin-null-object",
    "bltin-type-objects",
    "booleans",
    "break",
    "callable-types",
    "calls",
    "class",
    "comparisons",
    "compound",
    "context-managers",
    "continue",
    "conversions",
    "customization",
    "debugger",
    "del",
    "dict",
    "dynamic-features",
    "else",
    "exceptions",
    "execmodel",
    "exprlists",
    "floating",
    "for",
    "formatstrings",
    "function",
    "global",
    "id-classes",
    "identifiers",
    "if",
    "imaginary",
    "import",
    "in",
    "integers",
    "lambda",
    "lazy",
    "lists",
    "match",
    "naming",
    "nonlocal",
    "numbers",
    "numeric-types",
    "objects",
    "operator-summary",
    "pass",
    "power",
    "raise",
    "return",
    "sequence-types",
    "shifting",
    "slicings",
    "specialattrs",
    "specialnames",
    "string-methods",
    "strings",
    "subscriptions",
    "truth",
    "try",
    "types",
    "typesfunctions",
    "typesmapping",
    "typesmethods",
    "typesmodules",
    "typesseq",
    "typesseq-mutable",
    "unary",
    "while",
    "with",
    "yield",
})


class PydocTopicsBuilder(TextBuilder):
    name = "pydoc-topics"

    def init(self) -> None:
        super().init()
        self.topics: dict[str, str] = {}
        self.module_docs: dict[str, str] = {}

    def get_outdated_docs(self) -> str:
        # Return a string describing what an update build will build.
        return "all pydoc topics"

    def write_documents(self, _docnames: Set[str]) -> None:
        env = self.env

        labels: dict[str, tuple[str, str, str]]
        labels = env.domains.standard_domain.labels

        # docname -> list of (topic_label, label_id) pairs
        doc_labels: dict[str, list[tuple[str, str]]] = {}
        for topic_label in _PYDOC_TOPIC_LABELS:
            try:
                docname, label_id, _section_name = labels[topic_label]
            except KeyError:
                logger.warning("label %r not in documentation", topic_label)
                continue
            doc_labels.setdefault(docname, []).append((topic_label, label_id))

        py_domain = env.domains['py']
        for module_name, module_info in py_domain.data['modules'].items():
            docname = module_info[0]
            if docname.startswith('library/'):
                doc_file = docname.replace('library/', '')
                self.module_docs[module_name] = (
                    f"{doc_file}#module-{module_name}"
                )

        for docname, label_ids in status_iterator(
            doc_labels.items(),
            "building topics... ",
            length=len(doc_labels),
            stringify_func=_display_labels,
        ):
            doctree = env.get_and_resolve_doctree(docname, builder=self)
            doc_ids = doctree.ids
            for topic_label, label_id in label_ids:
                document = new_document("<section node>")
                document.append(doc_ids[label_id])
                visitor = TextTranslator(document, builder=self)
                document.walkabout(visitor)
                body = "\n".join(map(str.rstrip, visitor.body.splitlines()))
                self.topics[topic_label] = body + "\n"

    def finish(self) -> None:
        topics_repr = "\n".join(
            f"    '{topic}': {_repr(self.topics[topic])},"
            for topic in sorted(self.topics)
        )
        topics = f"""\
# Autogenerated by Sphinx on {asctime()}
# as part of the release process.

topics = {{
{topics_repr}
}}
"""
        self.outdir.joinpath("topics.py").write_text(topics, encoding="utf-8")

        module_docs_repr = "\n".join(
            f"    '{module}': '{doc_file}',"
            for module, doc_file in sorted(self.module_docs.items())
        )
        module_docs = f"""\
# Autogenerated by Sphinx on {asctime()}
# as part of the release process.

module_docs = {{
{module_docs_repr}
}}
"""
        self.outdir.joinpath("module_docs.py").write_text(
            module_docs, encoding="utf-8"
        )


def _display_labels(item: tuple[str, Sequence[tuple[str, str]]]) -> str:
    _docname, label_ids = item
    labels = [name for name, _id in label_ids]
    if len(labels) > 4:
        return f"{labels[0]}, {labels[1]}, ..., {labels[-2]}, {labels[-1]}"
    return ", ".join(labels)


def _repr(text: str, /) -> str:
    """Return a triple-single-quoted representation of text."""
    if "'''" not in text:
        return f"r'''{text}'''"
    text = text.replace("\\", "\\\\").replace("'''", r"\'\'\'")
    return f"'''{text}'''"


def setup(app: Sphinx) -> ExtensionMetadata:
    app.add_builder(PydocTopicsBuilder)

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