from django.urls import reverse


class Node:
    def __init__(self, *, name=None, description=None, icon=None, link=None):
        self.parent = None
        self.name = name
        self.description = description
        self.icon = icon
        self.link = link
        self._children = []
        self._children_dict = {}
        self._resolved_namespace = None

    @property
    def namespace(self):
        if self._resolved_namespace:
            return self._resolved_namespace

        bits = self.link.split(":")
        self._resolved_namespace = ":".join(bits[:-1])
        return self._resolved_namespace

    def children(self):
        return self._children

    def children_as_dicts(self):
        childrens = []
        for children in self._children:
            childrens.append(
                {
                    "name": children.name,
                    "description": children.description,
                    "icon": children.icon,
                    "link": reverse(children.link),
                    "namespace": children.namespace,
                }
            )
        return childrens

    def add_node(self, node, after=None, before=None):
        if after:
            return self.add_node_after(node, after)
        if before:
            return self.add_node_before(node, before)
        node.parent = self
        self._children.append(node)
        self._children_dict[node.link] = node
        return True

    def add_node_after(self, node, after):
        success = False
        new_children_list = []

        for children in self._children:
            new_children_list.append(children)
            if children.link == after:
                new_children_list.append(node)
                success = True

        if success:
            node.parent = self
            self._children_dict[node.link] = node
            self._children = new_children_list
        return success

    def add_node_before(self, node, before):
        success = False
        new_children_list = []

        for children in self._children:
            if children.link == before:
                new_children_list.append(node)
                success = True
            new_children_list.append(children)

        if success:
            node.parent = self
            self._children_dict[node.link] = node
            self._children = new_children_list
        return success

    def child(self, namespace):
        try:
            return self._children_dict[namespace]
        except KeyError:
            raise ValueError(
                "Node %s is not a child of node %s" % (namespace, self.name)
            )

    def is_root(self):
        return False


class AdminSite:
    def __init__(self):
        self.nodes_record = []
        self.nodes_dict = {}

    def build_nodes_dict(self):
        nodes_dict = {"misago:admin": Node(link="misago:admin:index")}

        iterations = 0
        while self.nodes_record:
            iterations += 1
            if iterations > 512:
                message = (
                    "Misago Admin hierarchy is invalid or too complex to resolve. "
                    "Nodes left: %s"
                )
                raise ValueError(message % self.nodes_record)

            for index, node in enumerate(self.nodes_record):
                if node["parent"] in nodes_dict:
                    node_obj = Node(
                        name=node["name"],
                        description=node["description"],
                        icon=node["icon"],
                        link=node["link"],
                    )

                    parent = nodes_dict[node["parent"]]
                    if node["after"]:
                        node_added = parent.add_node(node_obj, after=node["after"])
                    elif node["before"]:
                        node_added = parent.add_node(node_obj, before=node["before"])
                    else:
                        node_added = parent.add_node(node_obj)

                    if node_added:
                        namespace = node.get("namespace") or node_obj.namespace

                        if namespace not in nodes_dict:
                            nodes_dict[namespace] = node_obj

                        del self.nodes_record[index]
                        break

        return nodes_dict

    def add_node(
        self,
        *,
        name=None,
        description=None,
        icon=None,
        parent=None,
        after=None,
        before=None,
        namespace=None,
        link="index",
    ):
        if self.nodes_dict:
            raise RuntimeError(
                "Misago admin site has already been initialized. "
                "You can't add new nodes to it."
            )

        if after and before:
            raise ValueError("after and before arguments are exclusive")

        self.nodes_record.append(
            {
                "name": name,
                "description": description,
                "icon": icon,
                "parent": join_namespace(parent),
                "after": join_namespace(parent, after) if after else None,
                "before": join_namespace(parent, before) if before else None,
                "namespace": join_namespace(parent, namespace),
                "link": join_namespace(parent, namespace, link),
            }
        )

    def visible_branches(self, request):
        if not self.nodes_dict:
            self.nodes_dict = self.build_nodes_dict()

        branches = []

        try:
            namespace = request.resolver_match.namespace
        except AttributeError:
            namespace = "misago:admin"

        if namespace in self.nodes_dict:
            node = self.nodes_dict[namespace]
            while node:
                children = node.children_as_dicts()
                if children:
                    branches.append(children)
                node = node.parent

        try:
            namespaces = request.resolver_match.namespaces
        except AttributeError:
            namespaces = ["misago", "admin"]

        branches.reverse()
        for depth, branch in enumerate(branches):
            depth_namespace = namespaces[2 : 3 + depth]
            for node in branch:
                node_namespace = node["namespace"].split(":")[2 : 3 + depth]
                if request.resolver_match:
                    node["is_active"] = depth_namespace == node_namespace
                else:
                    node["is_active"] = False

        return branches


def join_namespace(*args):
    parts = list(filter(None, args))
    parts.insert(0, "misago:admin")
    return ":".join(parts)


site = AdminSite()