import importlib
from typing import get_type_hints

from docutils import nodes
from sphinx.util.docutils import SphinxDirective
from sphinx.util.logging import getLogger

from . import __version__


def valid_option(name: str):
    return (
        not name.startswith("_")
        and not name.endswith("__DOC")
        and not name.endswith("__SHORT")
        and not name.endswith("__CATEGORY")
        and name.upper() == name
        and not name.endswith("__DYNAMIC_DEFAULT")
    )


def get_row(t1: str, t2: str, t3: str) -> nodes.row:
    row = nodes.row()
    entry = nodes.entry()
    entry.append(nodes.Text(t1))
    row += entry

    entry = nodes.entry()
    entry.append(nodes.Text(t2))
    row += entry

    entry = nodes.entry()
    entry.append(format_doc(t3))

    row += entry
    return row


def get_params(module):
    params = [name for name in dir(module) if valid_option(name)]
    types = {}
    for name, hint in get_type_hints(module).items():
        types[name] = hint
        if not valid_option(name):
            continue
        # options without default values
        if name not in params:
            params.append(name)
    return params


def format_doc(text: str):
    p = nodes.paragraph()
    for part in text.split():
        uri = None
        if part.startswith("<https://"):
            if part.endswith(">."):
                uri = part[1:-2]
            elif part.endswith(">"):
                uri = part[1:-1]
        elif part.startswith("https://"):
            uri = part

        if uri:
            log.warning("URI: %s", uri)
            p.append(nodes.reference(text=part + " ", refuri=uri))
        else:
            p.append(nodes.Text(part + " "))
    return p


def make_table(params, module):
    table = nodes.table()
    tgroup = nodes.tgroup()

    colspec = nodes.colspec()

    tgroup += colspec
    tgroup += colspec
    tgroup += colspec

    table += tgroup

    thead = nodes.thead()
    thead += get_row("name", "default", "help")

    tgroup += thead

    tbody = nodes.tbody()

    for param in params:
        tbody += get_row(
            param.lower().replace("_", "-"),
            "inferred"
            if hasattr(module, param + "__DYNAMIC_DEFAULT")
            else getattr(module, param, "unset"),
            getattr(module, param + "__DOC"),
        )

    tgroup += tbody

    return table


def get_categories(params, module):
    return [
        getattr(module, param + "__CATEGORY", (float("inf"), None)) for param in params
    ]


class ConfigObj(SphinxDirective):
    has_content = True

    def run(self):
        module = importlib.import_module(self.content[0])

        params = get_params(module)
        categories = get_categories(params, module)

        cat_names = list(sorted(set(categories), key=lambda el: el[0]))

        p = nodes.paragraph()

        if len(cat_names) == 1:
            table = make_table(params, module)

            return [p, table]
        else:
            result = []
            for order, cat_name in cat_names:
                section = nodes.section()
                section_id = nodes.make_id(cat_name or "misc")
                section["ids"].append(section_id)

                title = nodes.title(text=cat_name or "Misc")
                section += title
                section += make_table(
                    [p for p, c in zip(params, categories) if c[1] == cat_name],
                    module,
                )
                result.append(section)
            return result


def setup(app):
    app.add_directive("config-obj", ConfigObj)

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


log = getLogger("config_obj")
