"""Program to generate documentation for a given PyToolConfig object."""


from __future__ import annotations

from dataclasses import is_dataclass
from typing import TYPE_CHECKING, Any, Generator

if TYPE_CHECKING:
    from _typeshed import DataclassInstance
    from docutils.statemachine import StringList
    from sphinx.application import Sphinx

    from .pytoolconfig import PyToolConfig
    from .types import ConfigField

from typing import get_origin

from sphinx.ext.autodoc import ClassDocumenter
from tabulate import tabulate

from .fields import _gather_config_fields
from .sources import Source
from .universal_config import UniversalConfig


def _type_to_str(type_to_print: type[Any]) -> str | None:
    if type_to_print is None:
        return None
    if get_origin(type_to_print) is None:
        try:
            return type_to_print.__name__
        except AttributeError:
            return str(type_to_print)
    return str(type_to_print).replace("typing.", "")


def _subtables(model: type[DataclassInstance]) -> dict[str, type[DataclassInstance]]:
    result = {}
    for name, field in _gather_config_fields(model).items():
        if is_dataclass(field._type):
            result[name] = field._type
    return result


def _generate_table(
    model: type[DataclassInstance],
    tablefmt: str = "rst",
    prefix: str = "",
) -> Generator[str, None, None]:
    header = ["name", "description", "type", "default"]
    model_fields: dict[str, ConfigField] = _gather_config_fields(model)
    command_line = any(field.command_line for field in model_fields.values())
    if command_line:
        header.append("command line flag")
    table = []
    for name, field in model_fields.items():
        if not is_dataclass(field._type):
            row = [
                f"{name}" if not prefix else f"{prefix}.{name}",
                field.description.replace("\n", " ") if field.description else None,
                _type_to_str(field._type),
                field._default,
            ]
            if field.universal_config:
                key = field.universal_config
                assert is_dataclass(UniversalConfig)
                universal_key = _gather_config_fields(UniversalConfig)[key.name]
                row[1] = universal_key.description
                row[3] = universal_key._default
            if command_line:
                cli_doc = field.command_line
                if cli_doc is not None:
                    row.append(", ".join(cli_doc))
                else:
                    row.append(None)
            table.append(row)
    yield from tabulate(table, tablefmt=tablefmt, headers=header).split("\n")


class PyToolConfigAutoDocumenter(ClassDocumenter):

    """Sphinx autodocumenter for pytoolconfig models."""

    objtype = "pytoolconfigtable"
    content_indent = ""
    titles_allowed = True

    @classmethod
    def can_document_member(
        cls,
        member: Any,
        membername: str,  # noqa: ARG003
        isattr: bool,  # noqa: ARG003
        parent: Any,  # noqa: ARG003
    ) -> bool:
        """Check if member is dataclass."""
        return is_dataclass(member)

    def add_directive_header(self, sig: str) -> None:
        """Remove directive headers."""

    def add_content(
        self,
        more_content: StringList | None,  # noqa: ARG002
        no_docstring: bool = False,  # noqa: ARG002
    ) -> None:
        """Create simple table to document configuration options."""
        source = self.get_sourcename()
        config = self.object
        for line in _generate_table(config):
            self.add_line(line, source)


class PyToolConfigSourceDocumenter(ClassDocumenter):

    """Expiremental documenter for docmenting a source for pytoolconfig."""

    objtype = "pytoolconfigsources"
    content_indent = ""
    titles_allowed = True

    @classmethod
    def can_document_member(
        cls,
        member: Any,
        membername: str,  # noqa: ARG003
        isattr: bool,  # noqa: ARG003
        parent: Any,  # noqa: ARG003
    ) -> bool:
        """Check if member is dataclass."""
        return isinstance(member, Source)

    def add_directive_header(self, sig: str) -> None:
        """Remove directive headers."""

    def add_content(
        self,
        more_content: StringList | None,  # noqa: ARG002
        no_docstring: bool = False,  # noqa: ARG002
    ) -> None:
        """Create simple table to document configuration options."""
        source = self.get_sourcename()
        config = self.object
        for line in _generate_table(config):
            self.add_line(line, source)


def setup(app: Sphinx) -> None:
    """Register automatic documenter."""
    app.setup_extension("sphinx.ext.autodoc")
    app.add_autodocumenter(PyToolConfigAutoDocumenter)


def _generate_documentation(config: PyToolConfig) -> Generator[str, None, None]:
    """Generate Markdown documentation for a given config model.

    This currently Beta at best. Do not use.
    """
    yield "# Configuration\n"
    if len(config.sources) > 1:
        yield f"{config.tool} supports the following sources:\n"
        for idx, source in enumerate(config.sources):
            yield f" {idx}. {source.name}\n"
    else:
        name = config.sources[0].name
        yield f"{config.tool} supports the {name} format\n"
    yield "\n"
    for source in config.sources:
        if source.description:
            yield f"## {source.name} \n"
            yield source.description
            yield "\n"
    yield "## Options\n"
    yield from _generate_table(config.model, "github")
    yield "\n"
    for prefix, subtable in _subtables(config.model).items():
        yield from _generate_table(subtable, "github", prefix)
        yield "\n"
        yield "\n"
