import os
import re
from mdutils.mdutils import MdUtils
from powerapps_docstring.parser import Parser
import gh_md_to_html

# https://mdutils.readthedocs.io/en/latest/examples/Example_Python.html


class Docstring():
    def __init__(self, source, output, config) -> None:
        self.source_path = os.path.normpath(source)
        self.output_path = os.path.normpath(output)
        self.parser = Parser(self.source_path)
        self.config = config
        self.manifest_file = self.parser.get_canvas_manifest()
        self.relevant_objects = self.config["RelevantObjects"]
        self.relevant_object_keys = [
            f" As {x}" for x in self.relevant_objects.keys()]
        self.mermaid_screenflow = self._create_mermaid_screenflow()

    def _get_screen_files(self):
        screen_files = []
        screens_path = os.path.join(self.source_path, "Src")

        # read screen order from manifest and check if files exists
        screen_order = self.manifest_file["ScreenOrder"]
        for screen in screen_order:
            screen_file_name = screen + ".fx.yaml"
            if screen_file_name in os.listdir(screens_path):
                if screen_file_name != "App.fx.yaml":
                    screen_files.append(screen_file_name)

        return screen_files

    def _extract_parts_from_propperty(self, propperty) -> tuple:
        """Extracts the parts from a propperty.

        Parts can be:
        - docstring
        - function

        Args:
            propperty (str): The string representation of the propperty

        Returns:
            tuple: bool for contains docstring, docstring, function
        """
        if propperty.startswith("=/*"):
            start = propperty.find("/*") + len("/*")
            end = propperty.find("*/")
            docstring = propperty[start:end]
            func = propperty[end+2:]
            return True, docstring, func.strip()
        else:
            return False, None, propperty[1:]

    def _extract_screen_content_to_markdown(self, screen_objects) -> None:
        self.md_file.new_header(level=2, title=screen_objects[0])
        self.md_file.new_line("---")

        mermaid_screens = list(
            filter(lambda x: screen_objects[0] in x, self.mermaid_screenflow))

        if mermaid_screens:
            mermaid_screens = [self.config["MermaidPrefix"]] + \
                ["graph LR"] + mermaid_screens + \
                [self.config["MermaidSuffix"]]

        for line in mermaid_screens:
            self.md_file.new_line(line)

        # add variables to screen if enabled in config
        if self.config["ScreenFlow"]["ShowVariables"]:
            # find variable with regex pattern
            global_variables = re.findall(
                r"Set\((.[^,]*)", str(screen_objects[1]))
            global_variables = list(set(global_variables))
            global_variables = self._cleanUpList(global_variables)

            self.md_file.new_header(level=3, title="Variables")
            self.md_file.new_line(
                "Following variables have been created / or updated on this screen")

            global_variables_links = list(
                map(lambda x: "["+x+"]" + "(#" + str.lower(x) + ")", global_variables))

            self.md_file.new_list(global_variables_links)

        # add collects to screen if enabled in config
        if self.config["ScreenFlow"]["ShowCollects"]:
            # find variable with regex pattern
            global_collects = re.findall(
                r"Collect\((.[^,]*)", str(screen_objects[1]))
            global_collects = list(set(global_collects))
            global_collects = self._cleanUpList(global_collects)
            self.md_file.new_header(level=3, title="Collects")
            self.md_file.new_line(
                "Following collects have been created, updated or cleared on this screen")

            global_collects_links = list(
                map(lambda x: "["+x+"]" + "(#" + str.lower(x) + ")", global_collects))

            self.md_file.new_list(global_collects_links)

        # add flows to screen if enabled in config
        if self.config["ScreenFlow"]["ShowFlows"]:
            # find variable with regex pattern
            flows = re.findall(
                r"(\w*).Run", str(screen_objects[1]))
            flows = list(set(flows))
            flows = self._cleanUpList(flows)

            self.md_file.new_header(level=3, title="Flows")
            self.md_file.new_line(
                "Following flows have used on this screen")

            flows_links = list(
                map(lambda x: "["+x+"]" + "(#" + str.lower(x) + ")", flows))

            self.md_file.new_list(flows_links)

        def _recursive(key, value):
            # check for object
            rel_obj_key = None

            for rel_obj in self.relevant_objects.keys():
                if f" As {rel_obj}" in key:
                    rel_obj_key = rel_obj
                    break

            # key is header
            if rel_obj_key != None:
                # we found a header
                self.md_file.new_header(level=3, title=key)

                # check if value has content (deals with blank screens)
                if not value:
                    return

                for sub_dict_key, sub_dict_item in value.items():
                    if sub_dict_key in self.relevant_objects.get(rel_obj_key):
                        if sub_dict_key == "OnVisible":
                            screen_docstring = self._extract_parts_from_propperty(
                                sub_dict_item)
                            # check if docstring exists then add screen docstring
                            if screen_docstring[0]:
                                self.md_file.new_paragraph(screen_docstring[1])
                                sub_dict_item = screen_docstring[2]
                        if sub_dict_item != "":
                            self.md_file.new_header(
                                level=4, title=sub_dict_key)
                            self.md_file.insert_code(str(sub_dict_item))

                    elif type(sub_dict_item) == dict:
                        # we found another dict so start over
                        for new_key, new_value in sub_dict_item.items():
                            _recursive(new_key, new_value)

        for key, value in screen_objects[1].items():
            _recursive(key, value)

    def _create_mermaid_screenflow(self):

        # from https://stackoverflow.com/a/20254842
        def get_recursively(search_dict, field):
            """
            Takes a dict with nested lists and dicts,
            and searches all dicts for a key of the field
            provided.
            """
            fields_found = []

            for key, value in search_dict.items():

                if key == field:
                    fields_found.append(value)

                elif isinstance(value, dict):
                    results = get_recursively(value, field)
                    for result in results:
                        fields_found.append(result)

                elif isinstance(value, list):
                    for item in value:
                        if isinstance(item, dict):
                            more_results = get_recursively(item, field)
                            for another_result in more_results:
                                fields_found.append(another_result)

            return fields_found

        # create header for mermaid graph
        screenflow_list = [self.config["MermaidPrefix"]]
        screenflow_list.append("graph LR")

        screen_files = self._get_screen_files()

        for screen in screen_files:
            # check if screen has been excluded
            if screen.replace(".fx.yaml", "") not in self.config["ScreenFlow"]["ExcludeScreens"]:
                screen_obj = self.parser.get_screen_objects(screen)
                from_screen = screen_obj[0]
                list_of_onselects = get_recursively(screen_obj[1], "OnSelect")

                for item in list_of_onselects:
                    if "Navigate(" in item:

                        regex = r"=Navigate\W{1,2}(.*?)[^a-zA-Z0-9_ ]"
                        matches = re.search(regex, item)

                        if matches:
                            groupLen = len(matches.groups())

                            to_screen = matches.group(1)

                            # # item = item.strip().replace("\n", "").replace("\t", "")
                            # navigate_occurences = [m.start() for m in re.finditer(
                            #     'Navigate\(', item)]

                            # for occurence in navigate_occurences:
                            #     # print(occurence)
                            #     start = occurence + len("Navigate(")
                            #     # print(start)
                            #     end = item[start:].find(",")
                            #     if end == -1:
                            #         end = item[start:].find(")")
                            #     end = end + start
                            #     to_screen = item[start:end]
                            #     to_screen = to_screen.replace("\n", "").replace(
                            #         "\t", "").replace(")", "").replace("'", "")
                            if to_screen != None and to_screen != "" and not to_screen.startswith("[@") and to_screen not in self.config["ScreenFlow"]["ExcludeScreens"]:
                                screenflow_list.append(
                                    "".join(from_screen.split()) + "(" + from_screen + ")" +
                                    " --> " +
                                    "".join(to_screen.split()) +
                                    "(" + to_screen + ")"
                                )
                                    # from_screen_lower_case = str.lower(from_screen)
                                    # screenflow_list.append(f'click {from_screen} "#{from_screen_lower_case}" "link"')

        screenflow_list.append(self.config["MermaidSuffix"])

        # to avoid double entrys in the graph
        # the doubled items are removed by convertig to dict and back to list
        screenflow_list = list(dict.fromkeys(screenflow_list))

        return screenflow_list  # "\n".join(screenflow_list)

    def _create_chapter_app(self, app_name):
        # # APP # #
        # get contents from App.fx.yaml
        app_screen = self.parser.get_screen_objects("App.fx.yaml")

        # read StartScreen and OnStart propperties from App
        start_screen = app_screen[1]["App As appinfo"].get("StartScreen")
        on_start = app_screen[1]["App As appinfo"].get("OnStart")

        # create heading for app info
        self.md_file.new_line("")
        self.md_file.new_line("")
        self.md_file.new_header(level=1, title=app_name)

        # write app info
        if start_screen != None:
            appinfo = self._extract_parts_from_propperty(start_screen)
            self.md_file.new_line("")
            self.md_file.new_header(level=2, title="StartScreen")
            self.md_file.insert_code(appinfo[2], language='typescript')
            self.md_file.new_line("")

        if on_start != None:
            appinfo = self._extract_parts_from_propperty(on_start)
            if appinfo[1] != None:
                self.md_file.new_paragraph(appinfo[1])
            self.md_file.new_line("")
            self.md_file.new_header(level=2, title="OnStart")
            self.md_file.insert_code(appinfo[2])

    def _create_chapter_connections(self):
        # # Connections # #
        # writing connections header
        self.md_file.new_header(level=1, title="Connections")
        connections = self.parser.get_connections()
        if len(connections) > 0:
            for connection in connections.items():
                # not all connections have an apiTier
                try:
                    api_tier = f" ({connection[1]['connectionRef']['apiTier']})"
                except KeyError:
                    api_tier = ""

                self.md_file.new_line(connection[1]["connectionRef"]["displayName"] +
                                      api_tier, "b")    # Header
                self.md_file.new_line("")

                for parameter in connection[1]["connectionParameters"].items():
                    self.md_file.new_line(f"{parameter[0]}: {parameter[1]}")

                self.md_file.new_line("")
                self.md_file.new_line("With following datasources:")
                self.md_file.new_line("")
                self.md_file.new_list(
                    [x for x in connection[1]["dataSources"]])
                self.md_file.new_line("")

    def _create_chapter_screens(self):
        # # Screen # #
        self.md_file.new_header(level=1, title="Screens")
        # TODO: add screenflow visualization

        # self.md_file.new_paragraph(self._create_mermaid_screenflow())
        for scr_flow in self.mermaid_screenflow:
            self.md_file.new_line(scr_flow)

        # loop thru all screens and create markdown
        for file in self._get_screen_files():
            screen_objects = self.parser.get_screen_objects(file)
            self._extract_screen_content_to_markdown(screen_objects)


    def _create_global_variables(self):
        screen_set_variables = {}
        screen_use_variables = {}

        for file in self._get_screen_files():
            # check on which screen variables are set
            screen_objects = self.parser.get_screen_objects(file)
            global_variables_on_screen = re.findall(
                r"Set\((.[^,]*)", str(screen_objects[1]))
            global_variables_on_screen = list(set(global_variables_on_screen))
            screen_set_variables[screen_objects[0]
                                 ] = global_variables_on_screen

            global_variables_on_screen : list = self._cleanUpList(global_variables_on_screen)

        # get all set global variables
        all_global_variables = [
            item for sublist in screen_set_variables.values() for item in sublist]
        all_global_variables = list(set(all_global_variables))

        for file in self._get_screen_files():
            screen_objects = self.parser.get_screen_objects(file)
            used_variables_on_screen = []

            # check if variables are used on this screen
            for variable in all_global_variables:
                used_variables_on_screen = used_variables_on_screen + \
                    re.findall(f"(?<!Set\(){variable}", str(screen_objects[1]))

            # remove duplicates
            used_variables_on_screen = list(set(used_variables_on_screen))
            screen_use_variables[screen_objects[0]] = used_variables_on_screen

        # create mermaid for each variable (where set and where used)
        self.md_file.new_header(level=1, title="Global Variables")
        self.md_file.new_line(
            "Usage of global variables is shown based on the screen(s) where this variable is set and the screen(s) where it is used. ")

        for variable in all_global_variables:
            variable_mermaid = [self.config["MermaidPrefix"]]
            variable_mermaid.append("graph LR")

            for key, value in screen_set_variables.items():
                if variable in value:
                    variable_mermaid.append(
                        "Set" + "".join(key.split()) + r"(" + key + r")" +
                        "-- set -->" +
                        "".join(variable.split()) + r"[/" + variable + r"/]"
                    )

            for key, value in screen_use_variables.items():
                if variable in value:
                    variable_mermaid.append(
                        "".join(variable.split()) + r"[/" + variable + r"/]" +
                        "-. use .->" +
                        "Use" + "".join(key.split()) + r"(" + key + r")"
                    )

            variable_mermaid.append(self.config["MermaidSuffix"])

            variable_mermaid = list(dict.fromkeys(variable_mermaid))

            if len(variable_mermaid) > 2:
                self.md_file.new_header(level=2, title=variable)

                for line in variable_mermaid:
                    self.md_file.new_line(line)


    def _cleanUpList(self, li):
        li = [x.replace('\\n', '') for x in li]
        li = [x.replace('\n', '') for x in li]
        li = [x.strip() for x in li]
        return li

    def _create_global_collects(self):
        screen_set_collects = {}
        screen_clear_collects = {}
        screen_use_collects = {}
        screen_all_collects = {}

        for file in self._get_screen_files():
            # check on which screen variables are set
            screen_objects = self.parser.get_screen_objects(file)

            all_collects_on_screen = re.findall(
                r"Collect\((.[^,]*)", str(screen_objects[1]))

            all_collects_on_screen = self._cleanUpList(all_collects_on_screen)

            s= str(screen_objects[1])
            r= re.compile(
                r"Collect\((?P<collect>.[^,]*)|ClearCollect\((?P<clearCollect>.[^,]*)")
            groups = [m.groupdict() for m in r.finditer(s)]

            collects_on_screen = [d["collect"]
                                  for d in groups if d['collect']]
            collects_on_screen = self._cleanUpList(collects_on_screen)

            clearCollects_on_screen = [d["clearCollect"]
                                       for d in groups if d['clearCollect']]
            clearCollects_on_screen = self._cleanUpList(
                clearCollects_on_screen)

            all_collects_on_screen = list(set(all_collects_on_screen))
            collects_on_screen = list(set(collects_on_screen))
            clearCollects_on_screen = list(set(clearCollects_on_screen))

            screen_all_collects[screen_objects[0]
                                ] = all_collects_on_screen

            screen_set_collects[screen_objects[0]
                                ] = collects_on_screen

            screen_clear_collects[screen_objects[0]
                                  ] = clearCollects_on_screen

        # get all collects
        all_collects = [
            item for sublist in screen_all_collects.values() for item in sublist]
        all_collects = list(set(all_collects))

        for file in self._get_screen_files():
            screen_objects = self.parser.get_screen_objects(file)
            used_collects_on_screen = []

            # check if collects are used on this screen
            for collect in all_collects:
                used_collects_on_screen = used_collects_on_screen + \
                    re.findall(
                        f"(?<!Collect\(){collect}", str(screen_objects[1]))
                used_collects_on_screen = self._cleanUpList(
                    used_collects_on_screen)


            # remove duplicates
            used_collects_on_screen = list(set(used_collects_on_screen))
            screen_use_collects[screen_objects[0]] = used_collects_on_screen

        # create mermaid for each variable (where set, where used and cleared)
        self.md_file.new_header(level=1, title="Global Collects")
        self.md_file.new_line(
            "Usage of global collects is shown based on the screen(s) where this collect is set, used or cleared")

        for variable in all_collects:
            variable_mermaid = [self.config["MermaidPrefix"]]
            variable_mermaid.append("graph LR")

            for key, value in screen_set_collects.items():
                if variable in value:
                    variable_mermaid.append(
                        "Collect" + "".join(key.split()) + r"(" + key + r")" +
                        "-- collect -->" +
                        "".join(variable.split()) + r"[/" + variable + r"/]"
                    )

            for key, value in screen_use_collects.items():
                if variable in value:
                    variable_mermaid.append(
                        "".join(variable.split()) + r"[/" + variable + r"/]" +
                        "-. use .->" +
                        "Use" + "".join(key.split()) + r"(" + key + r")"
                    )

            for key, value in screen_clear_collects.items():
                if variable in value:
                    variable_mermaid.append(
                        "".join(variable.split()) + r"[/" + variable + r"/]" +
                        "-. clear .->" +
                        "Clear" + "".join(key.split()) + r"(" + key + r")"
                    )

            variable_mermaid.append(self.config["MermaidSuffix"])

            variable_mermaid = list(dict.fromkeys(variable_mermaid))

            if len(variable_mermaid) > 2:
                self.md_file.new_header(level=2, title=variable)

                for line in variable_mermaid:
                    self.md_file.new_line(line)

    def _create_global_flows(self):
        screen_use_flows = {}

        for file in self._get_screen_files():
            # check on which screen variables are set
            screen_objects = self.parser.get_screen_objects(file)
            global_flows_on_screen = re.findall(
                r"(\w*).Run", str(screen_objects[1]))
            global_flows_on_screen = list(set(global_flows_on_screen))
            screen_use_flows[screen_objects[0]
                                 ] = global_flows_on_screen

            global_flows_on_screen = self._cleanUpList(global_flows_on_screen)

        # get all set global flows
        all_global_flows = [
            item for sublist in screen_use_flows.values() for item in sublist]
        all_global_flows = list(set(all_global_flows))

        for file in self._get_screen_files():
            screen_objects = self.parser.get_screen_objects(file)
            used_flows_on_screen = []

            # check if variables are used on this screen
            for variable in all_global_flows:
                used_flows_on_screen = used_flows_on_screen + \
                    re.findall(f"{variable}(?<!.Run)", str(screen_objects[1]))

            # remove duplicates
            used_flows_on_screen = list(set(used_flows_on_screen))
            screen_use_flows[screen_objects[0]] = used_flows_on_screen

        # create mermaid for each variable (where set and where used)
        self.md_file.new_header(level=1, title="Global Flows")
        self.md_file.new_line(
            "Usage of global flows is shown based on the screen(s) where this flows is executed. ")

        for variable in all_global_flows:
            variable_mermaid = [self.config["MermaidPrefix"]]
            variable_mermaid.append("graph LR")

            for key, value in screen_use_flows.items():
                if variable in value:
                    variable_mermaid.append(
                        "".join(variable.split()) + r"[/" + variable + r"/]" +
                        "-. executed_from .->" +
                        "executed_from" + "".join(key.split()) + r"(" + key + r")"
                    )

            variable_mermaid.append(self.config["MermaidSuffix"])

            variable_mermaid = list(dict.fromkeys(variable_mermaid))

            if len(variable_mermaid) > 2:
                self.md_file.new_header(level=2, title=variable)

                for line in variable_mermaid:
                    self.md_file.new_line(line)



    def create_documentation(self, format=None):

        if format == None:
            self.format = "markdown"
        else:
            self.format = format

        # instantiate the md file
        # TODO: get title from docstring variable
        app_name = self.manifest_file["PublishInfo"]["AppName"]
        output_file = os.path.join(self.output_path, f'{app_name}-doc')
        self.md_file = MdUtils(file_name=output_file,
                               title='Power App Documentation')

        for chapter in self.config["DocumentStructure"]:
            if chapter == "App":
                self._create_chapter_app(app_name=app_name)
            elif chapter == "Connections":
                self._create_chapter_connections()
            elif chapter == "Screens":
                self._create_chapter_screens()
            elif chapter == "GlobalVariables":
                self._create_global_variables()
            elif chapter == "GlobalCollects":
                self._create_global_collects()
            elif chapter == "GlobalFlows":
                self._create_global_flows()
    


        # write toc + file
        self.md_file.new_table_of_contents(table_title='Contents', depth=2)
        self.md_file.create_md_file()

        documentation_output_path = output_file + ".md"

        print(f"Start HTML Documentation creating")
        html_path = documentation_output_path + '.html'
        html = gh_md_to_html.main(documentation_output_path)
        
        with open(html_path, "w") as html_file:
            html_file.write(html)
        html_file.close

        with open(html_path) as fin:
            html_lines = fin.readlines()
        fin.close

        new_lines = [r'<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>']
        new_lines.append(r'<script>mermaid.initialize({ startOnLoad: true });</script>')

        for line in html_lines:
            if ':::' or '<br/>' in line:
                line = (line.replace('<br/>', ''))
                line = (line.replace(':::mermaid', '<div class="mermaid">'))
                line = (line.replace(':::', '</div>'))
                new_lines.append(line)
            else:
                new_lines.append(line)

        fout = open(html_path, "w")
        fout.writelines(new_lines)
        fout.close()

        #copyfile(html_path,self.output_path + '/export.html')
        

        print(f"HTML Documentation created successfully: {html_path}")

        return documentation_output_path

import os
import re
from mdutils.mdutils import MdUtils
from powerapps_docstring.parser import Parser
import gh_md_to_html

# https://mdutils.readthedocs.io/en/latest/examples/Example_Python.html


class Docstring():
    def __init__(self, source, output, config) -> None:
        self.source_path = os.path.normpath(source)
        self.output_path = os.path.normpath(output)
        self.parser = Parser(self.source_path)
        self.config = config
        self.manifest_file = self.parser.get_canvas_manifest()
        self.relevant_objects = self.config["RelevantObjects"]
        self.relevant_object_keys = [
            f" As {x}" for x in self.relevant_objects.keys()]
        self.mermaid_screenflow = self._create_mermaid_screenflow()

    def _get_screen_files(self):
        screen_files = []
        screens_path = os.path.join(self.source_path, "Src")

        # read screen order from manifest and check if files exists
        screen_order = self.manifest_file["ScreenOrder"]
        for screen in screen_order:
            screen_file_name = screen + ".fx.yaml"
            if screen_file_name in os.listdir(screens_path):
                if screen_file_name != "App.fx.yaml":
                    screen_files.append(screen_file_name)

        return screen_files

    def _extract_parts_from_propperty(self, propperty) -> tuple:
        """Extracts the parts from a propperty.

        Parts can be:
        - docstring
        - function

        Args:
            propperty (str): The string representation of the propperty

        Returns:
            tuple: bool for contains docstring, docstring, function
        """
        if propperty.startswith("=/*"):
            start = propperty.find("/*") + len("/*")
            end = propperty.find("*/")
            docstring = propperty[start:end]
            func = propperty[end+2:]
            return True, docstring, func.strip()
        else:
            return False, None, propperty[1:]

    def _extract_screen_content_to_markdown(self, screen_objects) -> None:
        self.md_file.new_header(level=2, title=screen_objects[0])
        self.md_file.new_line("---")

        mermaid_screens = list(
            filter(lambda x: screen_objects[0] in x, self.mermaid_screenflow))

        if mermaid_screens:
            mermaid_screens = [self.config["MermaidPrefix"]] + \
                ["graph LR"] + mermaid_screens + \
                [self.config["MermaidSuffix"]]

        for line in mermaid_screens:
            self.md_file.new_line(line)

        # add variables to screen if enabled in config
        if self.config["ScreenFlow"]["ShowVariables"]:
            # find variable with regex pattern
            global_variables = re.findall(
                r"Set\((.[^,]*)", str(screen_objects[1]))
            global_variables = list(set(global_variables))
            global_variables = self._cleanUpList(global_variables)

            self.md_file.new_header(level=3, title="Variables")
            self.md_file.new_line(
                "Following variables have been created / or updated on this screen")

            global_variables_links = list(
                map(lambda x: "["+x+"]" + "(#" + str.lower(x) + ")", global_variables))

            self.md_file.new_list(global_variables_links)

        # add collects to screen if enabled in config
        if self.config["ScreenFlow"]["ShowCollects"]:
            # find variable with regex pattern
            global_collects = re.findall(
                r"Collect\((.[^,]*)", str(screen_objects[1]))
            global_collects = list(set(global_collects))
            global_collects = self._cleanUpList(global_collects)
            self.md_file.new_header(level=3, title="Collects")
            self.md_file.new_line(
                "Following collects have been created, updated or cleared on this screen")

            global_collects_links = list(
                map(lambda x: "["+x+"]" + "(#" + str.lower(x) + ")", global_collects))

            self.md_file.new_list(global_collects_links)

        # add flows to screen if enabled in config
        if self.config["ScreenFlow"]["ShowFlows"]:
            # find variable with regex pattern
            flows = re.findall(
                r"(\w*).Run", str(screen_objects[1]))
            flows = list(set(flows))
            flows = self._cleanUpList(flows)

            self.md_file.new_header(level=3, title="Flows")
            self.md_file.new_line(
                "Following flows have used on this screen")

            flows_links = list(
                map(lambda x: "["+x+"]" + "(#" + str.lower(x) + ")", flows))

            self.md_file.new_list(flows_links)

        def _recursive(key, value):
            # check for object
            rel_obj_key = None

            for rel_obj in self.relevant_objects.keys():
                if f" As {rel_obj}" in key:
                    rel_obj_key = rel_obj
                    break

            # key is header
            if rel_obj_key != None:
                # we found a header
                self.md_file.new_header(level=3, title=key)

                # check if value has content (deals with blank screens)
                if not value:
                    return

                for sub_dict_key, sub_dict_item in value.items():
                    if sub_dict_key in self.relevant_objects.get(rel_obj_key):
                        if sub_dict_key == "OnVisible":
                            screen_docstring = self._extract_parts_from_propperty(
                                sub_dict_item)
                            # check if docstring exists then add screen docstring
                            if screen_docstring[0]:
                                self.md_file.new_paragraph(screen_docstring[1])
                                sub_dict_item = screen_docstring[2]
                        if sub_dict_item != "":
                            self.md_file.new_header(
                                level=4, title=sub_dict_key)
                            self.md_file.insert_code(str(sub_dict_item))

                    elif type(sub_dict_item) == dict:
                        # we found another dict so start over
                        for new_key, new_value in sub_dict_item.items():
                            _recursive(new_key, new_value)

        for key, value in screen_objects[1].items():
            _recursive(key, value)

    def _create_mermaid_screenflow(self):

        # from https://stackoverflow.com/a/20254842
        def get_recursively(search_dict, field):
            """
            Takes a dict with nested lists and dicts,
            and searches all dicts for a key of the field
            provided.
            """
            fields_found = []

            for key, value in search_dict.items():

                if key == field:
                    fields_found.append(value)

                elif isinstance(value, dict):
                    results = get_recursively(value, field)
                    for result in results:
                        fields_found.append(result)

                elif isinstance(value, list):
                    for item in value:
                        if isinstance(item, dict):
                            more_results = get_recursively(item, field)
                            for another_result in more_results:
                                fields_found.append(another_result)

            return fields_found

        # create header for mermaid graph
        screenflow_list = [self.config["MermaidPrefix"]]
        screenflow_list.append("graph LR")

        screen_files = self._get_screen_files()

        for screen in screen_files:
            # check if screen has been excluded
            if screen.replace(".fx.yaml", "") not in self.config["ScreenFlow"]["ExcludeScreens"]:
                screen_obj = self.parser.get_screen_objects(screen)
                from_screen = screen_obj[0]
                list_of_onselects = get_recursively(screen_obj[1], "OnSelect")

                for item in list_of_onselects:
                    if "Navigate(" in item:

                        regex = r"=Navigate\W{1,2}(.*?)[^a-zA-Z0-9_ ]"
                        matches = re.search(regex, item)

                        if matches:
                            groupLen = len(matches.groups())

                            to_screen = matches.group(1)

                            # # item = item.strip().replace("\n", "").replace("\t", "")
                            # navigate_occurences = [m.start() for m in re.finditer(
                            #     'Navigate\(', item)]

                            # for occurence in navigate_occurences:
                            #     # print(occurence)
                            #     start = occurence + len("Navigate(")
                            #     # print(start)
                            #     end = item[start:].find(",")
                            #     if end == -1:
                            #         end = item[start:].find(")")
                            #     end = end + start
                            #     to_screen = item[start:end]
                            #     to_screen = to_screen.replace("\n", "").replace(
                            #         "\t", "").replace(")", "").replace("'", "")
                            if to_screen != None and to_screen != "" and not to_screen.startswith("[@") and to_screen not in self.config["ScreenFlow"]["ExcludeScreens"]:
                                screenflow_list.append(
                                    "".join(from_screen.split()) + "(" + from_screen + ")" +
                                    " --> " +
                                    "".join(to_screen.split()) +
                                    "(" + to_screen + ")"
                                )
                                    # from_screen_lower_case = str.lower(from_screen)
                                    # screenflow_list.append(f'click {from_screen} "#{from_screen_lower_case}" "link"')

        screenflow_list.append(self.config["MermaidSuffix"])

        # to avoid double entrys in the graph
        # the doubled items are removed by convertig to dict and back to list
        screenflow_list = list(dict.fromkeys(screenflow_list))

        return screenflow_list  # "\n".join(screenflow_list)

    def _create_chapter_app(self, app_name):
        # # APP # #
        # get contents from App.fx.yaml
        app_screen = self.parser.get_screen_objects("App.fx.yaml")

        # read StartScreen and OnStart propperties from App
        start_screen = app_screen[1]["App As appinfo"].get("StartScreen")
        on_start = app_screen[1]["App As appinfo"].get("OnStart")

        # create heading for app info
        self.md_file.new_line("")
        self.md_file.new_line("")
        self.md_file.new_header(level=1, title=app_name)

        # write app info
        if start_screen != None:
            appinfo = self._extract_parts_from_propperty(start_screen)
            self.md_file.new_line("")
            self.md_file.new_header(level=2, title="StartScreen")
            self.md_file.insert_code(appinfo[2], language='typescript')
            self.md_file.new_line("")

        if on_start != None:
            appinfo = self._extract_parts_from_propperty(on_start)
            if appinfo[1] != None:
                self.md_file.new_paragraph(appinfo[1])
            self.md_file.new_line("")
            self.md_file.new_header(level=2, title="OnStart")
            self.md_file.insert_code(appinfo[2])

    def _create_chapter_connections(self):
        # # Connections # #
        # writing connections header
        self.md_file.new_header(level=1, title="Connections")
        connections = self.parser.get_connections()
        if len(connections) > 0:
            for connection in connections.items():
                # not all connections have an apiTier
                try:
                    api_tier = f" ({connection[1]['connectionRef']['apiTier']})"
                except KeyError:
                    api_tier = ""

                self.md_file.new_line(connection[1]["connectionRef"]["displayName"] +
                                      api_tier, "b")    # Header
                self.md_file.new_line("")

                for parameter in connection[1]["connectionParameters"].items():
                    self.md_file.new_line(f"{parameter[0]}: {parameter[1]}")

                self.md_file.new_line("")
                self.md_file.new_line("With following datasources:")
                self.md_file.new_line("")
                self.md_file.new_list(
                    [x for x in connection[1]["dataSources"]])
                self.md_file.new_line("")

    def _create_chapter_screens(self):
        # # Screen # #
        self.md_file.new_header(level=1, title="Screens")
        # TODO: add screenflow visualization

        # self.md_file.new_paragraph(self._create_mermaid_screenflow())
        for scr_flow in self.mermaid_screenflow:
            self.md_file.new_line(scr_flow)

        # loop thru all screens and create markdown
        for file in self._get_screen_files():
            screen_objects = self.parser.get_screen_objects(file)
            self._extract_screen_content_to_markdown(screen_objects)


    def _create_global_variables(self):
        screen_set_variables = {}
        screen_use_variables = {}

        for file in self._get_screen_files():
            # check on which screen variables are set
            screen_objects = self.parser.get_screen_objects(file)
            global_variables_on_screen = re.findall(
                r"Set\((.[^,]*)", str(screen_objects[1]))
            global_variables_on_screen = list(set(global_variables_on_screen))
            screen_set_variables[screen_objects[0]
                                 ] = global_variables_on_screen

            global_variables_on_screen : list = self._cleanUpList(global_variables_on_screen)

        # get all set global variables
        all_global_variables = [
            item for sublist in screen_set_variables.values() for item in sublist]
        all_global_variables = list(set(all_global_variables))

        for file in self._get_screen_files():
            screen_objects = self.parser.get_screen_objects(file)
            used_variables_on_screen = []

            # check if variables are used on this screen
            for variable in all_global_variables:
                used_variables_on_screen = used_variables_on_screen + \
                    re.findall(f"(?<!Set\(){variable}", str(screen_objects[1]))

            # remove duplicates
            used_variables_on_screen = list(set(used_variables_on_screen))
            screen_use_variables[screen_objects[0]] = used_variables_on_screen

        # create mermaid for each variable (where set and where used)
        self.md_file.new_header(level=1, title="Global Variables")
        self.md_file.new_line(
            "Usage of global variables is shown based on the screen(s) where this variable is set and the screen(s) where it is used. ")

        for variable in all_global_variables:
            variable_mermaid = [self.config["MermaidPrefix"]]
            variable_mermaid.append("graph LR")

            for key, value in screen_set_variables.items():
                if variable in value:
                    variable_mermaid.append(
                        "Set" + "".join(key.split()) + r"(" + key + r")" +
                        "-- set -->" +
                        "".join(variable.split()) + r"[/" + variable + r"/]"
                    )

            for key, value in screen_use_variables.items():
                if variable in value:
                    variable_mermaid.append(
                        "".join(variable.split()) + r"[/" + variable + r"/]" +
                        "-. use .->" +
                        "Use" + "".join(key.split()) + r"(" + key + r")"
                    )

            variable_mermaid.append(self.config["MermaidSuffix"])

            variable_mermaid = list(dict.fromkeys(variable_mermaid))

            if len(variable_mermaid) > 2:
                self.md_file.new_header(level=2, title=variable)

                for line in variable_mermaid:
                    self.md_file.new_line(line)


    def _cleanUpList(self, li):
        li = [x.replace('\\n', '') for x in li]
        li = [x.replace('\n', '') for x in li]
        li = [x.strip() for x in li]
        return li

    def _create_global_collects(self):
        screen_set_collects = {}
        screen_clear_collects = {}
        screen_use_collects = {}
        screen_all_collects = {}

        for file in self._get_screen_files():
            # check on which screen variables are set
            screen_objects = self.parser.get_screen_objects(file)

            all_collects_on_screen = re.findall(
                r"Collect\((.[^,]*)", str(screen_objects[1]))

            all_collects_on_screen = self._cleanUpList(all_collects_on_screen)

            s= str(screen_objects[1])
            r= re.compile(
                r"Collect\((?P<collect>.[^,]*)|ClearCollect\((?P<clearCollect>.[^,]*)")
            groups = [m.groupdict() for m in r.finditer(s)]

            collects_on_screen = [d["collect"]
                                  for d in groups if d['collect']]
            collects_on_screen = self._cleanUpList(collects_on_screen)

            clearCollects_on_screen = [d["clearCollect"]
                                       for d in groups if d['clearCollect']]
            clearCollects_on_screen = self._cleanUpList(
                clearCollects_on_screen)

            all_collects_on_screen = list(set(all_collects_on_screen))
            collects_on_screen = list(set(collects_on_screen))
            clearCollects_on_screen = list(set(clearCollects_on_screen))

            screen_all_collects[screen_objects[0]
                                ] = all_collects_on_screen

            screen_set_collects[screen_objects[0]
                                ] = collects_on_screen

            screen_clear_collects[screen_objects[0]
                                  ] = clearCollects_on_screen

        # get all collects
        all_collects = [
            item for sublist in screen_all_collects.values() for item in sublist]
        all_collects = list(set(all_collects))

        for file in self._get_screen_files():
            screen_objects = self.parser.get_screen_objects(file)
            used_collects_on_screen = []

            # check if collects are used on this screen
            for collect in all_collects:
                used_collects_on_screen = used_collects_on_screen + \
                    re.findall(
                        f"(?<!Collect\(){collect}", str(screen_objects[1]))
                used_collects_on_screen = self._cleanUpList(
                    used_collects_on_screen)


            # remove duplicates
            used_collects_on_screen = list(set(used_collects_on_screen))
            screen_use_collects[screen_objects[0]] = used_collects_on_screen

        # create mermaid for each variable (where set, where used and cleared)
        self.md_file.new_header(level=1, title="Global Collects")
        self.md_file.new_line(
            "Usage of global collects is shown based on the screen(s) where this collect is set, used or cleared")

        for variable in all_collects:
            variable_mermaid = [self.config["MermaidPrefix"]]
            variable_mermaid.append("graph LR")

            for key, value in screen_set_collects.items():
                if variable in value:
                    variable_mermaid.append(
                        "Collect" + "".join(key.split()) + r"(" + key + r")" +
                        "-- collect -->" +
                        "".join(variable.split()) + r"[/" + variable + r"/]"
                    )

            for key, value in screen_use_collects.items():
                if variable in value:
                    variable_mermaid.append(
                        "".join(variable.split()) + r"[/" + variable + r"/]" +
                        "-. use .->" +
                        "Use" + "".join(key.split()) + r"(" + key + r")"
                    )

            for key, value in screen_clear_collects.items():
                if variable in value:
                    variable_mermaid.append(
                        "".join(variable.split()) + r"[/" + variable + r"/]" +
                        "-. clear .->" +
                        "Clear" + "".join(key.split()) + r"(" + key + r")"
                    )

            variable_mermaid.append(self.config["MermaidSuffix"])

            variable_mermaid = list(dict.fromkeys(variable_mermaid))

            if len(variable_mermaid) > 2:
                self.md_file.new_header(level=2, title=variable)

                for line in variable_mermaid:
                    self.md_file.new_line(line)

    def _create_global_flows(self):
        screen_use_flows = {}

        for file in self._get_screen_files():
            # check on which screen variables are set
            screen_objects = self.parser.get_screen_objects(file)
            global_flows_on_screen = re.findall(
                r"(\w*).Run", str(screen_objects[1]))
            global_flows_on_screen = list(set(global_flows_on_screen))
            screen_use_flows[screen_objects[0]
                                 ] = global_flows_on_screen

            global_flows_on_screen = self._cleanUpList(global_flows_on_screen)

        # get all set global flows
        all_global_flows = [
            item for sublist in screen_use_flows.values() for item in sublist]
        all_global_flows = list(set(all_global_flows))

        for file in self._get_screen_files():
            screen_objects = self.parser.get_screen_objects(file)
            used_flows_on_screen = []

            # check if variables are used on this screen
            for variable in all_global_flows:
                used_flows_on_screen = used_flows_on_screen + \
                    re.findall(f"{variable}(?<!.Run)", str(screen_objects[1]))

            # remove duplicates
            used_flows_on_screen = list(set(used_flows_on_screen))
            screen_use_flows[screen_objects[0]] = used_flows_on_screen

        # create mermaid for each variable (where set and where used)
        self.md_file.new_header(level=1, title="Global Flows")
        self.md_file.new_line(
            "Usage of global flows is shown based on the screen(s) where this flows is executed. ")

        for variable in all_global_flows:
            variable_mermaid = [self.config["MermaidPrefix"]]
            variable_mermaid.append("graph LR")

            for key, value in screen_use_flows.items():
                if variable in value:
                    variable_mermaid.append(
                        "".join(variable.split()) + r"[/" + variable + r"/]" +
                        "-. executed_from .->" +
                        "executed_from" + "".join(key.split()) + r"(" + key + r")"
                    )

            variable_mermaid.append(self.config["MermaidSuffix"])

            variable_mermaid = list(dict.fromkeys(variable_mermaid))

            if len(variable_mermaid) > 2:
                self.md_file.new_header(level=2, title=variable)

                for line in variable_mermaid:
                    self.md_file.new_line(line)



    def create_documentation(self, format=None):

        if format == None:
            self.format = "markdown"
        else:
            self.format = format

        # instantiate the md file
        # TODO: get title from docstring variable
        app_name = self.manifest_file["PublishInfo"]["AppName"]
        output_file = os.path.join(self.output_path, f'{app_name}-doc')
        self.md_file = MdUtils(file_name=output_file,
                               title='Power App Documentation')

        for chapter in self.config["DocumentStructure"]:
            if chapter == "App":
                self._create_chapter_app(app_name=app_name)
            elif chapter == "Connections":
                self._create_chapter_connections()
            elif chapter == "Screens":
                self._create_chapter_screens()
            elif chapter == "GlobalVariables":
                self._create_global_variables()
            elif chapter == "GlobalCollects":
                self._create_global_collects()
            elif chapter == "GlobalFlows":
                self._create_global_flows()
    


        # write toc + file
        self.md_file.new_table_of_contents(table_title='Contents', depth=2)
        self.md_file.create_md_file()

        documentation_output_path = output_file + ".md"

        print(f"Start HTML Documentation creating")
        html_path = documentation_output_path + '.html'
        html = gh_md_to_html.main(documentation_output_path)
        
        with open(html_path, "w") as html_file:
            html_file.write(html)
        html_file.close

        with open(html_path) as fin:
            html_lines = fin.readlines()
        fin.close

        new_lines = [r'<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>']
        new_lines.append(r'<script>mermaid.initialize({ startOnLoad: true });</script>')

        for line in html_lines:
            if ':::' or '<br/>' in line:
                line = (line.replace('<br/>', ''))
                line = (line.replace(':::mermaid', '<div class="mermaid">'))
                line = (line.replace(':::', '</div>'))
                new_lines.append(line)
            else:
                new_lines.append(line)

        fout = open(html_path, "w")
        fout.writelines(new_lines)
        fout.close()

        #copyfile(html_path,self.output_path + '/export.html')
        

        print(f"HTML Documentation created successfully: {html_path}")

        return documentation_output_path

# https://mdutils.readthedocs.io/en/latest/examples/Example_Python.html # read screen order from manifest and check if files exists Extracts the parts from a propperty.          Parts can be:         - docstring         - function          Args:             propperty (str): The string representation of the propperty          Returns:             tuple: bool for contains docstring, docstring, function # add variables to screen if enabled in config # find variable with regex pattern #" + str.lower(x) + ")", global_variables)) # add collects to screen if enabled in config # find variable with regex pattern #" + str.lower(x) + ")", global_collects)) # add flows to screen if enabled in config # find variable with regex pattern #" + str.lower(x) + ")", flows)) # check for object # key is header # we found a header # check if value has content (deals with blank screens) # check if docstring exists then add screen docstring # we found another dict so start over # from https://stackoverflow.com/a/20254842 Takes a dict with nested lists and dicts,             and searches all dicts for a key of the field             provided. # create header for mermaid graph # check if screen has been excluded # # item = item.strip().replace("\n", "").replace("\t", "") # navigate_occurences = [m.start() for m in re.finditer( #     'Navigate\(', item)] # for occurence in navigate_occurences: #     # print(occurence) #     start = occurence + len("Navigate(") #     # print(start) #     end = item[start:].find(",") #     if end == -1: #         end = item[start:].find(")") #     end = end + start #     to_screen = item[start:end] #     to_screen = to_screen.replace("\n", "").replace( #         "\t", "").replace(")", "").replace("'", "") # from_screen_lower_case = str.lower(from_screen) # screenflow_list.append(f'click {from_screen} "#{from_screen_lower_case}" "link"') # to avoid double entrys in the graph # the doubled items are removed by convertig to dict and back to list # "\n".join(screenflow_list) # # APP # # # get contents from App.fx.yaml # read StartScreen and OnStart propperties from App # create heading for app info # write app info # # Connections # # # writing connections header # not all connections have an apiTier # Header # # Screen # # # TODO: add screenflow visualization # self.md_file.new_paragraph(self._create_mermaid_screenflow()) # loop thru all screens and create markdown # check on which screen variables are set # get all set global variables # check if variables are used on this screen # remove duplicates # create mermaid for each variable (where set and where used) # check on which screen variables are set # get all collects # check if collects are used on this screen # remove duplicates # create mermaid for each variable (where set, where used and cleared) # check on which screen variables are set # get all set global flows # check if variables are used on this screen # remove duplicates # create mermaid for each variable (where set and where used) # instantiate the md file # TODO: get title from docstring variable # write toc + file #copyfile(html_path,self.output_path + '/export.html')