import re
from browser_env.processors import TreeNode
from functools import partial

RETAINED_PROPERTIES = ["required", "disabled", "checked", "valuemin", "valuemax", "valuetext", "selected", "page_dialog_message"]
UNWANTED_PROPERTIES = ["focused", "autocomplete", "hasPopup", "expanded", "multiselectable", "orientation", "controls"]
UNINTERACTIVE_ROLES = ["StaticText", "LabelText", "main", "heading", "LayoutTable", "tabpanel", "LayoutTableRow", "LayoutTableCell", "time", "list", "contentinfo", "table", "row", "rowheader", "columnheader", "gridcell", "caption", "DescriptionList", "DescriptionListTerm", "DescriptionListDetail", "RootWebArea", "rowgroup", "alert"]
ROLE_REPLACEMENT_DICT = {
    "StaticText": "text",
    "LabelText": "text",
    # "caption": "text",
    # "generic": "text"
}

def parse_text_to_tree(text):
    lines = text.split('\n')

    root = None
    parent_stack = {}

    for line in lines:
        if line.strip() == "":
            continue
        line_strip = line.strip()
        line_parts = line_strip.split(' ')
        id = line_parts[0][1:-1]
        type = line_parts[1]
        text = ' '.join(line_parts[2:])
        level = 0
        for char in line:
            if char == '\t':
                level += 1
            else:
                break

        node = TreeNode(id, type, text, level)

        if line.startswith('\t'):
            parent_stack[level].add_child(node)
        else:
            root = node

        parent_stack[level+1] = node

    return root

def remove_unwanted_characters(text):
    text = text.replace('\xa0', ' ')
    cleaned_text = re.sub(r'[^\w\s,.!?;:\-\'\"()&/\u2019@]+', '', text, flags=re.UNICODE)
    cleaned_text = re.sub(r'\s+', ' ', cleaned_text)
    return cleaned_text.strip()

def search_node_by_id(node, target_id):
    if node.node_id == target_id:
        return node
    for child in node.children:
        result = search_node_by_id(child, target_id)
        if result:
            return result
    return None

def action_replace_node_role(node:TreeNode, role_replacement_dict:dict):
    if node.role in role_replacement_dict.keys():
        node.role = role_replacement_dict[node.role]

def action_remove_unwanted_characters(node:TreeNode):
    node.name = remove_unwanted_characters(node.name)

def action_remove_unwanted_properties(node:TreeNode):
    if node.has_properties():
        node.properties = {p: node.properties[p] for p in node.properties.keys() if p not in UNWANTED_PROPERTIES}
        if node.parent and node.parent.role=="row" and not node.properties["required"]:
            del node.properties["required"]
        if len(node.properties) == 0:
            node.properties = None

def action_remove_redundant_statictext_node(node:TreeNode):
    if not node.visible:
        return
    if not (node.all_children_invisible() and node.role in ["StaticText", "LabelText", "caption"]):
        return
    # if (not node.name) or (node.parent and node.name in node.parent.name) or (node.parent and any(node.name in sibling.name for sibling in node.siblings())):
    #     node.visible = False
    if (not node.name) or (node.parent and node.name in node.parent.name):
        node.visible = False

def action_merge_statictext_to_parent(node:TreeNode):
    if not node.visible:
        return
    if not (node.all_children_invisible() and node.role in ["StaticText", "LabelText", "caption"]):
        return
    if node.parent and not node.parent.name and len(node.parent.children) == 1:
        node.parent.name = node.name
        node.visible = False

def action_merge_menuitem_and_option(node:TreeNode):
    if not node.visible:
        return
    if not ((node.visible_children() and all(c.role=="menuitem" for c in node.visible_children())) or (node.visible_children() and all(c.role=="option" for c in node.visible_children()))):
        return
    if node.visible_children()[0].role == "menuitem":
        if not node.name.strip():
            node.name = "; ".join([action_return_visible_node(c).strip()[len("menuitem "):] for c in node.visible_children()])
        else:
            node.name += ": " + "; ".join([action_return_visible_node(c).strip()[len("menuitem "):] for c in node.visible_children()])
    elif node.visible_children()[0].role == "option":
        if not node.name.strip():
            node.name = "; ".join([action_return_visible_node(c).strip()[len("option "):] for c in node.visible_children()])
        else:
            node.name += ": " + "; ".join([action_return_visible_node(c).strip()[len("option "):] for c in node.visible_children()])
    for c in node.visible_children():
        c.visible = False

def action_merge_description_list(node:TreeNode):
    if not node.visible:
        return
    def reformat_sublist(current_list_term_buffer):
        if len(current_list_term_buffer) > 1:
            list_term_node_appended_name = []
            for n in current_list_term_buffer[1:]:
                list_term_node_appended_name.append(n.name)
                n.visible = False
            current_list_term_buffer[0].name += ": " + "; ".join(list_term_node_appended_name)
            
    if not node.role == "DescriptionList":
        return
    for child in node.visible_children():
        if child.role == "DescriptionListDetail" and not child.name and len(child.visible_children()) == 1:
            child.name = action_return_visible_node(child.visible_children()[0]).strip()
            child.visible_children()[0].visible = False
    list_term_buffer = []
    for child in node.visible_children():
        if child.role == "DescriptionListTerm" and child.all_children_invisible():
            reformat_sublist(current_list_term_buffer=list_term_buffer)
            list_term_buffer = [child]
        elif child.role == "DescriptionListDetail" and child.all_children_invisible() and list_term_buffer:
            list_term_buffer.append(child)
        elif child.role == "DescriptionListDetail" and not child.all_children_invisible():
            list_term_buffer = []
        else:
            reformat_sublist(current_list_term_buffer=list_term_buffer)
            list_term_buffer = []
        reformat_sublist(current_list_term_buffer=list_term_buffer)

def action_remove_image(node:TreeNode):
    if not node.visible:
        return
    if node.all_children_invisible() and (node.role=="img" or node.name=="Image"):
        node.visible = False

def action_set_invisible(node:TreeNode):
    node.visible = False

def action_set_visible(node:TreeNode):
    node.visible = True

def action_set_visible_if_with_name(node:TreeNode):
    if node.name:
        node.visible = True

def action_reformat_table(node:TreeNode):
    if not node.visible:
        return
    def merge_gridcell(gridcell_node:TreeNode):
        if gridcell_node.role not in ["gridcell", "columnheader", "rowheader", "LayoutTableCell"] or not gridcell_node.visible:
            return
        gridcell_buffer = []
        parse_node_descendants(gridcell_node, action_return_visible_node, gridcell_buffer)
        if len(gridcell_buffer) == 1:
            return
        gridcell_buffer = [s.strip() for s in gridcell_buffer]
        if gridcell_node.name:
            gridcell_node.name += "\t" + "\t".join(gridcell_buffer[1:])
        else:
            gridcell_node.name = "\t".join(gridcell_buffer[1:])
        parse_node_descendants(gridcell_node, action_set_invisible)
        gridcell_node.visible = True

    try:
        if node.role == "table":

            def reformat_subtable(row_list, current_table_children):
                import copy
                new_table_children = copy.deepcopy(current_table_children)
                if row_list:
                    # if row_list[0].children[0].role == "columnheader":
                    if any(row_0_child.role == "columnheader" for row_0_child in row_list[0].children):
                        if new_table_children and any(n.visible for n in new_table_children):
                            new_table_children.append(TreeNode(node_id=row_list[0].node_id, role="row", name="", depth=row_list[0].depth))
                        for i, row in enumerate(row_list):
                            new_role_name = []
                            for row_element in row.children:
                                new_role_name.append(row_element.name)
                            new_table_children.append(TreeNode(node_id=row.node_id, role="row", name="| "+" | ".join(new_role_name)+" |", depth=row.depth))
                            if i == 0 and len(row_list) > 1:
                                new_table_children.append(TreeNode(node_id=row.node_id, role="row", name="| "+" | ".join(["---"]*len(new_role_name))+" |", depth=row.depth))
                    elif row_list[0].children[0].role == "rowheader":
                        if new_table_children and any(n.visible for n in new_table_children):
                            new_table_children.append(TreeNode(node_id=row_list[0].node_id, role="row", name="", depth=row_list[0].depth))
                        titles = [r.children[0].name for r in row_list]
                        values = [r.children[1].name for r in row_list]
                        new_table_children.append(TreeNode(node_id=row_list[0].node_id, role="row", name="| "+" | ".join(titles)+" |", depth=row_list[0].depth))
                        new_table_children.append(TreeNode(node_id=row_list[0].node_id, role="row", name="| "+" | ".join(["---"]*len(titles))+" |", depth=row_list[0].depth))
                        new_table_children.append(TreeNode(node_id=row_list[0].node_id, role="row", name="| "+" | ".join(values)+" |", depth=row_list[0].depth))
                    elif row_list[0].children[0].role == "gridcell":
                        if new_table_children and any(n.visible for n in new_table_children):
                            new_table_children.append(TreeNode(node_id=row_list[0].node_id, role="row", name="", depth=row_list[0].depth))
                        for row in row_list:
                            new_table_children.append(TreeNode(node_id=row.node_id, role="row", name="| "+" | ".join([row_element.name for row_element in row.children])+" |", depth=row.depth))
                    else:
                        raise NotImplementedError("Unrecognized table format.")
                return new_table_children
            
            new_table_children = []
            row_list = []
            row_mode = False
            for child in node.children:
                if child.role == "row":
                    for row_element in child.visible_children(): # TODO: Visible?
                        merge_gridcell(row_element)

                # if child.role == "row" and child.children[0].role == "columnheader":
                if child.role == "row" and any(row_child.role == "columnheader" for row_child in child.children):
                    row_list = [child]
                    row_mode = False
                elif child.role == "row" and child.children[0].role == "rowheader":
                    if row_mode:
                        row_list.append(child)
                    else:
                        new_table_children = reformat_subtable(row_list=row_list, current_table_children=new_table_children)
                        row_list = [child]
                    row_mode = True
                elif child.role == "row" and child.children[0].role == "gridcell":
                    row_list.append(child)
                    row_mode = False
                elif child.role != "row":
                    new_table_children = reformat_subtable(row_list=row_list, current_table_children=new_table_children)
                    if child.role == "rowgroup":
                        for grandchild in child.visible_children(): # grandchild: row
                            for row_element in grandchild.visible_children(): # TODO: Visible?
                                merge_gridcell(row_element)
                        child.children = reformat_subtable(row_list=child.children, current_table_children=[])
                    new_table_children.append(child)
                    row_list = []
                else:
                    raise NotImplementedError()
            new_table_children = reformat_subtable(row_list=row_list, current_table_children=new_table_children)
            node.children = new_table_children
        elif node.role == "LayoutTable":
            def merge_adjacent_text_nodes(nodes):
                if not nodes:
                    return []

                merged_nodes = []
                current_node = nodes[0]

                for i in range(1, len(nodes)):
                    if current_node.visible and current_node.role in ["LayoutTableCell", "StaticText", "generic"]+list(set(ROLE_REPLACEMENT_DICT.values())) and nodes[i].visible and nodes[i].role in ["LayoutTableCell", "StaticText", "generic"]+list(set(ROLE_REPLACEMENT_DICT.values())):
                        current_node.role = ROLE_REPLACEMENT_DICT["StaticText"]
                        current_node.name += " " + nodes[i].name  # Merge text values
                        nodes[i].visible = False
                    else:
                        merged_nodes.append(current_node)
                        current_node = nodes[i]

                merged_nodes.append(current_node)

                return merged_nodes
            def dfs_merge_text(n:TreeNode):
                if not n.children:
                    return
                for c in n.children:
                    dfs_merge_text(c)
                n.children = merge_adjacent_text_nodes(n.children)
                if len(n.visible_children()) == 1 and n.visible_children()[0].role in ["LayoutTableCell", "StaticText", "generic"]+list(set(ROLE_REPLACEMENT_DICT.values())) and n.role in ["LayoutTableCell", "StaticText", "generic"]+list(set(ROLE_REPLACEMENT_DICT.values())):
                    n.name += "\t" + n.visible_children()[0].name
                    n.visible_children()[0].visible = False
                if n.role == "LayoutTableRow":
                    for row_element in n.children:
                        if row_element.visible and row_element.children:
                            for sub_element in row_element.children:
                                if sub_element.visible:
                                    node_str = action_return_visible_node(sub_element).strip()
                                    row_element.name += f"\t{node_str}"
                            row_element.children = []
                    n.name = "| " + " | ".join([c.name for c in n.children if c.visible]) + " |" # TODO: Visible?
                    for row_element in n.children:
                        row_element.visible = False
            dfs_merge_text(node)
    except Exception as e:
        print("Table reformatting error:", e)

def action_merge_duplicated_headings(node:TreeNode):
    if not node.visible or not node.all_children_invisible() or not node.parent or node.visible_siblings():
        return
    if node.role=="heading" and node.parent.role not in UNINTERACTIVE_ROLES and node.name == node.parent.name:
        node.visible = False
    if node.parent.role=="heading" and node.role not in UNINTERACTIVE_ROLES and node.name == node.parent.name:
        node.parent.node_id = node.node_id
        node.parent.role = node.role
        node.parent.properties = node.properties
        node.parent.children = node.children
        node.visible = False

def action_print_tree(node:TreeNode):
    print("\t" * node.depth + f"{node.visible} {node.depth} [{node.node_id}] {node.role}: {node.name}")

def action_return_visible_node(node:TreeNode, intent_bias=0, mode="concise", **kwargs):
    if not node.visible:
        return None
    if mode == "concise":
        node_str = node.role
        hidden_roles = UNINTERACTIVE_ROLES+list(set(ROLE_REPLACEMENT_DICT.values()))
        if "[" in node.name and "hidden_roles" in kwargs.keys():
            hidden_roles += kwargs["hidden_roles"]
        if node.role not in hidden_roles:
            node_str += f" [{node.node_id}]"    
    elif mode == "verbose":
        node_str = f"{node.role} [{node.node_id}]"
    elif mode == "name_only":
        node_str = node.role
    elif mode == "name_retained_id_only":
        node_str = node.role
        retained_ids = kwargs.get("retained_ids", [])
        if node.node_id in retained_ids:
            node_str += f" [{node.node_id}]"
    
    if node.name:
        node_str += f" {repr(node.name)}"
    if node.has_properties():
        for p in node.properties:
            p_value = node.properties[p]
            node_str += f" [{p}: {p_value}]"
    return "\t" * (node.depth-intent_bias) + node_str

def parse_node_siblings(node:TreeNode, action=action_print_tree, tree_buffer=[]):
    for sibling in node.siblings():
        res_action = action(sibling)
        if res_action:
            tree_buffer.append(res_action)

def parse_node_ancestors(node:TreeNode, action=action_print_tree, tree_buffer=[]):
    res_action = action(node)
    if res_action:
        tree_buffer.append(res_action)
    if node.parent:
        parse_node_ancestors(node=node.parent, action=action, tree_buffer=tree_buffer)

def parse_node_descendants(node:TreeNode, action=action_print_tree, tree_buffer=[]):
    res_action = action(node)
    if res_action:
        tree_buffer.append(res_action)
    for child in node.children:
        parse_node_descendants(node=child, action=action, tree_buffer=tree_buffer)

def prune_tree_fuzzy_node(node:TreeNode): # TODO: Bugs!!!
    if not node.children:
        return
    
    # Iterate over the children in reverse order to safely remove nodes
    fuzzy_children = []
    for child in reversed(node.children):
        prune_tree_fuzzy_node(child)
        if child.all_children_invisible() and not child.is_differentiable(strict=True):
            fuzzy_children.append(child)
    for child in fuzzy_children:
        child.visible = False

def translate_node_to_str(node: TreeNode, mode="concise", **kwargs):
    tree_buffer = []
    parse_node_descendants(node, partial(action_return_visible_node, intent_bias=node.depth, mode=mode, **kwargs), tree_buffer=tree_buffer)
    return "\n".join(tree_buffer[:1000])

def construct_new_DOM_with_visible_nodes(DOM_root:TreeNode):
    def dfs(node:TreeNode):
        if not node.visible:
            return None
        if not node.visible_children():
            return node.copy()
        new_self = node.copy()
        for child in node.visible_children():
            new_child = dfs(child)
            if new_child:
                new_self.add_child(new_child)
        return new_self
    new_DOM_Root = dfs(DOM_root)
    return new_DOM_Root

def prune_tree(objective, root_node, mode="str"):
    root_node_copy = construct_new_DOM_with_visible_nodes(root_node)
    parse_node_descendants(root_node_copy, action_remove_unwanted_characters)
    parse_node_descendants(root_node_copy, action_remove_unwanted_properties)
    parse_node_descendants(root_node_copy, action_remove_redundant_statictext_node)
    parse_node_descendants(root_node_copy, action_remove_image)
    prune_tree_fuzzy_node(root_node_copy)
    parse_node_descendants(root_node_copy, action_remove_image)
    parse_node_descendants(root_node_copy, action_merge_statictext_to_parent)
    parse_node_descendants(root_node_copy, action_remove_redundant_statictext_node)
    parse_node_descendants(root_node_copy, partial(action_replace_node_role, role_replacement_dict=ROLE_REPLACEMENT_DICT))
    parse_node_descendants(root_node_copy, action_merge_menuitem_and_option)
    parse_node_descendants(root_node_copy, action_merge_description_list)
    parse_node_descendants(root_node_copy, action_reformat_table)
    parse_node_descendants(root_node_copy, action_merge_duplicated_headings)

    if mode == "str":
        browser_content = translate_node_to_str(node=root_node_copy, mode="concise")
    elif mode == "node":
        browser_content = construct_new_DOM_with_visible_nodes(root_node_copy)
    return browser_content

def contains_keyword(title, keyword):
    return keyword in title.lower()
