from docutils import nodes
from docutils.parsers.rst import Directive

from sphinx.application import Sphinx
from sphinx.locale import _
from sphinx.util.docutils import SphinxDirective
from sphinx.util.typing import ExtensionMetadata


class todo(nodes.Admonition, nodes.Element):
    pass


class todolist(nodes.General, nodes.Element):
    pass


def visit_todo_node(self, node):
    self.visit_admonition(node)


def depart_todo_node(self, node):
    self.depart_admonition(node)


class TodolistDirective(Directive):
    def run(self):
        return [todolist('')]


class TodoDirective(SphinxDirective):
    # this enables content in the directive
    has_content = True

    def run(self):
        targetid = 'todo-%d' % self.env.new_serialno('todo')
        targetnode = nodes.target('', '', ids=[targetid])

        todo_node = todo('\n'.join(self.content))
        todo_node += nodes.title(_('Todo'), _('Todo'))
        todo_node += self.parse_content_to_nodes()

        if not hasattr(self.env, 'todo_all_todos'):
            self.env.todo_all_todos = []

        self.env.todo_all_todos.append({
            'docname': self.env.docname,
            'lineno': self.lineno,
            'todo': todo_node.deepcopy(),
            'target': targetnode,
        })

        return [targetnode, todo_node]


def purge_todos(app, env, docname):
    if not hasattr(env, 'todo_all_todos'):
        return

    env.todo_all_todos = [
        todo for todo in env.todo_all_todos if todo['docname'] != docname
    ]


def merge_todos(app, env, docnames, other):
    if not hasattr(env, 'todo_all_todos'):
        env.todo_all_todos = []
    if hasattr(other, 'todo_all_todos'):
        env.todo_all_todos.extend(other.todo_all_todos)


def process_todo_nodes(app, doctree, fromdocname):
    if not app.config.todo_include_todos:
        for node in doctree.findall(todo):
            node.parent.remove(node)

    # Replace all todolist nodes with a list of the collected todos.
    # Augment each todo with a backlink to the original location.
    env = app.builder.env

    if not hasattr(env, 'todo_all_todos'):
        env.todo_all_todos = []

    for node in doctree.findall(todolist):
        if not app.config.todo_include_todos:
            node.replace_self([])
            continue

        content = []

        for todo_info in env.todo_all_todos:
            para = nodes.paragraph()
            filename = env.doc2path(todo_info['docname'], base=None)
            description = _(
                '(The original entry is located in %s, line %d and can be found '
            ) % (filename, todo_info['lineno'])
            para += nodes.Text(description)

            # Create a reference
            newnode = nodes.reference('', '')
            innernode = nodes.emphasis(_('here'), _('here'))
            newnode['refdocname'] = todo_info['docname']
            newnode['refuri'] = app.builder.get_relative_uri(
                fromdocname, todo_info['docname']
            )
            newnode['refuri'] += '#' + todo_info['target']['refid']
            newnode.append(innernode)
            para += newnode
            para += nodes.Text('.)')

            # Insert into the todolist
            content.extend((
                todo_info['todo'],
                para,
            ))

        node.replace_self(content)


def setup(app: Sphinx) -> ExtensionMetadata:
    app.add_config_value('todo_include_todos', False, 'html')

    app.add_node(todolist)
    app.add_node(
        todo,
        html=(visit_todo_node, depart_todo_node),
        latex=(visit_todo_node, depart_todo_node),
        text=(visit_todo_node, depart_todo_node),
    )

    app.add_directive('todo', TodoDirective)
    app.add_directive('todolist', TodolistDirective)
    app.connect('doctree-resolved', process_todo_nodes)
    app.connect('env-purge-doc', purge_todos)
    app.connect('env-merge-info', merge_todos)

    return {
        'version': '0.1',
        'env_version': 1,
        'parallel_read_safe': True,
        'parallel_write_safe': True,
    }
