"""This module provides the MkapiPlugin class.

MkapiPlugin is a MkDocs plugin that creates Python API documentation from Docstring.
"""
import importlib
import inspect
import logging
import os
import re
import sys
from typing import Mapping

import yaml
from mkdocs.config import config_options
from mkdocs.plugins import BasePlugin
from mkdocs.structure.files import get_files

# not nice, but the project has circular import issues
global_config = {}

import mkapi
import mkapi.plugins.api
from mkapi.core.object import get_object
from mkapi.core.page import Page
from mkapi.core.module import get_module, modules

logger = logging.getLogger("mkdocs")


class MkapiPlugin(BasePlugin):
    """MkapiPlugin class for API generation."""

    config_scheme = (
        ("src_dirs", config_options.Type(list, default=[])),
        ("on_config", config_options.Type(str, default="")),
        ("filters", config_options.Type(list, default=[])),
        ("callback", config_options.Type(str, default="")),
    )
    server = None

    def on_config(self, config, **kwargs):
        """Inserts `src_dirs` to `sys.path`."""
        config_dir = os.path.dirname(config["config_file_path"])
        for src_dir in self.config["src_dirs"]:
            path = os.path.normpath(os.path.join(config_dir, src_dir))
            if path not in sys.path:
                sys.path.insert(0, path)
        if not self.config["src_dirs"]:
            path = os.getcwd()
            if path not in sys.path:
                sys.path.insert(0, path)
        self.pages = {}
        self.abs_api_paths = []
        if not self.server:
            config, self.abs_api_paths = mkapi.plugins.api.create_nav(
                config, self.config["filters"]
            )
            global_config["config"] = config
            global_config["abs_api_paths"] = self.abs_api_paths
        else:
            config = global_config["config"]
            self.abs_api_paths = global_config["abs_api_paths"]

        if self.config["on_config"]:
            on_config = get_object(self.config["on_config"])
            kwargs = {}
            params = inspect.signature(on_config).parameters
            if "config" in params:
                kwargs["config"] = config
            if "mkapi" in params:
                kwargs["mkapi"] = self
            logger.info(f"[MkApi] Calling user 'on_config' with {list(kwargs)}")
            config_ = on_config(**kwargs)
            if config_ is not None:
                config = config_

        ext = config["markdown_extensions"]
        if "admonition" not in ext:
            config["markdown_extensions"].append("admonition")

        global_config["use_directory_urls"] = config["use_directory_urls"]
        return config

    def on_files(self, files, config, **kwargs):
        """Collects plugin CSS ans JavaScript and appends them to `files`."""
        root = os.path.join(os.path.dirname(mkapi.__file__), "theme")
        docs_dir = config["docs_dir"]
        config["docs_dir"] = root
        files_ = get_files(config)
        config["docs_dir"] = docs_dir
        theme_name = config["theme"].name

        css = []
        js = []
        for file in files_:
            path = file.src_path.replace("\\", "/")
            if path.endswith(".css"):
                if "common" in path or theme_name in path:
                    files.append(file)
                    css.append(path)
            elif path.endswith(".js"):
                files.append(file)
                js.append(path)
            elif path.endswith(".yml"):
                path = os.path.normpath(os.path.join(root, path))
                with open(path) as f:
                    data = yaml.safe_load(f)
                css = data.get("extra_css", []) + css
                js = data.get("extra_javascript", []) + js
        css = [x for x in css if x not in config["extra_css"]]
        js = [x for x in js if x not in config["extra_javascript"]]
        config["extra_css"] = css + config["extra_css"]
        config["extra_javascript"] = js + config["extra_javascript"]

        return files

    def on_page_markdown(self, markdown, page, config, files, **kwargs):
        """Converts Markdown source to intermidiate version."""
        abs_src_path = page.file.abs_src_path
        clean_page_title(page)
        page = Page(
            markdown, abs_src_path, self.abs_api_paths, filters=self.config["filters"]
        )
        self.pages[abs_src_path] = page
        return page.markdown

    def on_page_content(self, html, page, config, files, **kwargs):
        """Merges html and MkApi's node structure."""
        if page.title:
            page.title = re.sub(r"<.*?>", "", page.title)
        abs_src_path = page.file.abs_src_path
        page = self.pages[abs_src_path]
        return page.content(html)

    def on_page_context(self, context, page, config, nav, **kwargs):
        abs_src_path = page.file.abs_src_path
        if abs_src_path in self.abs_api_paths:
            clear_prefix(page.toc, 2)
        else:
            for level, id in self.pages[abs_src_path].headings:
                clear_prefix(page.toc, level, id)
        return context

    def on_serve(self, server, config, builder, **kwargs):
        for path in ["theme", "templates"]:
            root = os.path.join(os.path.dirname(mkapi.__file__), path)
            server.watch(root, builder)
        # also watch the source file as well
        nav = config["nav"]
        for keypage in nav:
            for _, page in keypage.items():
                if not isinstance(page, list):
                    continue
                for onepage in page:
                    if not isinstance(onepage, dict):
                        continue
                    _watch_dict(onepage, server, builder, config)

        self.__class__.server = server
        return server

def _watch_dict(dic, server, builder, config):
    for key, val in dic.items():
        # <module>: api/<module>.md
        if isinstance(val, str) and val.endswith(f'/{key}.md'):
            if key in modules:
                del modules[key]
            module = get_module(key)
            def build():
                # trigger markdown file rebuild
                importlib.reload(module.obj)
                # trigger html rebuild
                os.utime(
                    os.path.abspath(os.path.join(config["docs_dir"], val)),
                    None
                )
                builder()
            server.watch(module.obj.__file__, build)
        elif isinstance(val, dict):
            _watch_dict(val, server, builder, config)

def clear_prefix(toc, level: int, id: str = ""):
    for toc_item in toc:
        if toc_item.level >= level and (not id or toc_item.title == id):
            toc_item.title = toc_item.title.split(".")[-1]
        clear_prefix(toc_item.children, level)
    return


def clean_page_title(page):
    title = page.title
    if title.startswith("![mkapi]("):
        page.title = title[9:-1].split("|")[0]
