"""
Sphinx Read the Docs theme.

From https://github.com/ryan-roemer/sphinx-bootstrap-theme.
"""

from os import path
from sys import version_info as python_version
from sphinx import version_info as sphinx_version
from sphinx.locale import _
from sphinx.util.logging import getLogger
import hashlib
import inspect
import os
import re
import sys
from multiprocessing import Manager
from pathlib import Path
from typing import List, Optional
from xml.etree import ElementTree

import bs4
import slugify
from bs4 import BeautifulSoup
from sphinx.util import console, logging

ROOT_SUFFIX = ""

USER_TABLE_CLASSES = []
USER_TABLE_NO_STRIP_CLASSES = ["no-sphinx-material-strip"]

__version__ = '2.0.0rc2'
__version_full__ = __version__

logger = getLogger(__name__)


def get_html_theme_path():
    """Return list of HTML theme paths."""
    cur_dir = path.abspath(path.dirname(path.dirname(__file__)))
    return cur_dir


def config_initiated(app, config):
    theme_options = config.html_theme_options or {}
    if theme_options.get('canonical_url'):
        logger.warning(
            _('The canonical_url option is deprecated, use the html_baseurl option from Sphinx instead.')
        )


def extend_html_context(app, pagename, templatename, context, doctree):
     # Add ``sphinx_version_info`` tuple for use in Jinja templates
     context['sphinx_version_info'] = sphinx_version




def update_html_context(app):
    config = app.config
    config.html_context = {**get_html_context(), **config.html_context}


def update_table_classes(app, config):
    table_classes = config.html_theme_options.get("table_classes")
    if table_classes:
        USER_TABLE_CLASSES.extend(table_classes)

    table_no_strip_classes = config.html_theme_options.get("table_no_strip")
    if table_no_strip_classes:
        USER_TABLE_NO_STRIP_CLASSES.extend(table_no_strip_classes)


def html_theme_path():
    return [os.path.dirname(os.path.abspath(__file__))]


def ul_to_list(node: bs4.element.Tag, fix_root: bool, page_name: str) -> List[dict]:
    out = []
    for child in node.find_all("li", recursive=False):
        if callable(child.isspace) and child.isspace():
            continue
        formatted = {}
        if child.a is not None:
            formatted["href"] = child.a["href"]
            print(formatted["href"], page_name)
            formatted["contents"] = "".join(map(str, child.a.contents))
            if fix_root and formatted["href"] == "#" and child.a.contents:
                slug = 'root'
                formatted["href"] = "#" + slug
            formatted["current"] = "current" in child.a.get("class", [])
        if child.ul is not None:
            formatted["children"] = ul_to_list(child.ul, fix_root, page_name)
        else:
            formatted["children"] = []
        out.append(formatted)
    return out


class CaptionList(list):
    _caption = ""

    def __init__(self, caption=""):
        super().__init__()
        self._caption = caption

    @property
    def caption(self):
        return self._caption

    @caption.setter
    def caption(self, value):
        self._caption = value


def derender_toc(
    toc_text, fix_root=True, page_name: str = "md-page-root--link"
) -> List[dict]:
    nodes = []
    try:
        toc = BeautifulSoup(toc_text, features="html.parser")
        for child in toc.children:
            if callable(child.isspace) and child.isspace():
                continue
            if child.name == "p":
                nodes.append({"caption": "".join(map(str, child.contents))})
            elif child.name == "ul":
                nodes.extend(ul_to_list(child, fix_root, page_name))
            else:
                raise NotImplementedError
    except Exception as exc:
        logger = logging.getLogger(__name__)
        logger.warning(
            "Failed to process toctree_text\n" + str(exc) + "\n" + str(toc_text)
        )

    return nodes


def walk_contents(tags):
    out = []
    for tag in tags.contents:
        if hasattr(tag, "contents"):
            out.append(walk_contents(tag))
        else:
            out.append(str(tag))
    return "".join(out)


def table_fix(body_text, page_name="md-page-root--link"):
    # This is a hack to skip certain classes of tables
    ignore_table_classes = {"highlighttable", "longtable", "dataframe"} | set(
        USER_TABLE_NO_STRIP_CLASSES
    )
    try:
        body = BeautifulSoup(body_text, features="html.parser")
        for table in body.select("table"):
            classes = set(table.get("class", tuple()))
            if classes.intersection(ignore_table_classes):
                continue
            classes = [tc for tc in classes if tc in USER_TABLE_CLASSES]
            if classes:
                table["class"] = classes
            else:
                del table["class"]
        first_h1: Optional[bs4.element.Tag] = body.find("h1")
        headers = body.find_all(re.compile("^h[1-6]$"))
        for i, header in enumerate(headers):
            for a in header.select("a"):
                if "headerlink" in a.get("class", ""):
                    header["id"] = a["href"][1:]
        if first_h1 is not None:
            slug = slugify.slugify(page_name) + ROOT_SUFFIX
            first_h1["id"] = slug
            for a in first_h1.select("a"):
                a["href"] = "#" + slug

        divs = body.find_all("div", {"class": "section"})
        for div in divs:
            div.unwrap()

        return str(body)
    except Exception as exc:
        logger = logging.getLogger(__name__)
        logger.warning("Failed to process body_text\n" + str(exc))
        return body_text


# These final lines exist to give sphinx a stable str representation of
# these two functions across runs, and to ensure that the str changes
# if the source does.
#
# Note that this would be better down with a metaclass factory
table_fix_src = inspect.getsource(table_fix)
table_fix_hash = hashlib.sha512(table_fix_src.encode()).hexdigest()
derender_toc_src = inspect.getsource(derender_toc)
derender_toc_hash = hashlib.sha512(derender_toc_src.encode()).hexdigest()


class TableFixMeta(type):
    def __repr__(self):
        return f"table_fix, hash: {table_fix_hash}"

    def __str__(self):
        return f"table_fix, hash: {table_fix_hash}"


class TableFix(object, metaclass=TableFixMeta):
    def __new__(cls, *args, **kwargs):
        return table_fix(*args, **kwargs)


class DerenderTocMeta(type):
    def __repr__(self):
        return f"derender_toc, hash: {derender_toc_hash}"

    def __str__(self):
        return f"derender_toc, hash: {derender_toc_hash}"


class DerenderToc(object, metaclass=DerenderTocMeta):
    def __new__(cls, *args, **kwargs):
        return derender_toc(*args, **kwargs)


def get_html_context():
    return {"table_fix": TableFix, "derender_toc": DerenderToc}

# See http://www.sphinx-doc.org/en/stable/theming.html#distribute-your-theme-as-a-python-package
def setup(app):
    if python_version[0] < 3:
        logger.error("Python 2 is not supported with sphinx_rtd_theme, update to Python 3.")

    app.require_sphinx('5.0')
    if app.config.html4_writer:
        logger.error("'html4_writer' is not supported with sphinx_rtd_theme.")

    # Since Sphinx 6, jquery isn't bundled anymore and we need to ensure that
    # the sphinxcontrib-jquery extension is enabled.
    # See: https://dev.readthedocs.io/en/latest/design/sphinx-jquery.html
    if sphinx_version >= (6, 0, 0):
        # Documentation of Sphinx guarantees that an extension is added and
        # enabled at most once.
        # See: https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx.application.Sphinx.setup_extension
        app.setup_extension("sphinxcontrib.jquery")
        # However, we need to call the extension's callback since setup_extension doesn't do it
        # See: https://github.com/sphinx-contrib/jquery/issues/23
        from sphinxcontrib.jquery import add_js_files as jquery_add_js_files
        jquery_add_js_files(app, app.config)

    # Register the theme that can be referenced without adding a theme path
    app.add_html_theme('sphinx_rtd_theme', path.abspath(path.dirname(__file__)))

    # Add Sphinx message catalog for newer versions of Sphinx
    # See http://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx.application.Sphinx.add_message_catalog
    rtd_locale_path = path.join(path.abspath(path.dirname(__file__)), 'locale')
    app.add_message_catalog('sphinx', rtd_locale_path)
    app.connect('config-inited', config_initiated)

    # sphinx emits the permalink icon for headers, so choose one more in keeping with our theme
    app.config.html_permalinks_icon = "\uf0c1"

    # Extend the default context when rendering the templates.
    app.connect("html-page-context", extend_html_context)
    
    app.connect("builder-inited", update_html_context)

    return {'parallel_read_safe': True, 'parallel_write_safe': True}
