from docutils import nodes
from docutils.parsers.rst import roles
from sphinx import addnodes
from sphinx.environment.adapters import toctree

from . import pygments_override, translator


def setup(app):
    app.set_translator('html', translator.BootstrapTranslator)

    app.connect('html-page-context', set_missing_meta)

    app.add_js_file('js/utils.js')  # Keep in first position
    app.add_js_file('js/layout.js')
    app.add_js_file('js/menu.js')
    app.add_js_file('js/page_toc.js')
    app.add_js_file('js/switchers.js')

    roles.register_canonical_role('icon', icon_role)

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

def set_missing_meta(app, pagename, templatename, context, doctree):
    if context.get('meta') is None:  # Pages without title (used with `include::`) have no meta
        context['meta'] = {}

class Monkey:
    """ Replace patched method of an object by a new method receiving the old one in argument. """
    def __init__(self, obj):
        self.obj = obj
    def __call__(self, fn):
        name = fn.__name__
        old = getattr(self.obj, name)
        setattr(self.obj, name, lambda self_, *args, **kwargs: fn(old, self_, *args, **kwargs))

@Monkey(toctree.TocTree)
def resolve(old_resolve, tree, docname, *args, **kwargs):

    def _update_toctree_nodes(_node) -> None:
        """ Make necessary changes to Docutils' nodes of the toc.

        Internal structure of toc nodes:
        <ul>
            <li>
                <p><a/></p>
                <ul>
                    ...
                </ul>
            </li>
            <li/>
        <ul/>
        """
        if isinstance(_node, nodes.reference):  # The node is a reference (<a/>)
            _node_docname = _get_docname(_node)
            _clear_reference_if_empty_page(_node, _node_docname)
            _set_docname_as_class(_node, _node_docname)
        elif isinstance(_node, (addnodes.compact_paragraph, nodes.bullet_list, nodes.list_item)):
            for _subnode in _node.children:
                _update_toctree_nodes(_subnode)

    def _get_docname(_node):
        """ Return the docname of the targeted document.

        docname = some_common_root/foo/bar/the_page_being_rendered
        _ref = ../../contributing/documentation
        _path_parts = ['..', '..', 'contributing', 'documentation']
        _res = ['some_common_root', 'contributing', 'documentation']
        _docname = some_common_root/contributing/documentation

        :return: The docname of the document targeted by `_node`, i.e. the relative path from the
                 documentation source directory (the `content/` directory)
        :rtype: str
        """
        _ref = _node['refuri'].replace('.html', '')
        _parent_directory_occurrences = _ref.count('..')
        if not _parent_directory_occurrences and '/' not in docname:
            # The current document is at the root of the documentation source directory
            # (e.g. docname == 'index'|'applications'|...). i.e., the ref is already the docname.
            _docname = _ref
        else:
            _path_parts = _ref.split('/')
            _res = docname.split('/')[:-(_parent_directory_occurrences+1)] \
                   + _path_parts[_parent_directory_occurrences:]
            _docname = '/'.join(_res)
        return _docname

    def _clear_reference_if_empty_page(_reference_node, _node_docname):
        """ Clear reference of 'empty' toctree pages.

        Inspect parent node's siblings to determine whether the node references a toc and, if so,
        clear its reference URL. (<a href="#"/>)
        If the page has the `show-content` metadata, don't clear the reference.
        """
        if _node_docname and any(
            isinstance(_subnode, nodes.bullet_list)
            for _subnode in _reference_node.parent.parent.children
        ):  # The node references a toc
            if 'show-content' not in tree.env.metadata[_node_docname]:
                _reference_node['refuri'] = '#'  # The page must not be accessible

    def _set_docname_as_class(_reference_node, _node_docname):
        _node_docname = _node_docname or docname  # refuri==None <-> href="#"
        _reference_node.parent.parent['classes'].append(f'o_menu_{_node_docname.replace("/", "_")}')

    resolved_toc = old_resolve(tree, docname, *args, **kwargs)
    if resolved_toc:  # `resolve` returns None if the depth of the TOC to resolve is too high
        _update_toctree_nodes(resolved_toc)
    return resolved_toc


def icon_role(name, rawtext, text, lineno, inliner, options=None, content=None):
    """ Implement an `icon` role for Odoo and Font Awesome icons. """
    for icon_class in text.split():
        if not (icon_class.startswith('fa-') or icon_class.startswith('oi-')):
            report_error = inliner.reporter.error(
                f"'{icon_class}' is not a valid icon formatting class.", lineno=lineno
            )
            error_node = inliner.problematic(rawtext, rawtext, report_error)
            return [error_node], [report_error]
    if text.startswith('oi-'):
        icon_html = f'<i class="oi {text}"></i>'
    elif text.startswith('fa-'):
        icon_html = f'<i class="fa {text}"></i>'
    else:
        icon_html = f'<i class="{text}"></i>'
    node = nodes.raw('', icon_html, format='html')
    return [node], []