"""A lightweight book theme based on the pydata sphinx theme."""
import hashlib
import os
from pathlib import Path
from functools import lru_cache

from docutils.parsers.rst.directives.body import Sidebar
from docutils import nodes as docutil_nodes
from sphinx.application import Sphinx
from sphinx.locale import get_translation
from sphinx.util import logging
from pydata_sphinx_theme.utils import get_theme_options_dict

from .nodes import SideNoteNode
from .header_buttons import (
   prep_header_buttons,
   add_header_buttons,
    update_sourcename,
    update_context_with_repository_info,
)
#from .header_buttons.launch import add_launch_buttons
from .header_buttons.source import add_source_buttons
from ._transforms import HandleFootnoteTransform

__version__ = "1.0.1"
"""sphinx-book-theme version"""

SPHINX_LOGGER = logging.getLogger(__name__)
DEFAULT_LOG_TYPE = "sphinxbooktheme"
MESSAGE_CATALOG_NAME = "booktheme"


def get_html_theme_path():
    """Return list of HTML theme paths."""
    parent = Path(__file__).parent.resolve()
    theme_path = parent / "theme" / "tskit_book_theme"
    return theme_path


def add_metadata_to_page(app, pagename, templatename, context, doctree):
    """Adds some metadata about the page that we re-use later."""
    # Add the site title to our context so it can be inserted into the navbar
    if not context.get("root_doc"):
        # TODO: Sphinx renamed master to root in 4.x, deprecate when we drop 3.x
        context["root_doc"] = context.get("master_doc")
    context["root_title"] = app.env.titles[context["root_doc"]].astext()

    # Update the page title because HTML makes it into the page title occasionally
    if pagename in app.env.titles:
        title = app.env.titles[pagename]
        context["pagetitle"] = title.astext()

    # Add a shortened page text to the context using the sections text
    if doctree:
        description = ""
        for section in doctree.traverse(docutil_nodes.section):
            description += section.astext().replace("\n", " ")
        description = description[:160]
        context["page_description"] = description

    # Add the author if it exists
    if app.config.author != "unknown":
        context["author"] = app.config.author

    # Translations
    translation = get_translation(MESSAGE_CATALOG_NAME)
    context["translate"] = translation

    # If search text hasn't been manually specified, use a shorter one here
    theme_options = get_theme_options_dict(app)
    if "search_bar_text" not in theme_options:
        context["theme_search_bar_text"] = translation("Search") + "..."


@lru_cache(maxsize=None)
def _gen_hash(path: str) -> str:
    return hashlib.sha1(path.read_bytes()).hexdigest()


def hash_assets_for_files(assets: list, theme_static: Path, context):
    """Generate a hash for assets, and append to its entry in context.

    assets: a list of assets to hash, each path should be relative to
         the theme's static folder.

    theme_static: a path to the theme's static folder.

    context: the Sphinx context object where asset links are stored. These are:
        `css_files` and `script_files` keys.
    """
    for asset in assets:
        # CSS assets are stored in css_files, JS assets in script_files
        asset_type = "css_files" if asset.endswith(".css") else "script_files"
        if asset_type in context:
            # Define paths to the original asset file, and its linked file in Sphinx
            asset_sphinx_link = f"_static/{asset}"
            asset_source_path = theme_static / asset
            if not asset_source_path.exists():
                SPHINX_LOGGER.warning(
                    f"Asset {asset_source_path} does not exist, not linking."
                )
            # Find this asset in context, and update it to include the digest
            if asset_sphinx_link in context[asset_type]:
                hash = _gen_hash(asset_source_path)
                ix = context[asset_type].index(asset_sphinx_link)
                context[asset_type][ix] = asset_sphinx_link + "?digest=" + hash


def hash_html_assets(app, pagename, templatename, context, doctree):
    """Add ?digest={hash} to assets in order to bust cache when changes are made.

    The source files are in `static` while the built HTML is in `_static`.
    """
    assets = ["scripts/tskit-book-theme.js"]
    # Only append the book theme CSS if it's explicitly this theme. Sub-themes
    # will define their own CSS file, so if a sub-theme is used, this code is
    # run but the book theme CSS file won't be linked in Sphinx.
    if app.config.html_theme == "tskit_book_theme":
        assets.append("styles/tskit-book-theme.css")
    hash_assets_for_files(assets, get_html_theme_path() / "static", context)


def update_mode_thebe_config(app):
    """Update thebe configuration with SBT-specific values"""
    theme_options = get_theme_options_dict(app)
    if theme_options.get("launch_buttons", {}).get("thebe") is True:
        # In case somebody specifies they want thebe in a launch button
        # but has not activated the sphinx_thebe extension.
        if not hasattr(app.env.config, "thebe_config"):
            SPHINX_LOGGER.warning(
                (
                    "Thebe is activated but not added to extensions list. "
                    "Add `sphinx_thebe` to your site's extensions list."
                )
            )
            return
        # Will be empty if it doesn't exist
        thebe_config = app.env.config.thebe_config
    else:
        return

    if not theme_options.get("launch_buttons", {}).get("thebe"):
        return

    # Update the repository branch and URL
    # Assume that if there's already a thebe_config, then we don't want to over-ride
    if "repository_url" not in thebe_config:
        thebe_config["repository_url"] = theme_options.get("repository_url")
    if "repository_branch" not in thebe_config:
        branch = theme_options.get("repository_branch")
        if not branch:
            # Explicitly check in case branch is ""
            branch = "master"
        thebe_config["repository_branch"] = branch

    app.env.config.thebe_config = thebe_config


def check_deprecation_keys(app):
    """Warns about the deprecated keys."""

    deprecated_config_list = ["single_page"]
    for key in deprecated_config_list:
        if key in get_theme_options_dict(app):
            SPHINX_LOGGER.warning(
                f"'{key}' was deprecated from version 0.3.4 onwards. See the CHANGELOG for more information: https://github.com/executablebooks/sphinx-book-theme/blob/master/CHANGELOG.md"  # noqa: E501
                f"[{DEFAULT_LOG_TYPE}]",
                type=DEFAULT_LOG_TYPE,
            )


class Margin(Sidebar):
    """Goes in the margin to the right of the page."""

    optional_arguments = 1
    required_arguments = 0

    def run(self):
        """Run the directive."""
        if not self.arguments:
            self.arguments = [""]
        nodes = super().run()
        nodes[0].attributes["classes"].append("margin")

        # Remove the "title" node if it is empty
        if not self.arguments:
            nodes[0].children.pop(0)
        return nodes


def update_general_config(app):
    theme_dir = get_html_theme_path()
    # Update templates for sidebar. Needed for jupyter-book builds as jb
    # uses an instance of Sphinx class from sphinx.application to build the app.
    # The __init__ function of which calls self.config.init_values() just
    # before emitting `config-inited` event. The init_values function overwrites
    # templates_path variable.
    app.config.templates_path.append(os.path.join(theme_dir, "components"))


def update_templates(app, pagename, templatename, context, doctree):
    """Update template names and assets for page build.

    This is a copy of what the pydata theme does here to include a new section
    - https://github.com/pydata/pydata-sphinx-theme/blob/0a4894fab49befc59eb497811949a1d0ede626eb/src/pydata_sphinx_theme/__init__.py#L173 # noqa: E501
    """
    # Allow for more flexibility in template names
    template_sections = ["theme_footer_content_items"]
    for section in template_sections:
        if context.get(section):
            # Break apart `,` separated strings so we can use , in the defaults
            if isinstance(context.get(section), str):
                context[section] = [
                    ii.strip() for ii in context.get(section).split(",")
                ]

            # Add `.html` to templates with no suffix
            for ii, template in enumerate(context.get(section)):
                if not os.path.splitext(template)[1]:
                    context[section][ii] = template + ".html"


def setup(app: Sphinx):
    # Register theme
    theme_dir = get_html_theme_path()
    app.add_html_theme("tskit_book_theme", theme_dir)
    app.add_js_file("scripts/tskit-book-theme.js")

    # Translations
    locale_dir = os.path.join(theme_dir, "static", "locales")
    app.add_message_catalog(MESSAGE_CATALOG_NAME, locale_dir)

    # Events
    app.connect("builder-inited", update_mode_thebe_config)
    app.connect("builder-inited", check_deprecation_keys)
    app.connect("builder-inited", update_sourcename)
    app.connect("builder-inited", update_context_with_repository_info)
    app.connect("builder-inited", update_general_config)
    app.connect("html-page-context", add_metadata_to_page)
    app.connect("html-page-context", hash_html_assets)
    app.connect("html-page-context", update_templates)

    # Nodes
    SideNoteNode.add_node(app)

    # Header buttons
    app.connect("html-page-context", prep_header_buttons)
    # Bump priority so that it runs after the pydata theme sets up the edit URL func.
#     app.connect("html-page-context", add_launch_buttons, priority=501)
    app.connect("html-page-context", add_source_buttons, priority=501)
#     app.connect("html-page-context", add_header_buttons, priority=501)

    # Directives
    #app.add_directive("margin", Margin)

    # Post-transforms
    app.add_post_transform(HandleFootnoteTransform)

    # Update templates for sidebar, for builds where config-inited is not called
    # (does not work in case of jupyter-book)
    app.config.templates_path.append(os.path.join(theme_dir, "components"))

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