import importlib
import json
from typing import Dict
from typing import Optional
from typing import Tuple
from typing import Type
from typing import Union

from docutils.parsers.rst import Directive
from docutils.parsers.rst import directives as docutils_directives

from esbonio.lsp.directives import DirectiveLanguageFeature
from esbonio.lsp.directives import Directives
from esbonio.lsp.util import resources


class Docutils(DirectiveLanguageFeature):
    """Support for docutils' built-in directives."""

    def __init__(self) -> None:
        self._directives: Optional[Dict[str, Type[Directive]]] = None
        """Cache for known directives."""

    @property
    def directives(self) -> Dict[str, Type[Directive]]:
        if self._directives is not None:
            return self._directives

        ignored_directives = ["restructuredtext-test-directive"]
        found_directives = {
            **docutils_directives._directive_registry,
            **docutils_directives._directives,
        }

        self._directives = {
            k: resolve_directive(v)
            for k, v in found_directives.items()
            if k not in ignored_directives
        }

        return self._directives

    def get_implementation(self, directive: str, domain: Optional[str]):
        if domain:
            return None

        return self.directives.get(directive, None)

    def index_directives(self) -> Dict[str, Type[Directive]]:
        return self.directives


def resolve_directive(
    directive: Union[Type[Directive], Tuple[str, str]]
) -> Type[Directive]:
    """Return the directive based on the given reference.

    'Core' docutils directives are returned as tuples ``(modulename, ClassName)``
    so they need to be resolved manually.
    """

    if isinstance(directive, tuple):
        mod, cls = directive

        modulename = f"docutils.parsers.rst.directives.{mod}"
        module = importlib.import_module(modulename)
        return getattr(module, cls)

    return directive


def esbonio_setup(directives: Directives):
    documentation = resources.read_string("esbonio.lsp.rst", "directives.json")

    directives.add_documentation(json.loads(documentation))
    directives.add_feature(Docutils())
