"""Tools for the automatic source link generation for mkdocstrings."""
import importlib
import inspect
from abc import ABC, abstractstaticmethod
from pathlib import Path
from typing import Dict, Optional, Union


class BuilderUtilities(ABC):
    """BuilderUtilities is the Metaclass of Utilities.

    The Utilities are essential for building the mkdocstrings and hyperlinks to the source code.

    Args:
        ABC (class): Helper class that provides a standard way to create an ABC using
             inheritance.
    """

    @abstractstaticmethod
    def insert_in_file(markdown_text: str, file_path: Path) -> None:
        """Abstract staticmethod of `insert_in_file`."""

    @abstractstaticmethod
    def element_to_mkdocstrings(element: str, titles_size: str) -> str:
        """Abstract staticmethod of `element_to_mkdocstrings`."""

    @abstractstaticmethod
    def make_source_link(
        cls: classmethod,
        project_url: Union[str, Dict[str, str]],
        source: str = "**source code**",
    ) -> str:
        """Abstract staticmethod of `make_title`."""

    @abstractstaticmethod
    def make_title(cls: classmethod, titles_size: str, underline_title: bool) -> str:
        """Abstract staticmethod of `import_object`."""

    @abstractstaticmethod
    def return_as_Path(path: str = None) -> Optional[Path]:
        """Abstract staticmethod of `return_as_Path`."""


class Utilities(BuilderUtilities):
    """The `Utilities` build the mkdocstrings and generate the hyperlinks to the source code.

    Args:
        AbstractUtilities (class): Builder class of the abstract staticmethods of `Utilities`.
    """

    @staticmethod
    def insert_in_file(markdown_text: str, file_path: Path) -> None:
        """Insert the markdown formatted text into a new or existing file.

        Args:
            markdown_text (str): Text as string, which follows the markdown format.
            file_path (Path): Filename and path as Path object.

        Raises:
            RuntimeError: If {{autogenerated}} is not provided in a template file, it will cause a
                a **RuntimeError**.
        """
        if file_path.exists():
            template = file_path.read_text(encoding="utf-8")
            if "{{autogenerated}}" not in template:
                raise RuntimeError(
                    f"Template found for {file_path} but missing "
                    f"{{autogenerated}} tag."
                )
            markdown_text = template.replace("{{autogenerated}}", markdown_text)
            print(f"...inserting autogenerated content into template:{file_path}")
        else:
            print(f"...creating new page with autogenerated content:{file_path}")
        file_path.parent.mkdir(parents=True, exist_ok=True)
        file_path.write_text(markdown_text, encoding="utf-8")

    @staticmethod
    def element_to_mkdocstrings(element: str, titles_size: str) -> str:
        """Converts point separated string into the mkdocstrings format.

        For converting the elements to mkdocstrings, the element will added **:::** in front of the
         element string. In addition to that, the the new mkdocstrings will get subheadings.

        Args:
            element (str): String of they python class, function, or method, which has to be
                 converted to a string in the mkdocstrings format.
            titles_size (str): Current title size in the style of '#', which defines the headings.

        Returns:
            str: String of they python class, function, or method, which is converted to a string
                 in the mkdocstrings format.
        """
        return f"##{titles_size} :::{element}\n"

    @staticmethod
    def make_source_link(
        cls: classmethod,
        project_url: Union[str, Dict[str, str]],
        source: str = "**source code**",
    ) -> str:
        """Make a source link to the code basis including the linestart.

        Args:
            cls (classmethod): Convert a function to be a class method.
            project_url (Union[str, Dict[str, str]]): URL to the repository like GitHub
                 https://github.com/AI2Business/mkdocstrings-sourcelink/.
            source (str, optional): Name or sticker name for rendering the link to the
                 source.

        Returns:
            str: Hyperlink in html format with link to the repository.

        !!! tip "About *source*"
            Instead of using a string for `source = "**source code**"`, icons can be used instead
             or as combination of string + icon(s) like.
            ```python
                source = ":material-github::material-source-branch: source-code"
            ```
            In case of using material-icons, please check https://pictogrammers.github.io/@mdi/font/5.4.55/
            and replace `mdl` by `material`.
        """
        if isinstance(project_url, dict):
            if isinstance(cls, property):
                base_module = cls.fget.__module__.split(".")[0]
            else:
                base_module = cls.__module__.split(".")[0]
            project_url = project_url[base_module]

        if isinstance(cls, property):
            path = cls.fget.__module__.replace(".", "/")
            line = inspect.getsourcelines(cls.fget)[-1]
        else:
            path = cls.__module__.replace(".", "/")
            line = inspect.getsourcelines(cls)[-1]

        return (
            f'<span style="float:right;">'
            f"[{source}]({project_url}/{path}.py#L{line})"
            f"</span>"
        )

    @staticmethod
    def make_title(cls: classmethod, titles_size: str, underline_title: bool) -> str:
        """Make the title of the class, function, or method.

        Args:
            cls (classmethod): Convert a function to be a class method. In case of class properties
                 `fget` is used to read out the name of the module.
            titles_size (str): Current title size in the style of '#', which defines the headings.

        Returns:
            str: The name of the class, function, or method in a markdown conformed title.
        """
        title_underline = "\n---\n" if underline_title else "\n"
        if isinstance(cls, property):
            return f"#{titles_size} {cls.fget.__name__}{title_underline}"
        return f"#{titles_size} {cls.__name__}{title_underline}"

    @staticmethod
    def import_object(element: str) -> object:
        """Import an object like class, function, or method from a string.

        Args:
            element (str): String of class, function, or method, which should be converted to an
                 object.

        Returns:
            object: Class, function, or method object for the giving element.
        """
        last_object_got = None
        seen_names = []
        for name in element.split("."):
            seen_names.append(name)
            try:
                last_object_got = importlib.import_module(".".join(seen_names))
            except ModuleNotFoundError:
                last_object_got = getattr(last_object_got, name)

        return last_object_got

    @staticmethod
    def return_as_Path(path: str = None) -> Optional[Path]:
        """Converts strings to Path of pathlib.

        Args:
            path (str, optional): String of a filename.

        Returns:
            Optional[Path]: Path object of the initial filename.
        """
        if path:
            return Path(path)
        return None
