from gi.repository import GLib, GObject

from threading import Thread
from typing import Callable

import markdown_it
from markdown_it.tree import SyntaxTreeNode

from iotas.note import Note


class OutlineHeading(GObject.Object):
    """Heading details for outline."""

    def __init__(self, text: str, line: int, level: int) -> None:
        super().__init__()
        self.text = text
        self.line = line
        self.level = level
        self.indent_level = 0


class OutlineGenerator(GObject.Object):
    """Generate outline from note headings."""

    def generate(self, note: Note, callback: Callable) -> None:
        """Generator outline for note.

        :param Note note: Note to render
        :param Callable callback: Method to call on completion
        """

        def thread_do():
            md = markdown_it.MarkdownIt("gfm-like")
            parser_tokens = md.parse(note.content)
            node = SyntaxTreeNode(parser_tokens)
            headings = []
            levels = []

            def add_children(in_node):
                if not in_node.is_root and in_node.type == "heading":
                    level = len(in_node.markup)
                    content = ""
                    for child in in_node.children:
                        content += child.content
                    headings.append(OutlineHeading(content, in_node.map[0], level))
                    levels.append(level)
                else:
                    for child in in_node.children:
                        add_children(child)

            add_children(node)

            # Squash the indentation levels, eg. a note with only h1 and h5 headings will show
            # as first and second level indentation
            indentation = {}
            i = 0
            for level in sorted(set(levels)):
                indentation[level] = i
                i += 1

            for heading in headings:
                heading.indent_level = indentation[heading.level]
            GLib.idle_add(callback, headings)

        thread = Thread(target=thread_do)
        thread.daemon = True
        thread.start()
