from __future__ import annotations
import bpy
import bmesh
from bpy_extras import view3d_utils
import time
import mathutils


from . import const, g
from .utils import functions
from .draw import draw
from . import boundary
from . import generate_loop
from .utils.trans import t


class EasyPatchModalOperator(bpy.types.Operator):
    bl_idname = f"{const.addon_prefix}.start"
    bl_label = "Start Patching"
    bl_options = {"REGISTER", "UNDO"}  # 不加UNDO撤回的话，会导致用户前一个步骤也撤回

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def modal(self, context, event):
        try:
            if context.area:
                context.area.tag_redraw()

            result = self.process_undo(context, event)
            if result is not None:
                return result

            # start_timestamp = time.time()
            # process drag
            result = self.process_drawing_verts(context, event)
            if result is not None:
                return result
            # drawing_verts_consume_time = time.time() - start_timestamp

            # process stop
            result = self.process_stop(context, event)
            if result is not None:
                return result

            # start_timestamp = time.time()
            result = self.process_running(context, event)
            if result is not None:
                return result
            # running_consume_time = time.time() - start_timestamp

            # start_timestamp = time.time()
            result = self.process_loop(context, event)
            if result is not None:
                return result
            # process_loop_consume_time = time.time() - start_timestamp

            # start_timestamp = time.time()
            update_hover(context, event)
            # update_hover_consume_time = time.time() - start_timestamp

            # last_echo_time = g.last_echo_time if g.last_echo_time else 0
            # if time.time() - last_echo_time > 0.1:
            #     print(
            #         drawing_verts_consume_time,
            #         running_consume_time,
            #         process_loop_consume_time,
            #         update_hover_consume_time,
            #     )
            #     g.last_echo_time = time.time()

            #
            g.last_mouse_co_2d = mathutils.Vector(
                (event.mouse_region_x, event.mouse_region_y)
            )
            return self.process_ui_block(context, event)

        except Exception as e:
            functions.safe_execute(self.clear, context)
            functions.log(f"An expection raise when modal, exit modal. Error: {str(e)}")
            self.report(
                {"WARNING"},
                f"An expection raise when modal, exit modal. Error: {str(e)}",
            )
            EasyPatchModalOperator.set_stop_flag()
            raise e

        return {"PASS_THROUGH"}
        # return {"RUNNING_MODAL"}

    def invoke(self, context, event):
        if not g.is_running:
            self.start(context)
            return {"RUNNING_MODAL"}
        else:
            EasyPatchModalOperator.stop()
        return {"FINISHED"}

    def start(self, context):
        functions.log("modal start")

        g.clear_all_variables()

        draw.start(self)
        g.is_running = True
        self._timer = context.window_manager.event_timer_add(0.1, window=context.window)
        context.window_manager.modal_handler_add(self)

        self.context = context
        self.region = context.region
        self.rv3d = context.region_data

        obj = functions.get_edit_obj()
        g.obj = obj
        g.obj_me = obj.data
        g.obj_bm = bmesh.from_edit_mesh(obj.data)

        g.mode = g.Mode.NORMAL
        g.drawing_verts_snap_vertex = None
        g.drawing_path_boundary = None

        g.snap_bvh_trees = functions.get_snap_bvh_trees()
        g.obj_bvh_tree = functions.get_obj_bvh_tree()

    def stop():
        functions.log("modal stop")
        EasyPatchModalOperator.set_stop_flag()

    def clear(self, context, remove_mesh_flag=True):
        functions.log("modal clear")

        # 用户tab离开编辑模式，会导致bm被删除，所以会报很多错

        if remove_mesh_flag:
            functions.safe_execute(self.remove_mesh)

        functions.safe_execute(context.window_manager.event_timer_remove, self._timer)
        functions.safe_execute(EasyPatchModalOperator.set_stop_flag)
        functions.safe_execute(draw.clear)

        if context.object.mode == "EDIT":
            bmesh.update_edit_mesh(g.obj_me)

        g.clear_all_variables()

    def set_stop_flag():
        g.is_running = False

    # 删除插件生成的网格
    def remove_mesh(self):
        if g.temp_bm_edges:
            functions.remove_bm_edges(g.obj_bm, g.temp_bm_edges)
            g.temp_bm_edges = set()

        if g.temp_bm_verts:
            functions.delete_bm_verts(g.obj_bm, g.temp_bm_verts)
            g.temp_bm_verts = set()

        for loop in g.loops:
            if loop.pattern:
                loop.pattern.remove_mesh()

        try:
            bmesh.update_edit_mesh(g.obj_me)
        except Exception as e:
            pass

    def process_stop(self, context, event):
        if (
            not g.is_running
            or event.type == "ESC"
            or context.object.mode != "EDIT"
            or g.obj_bm is None
        ):
            self.clear(context)
            return {"FINISHED"}

        if event.type in ["RET", "NUMPAD_ENTER"]:
            count_vertex = functions.get_created_verts_num()

            self.clear(context, remove_mesh_flag=False)
            self.report({"INFO"}, f"{count_vertex} {t('verts')} {t('created')}")
            return {"FINISHED"}

        if g.running_exception:
            raise g.running_exception

    def process_drawing_verts(self, context, event):
        # TODO: update snap和hovering和append_from_2d加一个移动阈值才更新,记录上一个鼠标位置，超过阈值才进行，modal末尾更新，共用一个值
        # TODO: loop加上一个屏蔽pattern生成的状态
        functions.update_snap_vertex(event, is_snap_opposite=g.is_snap_opposite)

        if g.mode == g.Mode.DRAWING:
            # # update and clear snap vertex when ctrl in running
            functions.update_solid_path(context, event)

            # append路径
            g.drawing_path_boundary.append_path_from_2d(
                context,
                (event.mouse_region_x, event.mouse_region_y),
                is_snap_surface=g.is_snap_surface,
                is_snap_opposite=g.is_snap_opposite,
            )

            # drawing中改变点数
            if event.type == "WHEELUPMOUSE":
                g.drawing_path_boundary.length += 1
                return {"RUNNING_MODAL"}
            if event.type == "WHEELDOWNMOUSE":
                g.drawing_path_boundary.length -= 1
                return {"RUNNING_MODAL"}

            # stop drag
            if event.type == "LEFTMOUSE" and event.value == "RELEASE":
                # # 第六条强制闭合
                # if len(self.patch.boundaries) == 5:
                #     g.drawing_verts_snap_vertex = self.patch.start_vertex

                snap_vert = (
                    g.drawing_verts_snap_vertex
                )  # 确保一致性，先获取，防止中间修改
                functions.log("stop drag vertex:" + str(snap_vert))

                done_boundary = g.drawing_path_boundary.done_from_temp(snap_vert)

                g.drawing_verts_snap_vertex = None
                g.mode = g.Mode.NORMAL

                # clear drag things
                g.drawing_path_boundary = None

                if not done_boundary:
                    return

                if done_boundary.path[-1] != done_boundary.end_vertex.co:
                    # 不知道为什么，end_vertex是吸附的时候需要手动补
                    done_boundary.path.append(
                        g.obj.matrix_world @ done_boundary.end_vertex.co
                    )

                generate_loop.regenerate_loops()

                if not g.is_drawing_along_mirror_persist:
                    g.is_drawing_along_mirror = False  # 每次画完都重置这个变量

            # 删除temp boundary
            if event.type == "RIGHTMOUSE" and event.value == "PRESS":
                g.drawing_path_boundary = None
                g.mode = g.Mode.NORMAL

                return {"RUNNING_MODAL"}

        # if event.value != "NOTHING":
        #     print(event.type, event.value)

        # TODO: 以CLICK_DRAG，用户可能会以左键单击误触发，造成多余的线条，多一个ctrl+enter，以保留多余线条
        # start drag
        if event.type == "LEFTMOUSE" and event.value == "CLICK_DRAG":
            functions.log("start drag vertex:" + str(g.drawing_verts_snap_vertex))
            g.mode = g.Mode.DRAWING

            # create temp boundary
            if g.drawing_verts_snap_vertex:
                g.drawing_path_boundary = boundary.Boundary.create_temp(
                    g.drawing_verts_snap_vertex
                )
            else:
                g.drawing_path_boundary = boundary.Boundary.create_temp(None)

            return {
                "RUNNING_MODAL"
            }  # block drag ui operation, if not running_modal, release will not trigger

    def process_undo(self, context, event):
        if event.type == "Z" and event.value == "PRESS" and event.ctrl:

            self.report({"WARNING"}, t("you_cant_undo_here"))
            return {"RUNNING_MODAL"}

    def process_ui_block(self, context, event):
        # I键内插，会导致未知问题
        if event.type in ["X", "I"]:
            return {"RUNNING_MODAL"}

        return {"PASS_THROUGH"}

    def process_loop(self, context, event):
        if not g.hovering_loop:
            return

        loop = g.hovering_loop

        # R
        if event.type == "R" and event.value == "PRESS":
            loop.change_solver_constraint_rotation()
            return {"RUNNING_MODAL"}

        # T
        if event.type == "T" and event.value == "PRESS":
            loop.next_solver_constraint_pattern(is_auto_search=g.is_auto_search_pattern)
            return {"RUNNING_MODAL"}

        # invert normal
        if event.type == "I" and event.value == "PRESS" and loop.pattern:
            functions.faces_normal_toward_viewport(g.obj, g.obj_bm, loop.pattern.faces)
            return {"RUNNING_MODAL"}

        # 改变padding约束
        if (
            event.shift
            and (event.type == "WHEELUPMOUSE" or event.type == "WHEELDOWNMOUSE")
            and g.hovering_boundary
        ):
            boundary_index = loop.get_boundary_index(g.hovering_boundary)
            if event.type == "WHEELUPMOUSE":
                loop.change_solver_constraint_list(boundary_index, 1)
            if event.type == "WHEELDOWNMOUSE":
                loop.change_solver_constraint_list(boundary_index, -1)

        # X附加边流
        if event.type == "X" and event.value == "PRESS" and event.shift:
            loop.change_solver_constraint_list("x", 1)
            return {"RUNNING_MODAL"}
        if event.type == "X" and event.value == "PRESS" and event.ctrl:
            loop.change_solver_constraint_list("x", -1)
            return {"RUNNING_MODAL"}

        # Y附加边流
        if event.type == "Y" and event.value == "PRESS" and event.shift:
            loop.change_solver_constraint_list("y", 1)
            return {"RUNNING_MODAL"}
        if event.type == "Y" and event.value == "PRESS" and event.ctrl:
            loop.change_solver_constraint_list("y", -1)
            return {"RUNNING_MODAL"}

        # C 清除约束
        if event.type == "C" and event.value == "PRESS":
            if event.shift:
                loop.init_solver_constraint(
                    is_init_list=True, is_init_rotation=False, is_init_pattern=False
                )
            elif event.ctrl:
                loop.init_solver_constraint(
                    is_init_list=False, is_init_rotation=True, is_init_pattern=False
                )
            else:
                loop.init_solver_constraint()
            return {"RUNNING_MODAL"}

    def process_running(self, context, event):
        if g.mode != g.Mode.NORMAL:
            return
        # 边缘建成后，改变长度
        if event.ctrl and (
            event.type == "WHEELUPMOUSE" or event.type == "WHEELDOWNMOUSE"
        ):
            # boundary = functions.get_nearest_boundary_from_mouse(event)
            boundary = g.hovering_boundary
            if boundary:
                if event.type == "WHEELUPMOUSE":
                    boundary.length += 1
                if event.type == "WHEELDOWNMOUSE":
                    boundary.length -= 1
                return {"RUNNING_MODAL"}

        # S
        if event.type == "S" and event.value == "PRESS":
            g.is_snap_surface = not g.is_snap_surface  # 切换吸附状态

            if g.hovering_loop and g.hovering_loop.pattern:
                if g.is_snap_surface:
                    # 吸附曲面
                    functions.snap_verts_to_surface_along_normal(
                        g.obj,
                        g.obj_bm,
                        g.hovering_loop.pattern.interior_verts,
                        g.snap_bvh_trees,
                        allow_opposite_direction=g.is_snap_opposite,
                    )
                else:
                    functions.smooth_verts_by_normal(
                        g.obj, g.obj_bm, g.hovering_loop.pattern.interior_verts
                    )

            return {"RUNNING_MODAL"}

        if event.type == "M" and event.value == "PRESS" and not event.ctrl:
            g.is_drawing_along_mirror = (
                not g.is_drawing_along_mirror
            )  # 切换镜像绘制状态

            if not g.is_drawing_along_mirror:
                g.is_drawing_along_mirror_persist = False
            return {"RUNNING_MODAL"}

        # 删除boundary
        if (
            event.type == "X"
            and event.value == "PRESS"
            and not event.shift
            and not event.ctrl
            and g.hovering_boundary
        ):
            g.hovering_boundary.delete()
            generate_loop.regenerate_loops()
            return {"RUNNING_MODAL"}

        if event.type == "M" and event.value == "PRESS" and event.ctrl:
            g.is_drawing_along_mirror_persist = (
                not g.is_drawing_along_mirror_persist
            )  # 切换镜像绘制状态

            g.is_drawing_along_mirror = g.is_drawing_along_mirror_persist
            return {"RUNNING_MODAL"}

        # smooth
        if event.shift:
            tobe_smooth_loops = []
            if event.ctrl:
                tobe_smooth_loops = g.loops
            else:
                if g.hovering_loop:
                    tobe_smooth_loops = [g.hovering_loop]

            for tobe_smooth_loop in tobe_smooth_loops:
                if (
                    not tobe_smooth_loop.pattern
                    or not tobe_smooth_loop.pattern.interior_verts
                ):
                    continue
                to_be_smooth_verts = tobe_smooth_loop.pattern.interior_verts

                if event.type in ["ONE", "NUMPAD_1"]:
                    functions.smooth_verts_by_avg(g.obj, g.obj_bm, to_be_smooth_verts)
                    return {"RUNNING_MODAL"}
                elif event.type in ["TWO", "NUMPAD_2"]:
                    functions.smooth_verts_by_normal(
                        g.obj,
                        g.obj_bm,
                        to_be_smooth_verts,
                        smooth_type=0,
                    )
                    return {"RUNNING_MODAL"}
                elif event.type in ["THREE", "NUMPAD_3"]:
                    functions.smooth_verts_by_normal(
                        g.obj,
                        g.obj_bm,
                        to_be_smooth_verts,
                        smooth_type=1,
                    )
                    return {"RUNNING_MODAL"}
                # self.is_snap_surface = False

        # v snap oppose，目前只针对path绘制的时候应用
        if event.type == "V" and event.value == "PRESS" and g.is_running:
            if g.is_snap_opposite:
                g.is_snap_opposite = False
            else:
                g.is_snap_opposite = True
            return {"RUNNING_MODAL"}

        if event.type == "B" and event.value == "PRESS":
            g.is_auto_search_pattern = not g.is_auto_search_pattern
            return {"RUNNING_MODAL"}


class EasyPatchSnapSelectedToSurfaceOperator(bpy.types.Operator):
    bl_idname = f"{const.addon_prefix}.snap_selected_to_surface"
    bl_label = "Snap Selected Vertices to Surface"
    # bl_description = t("toggle_driver")
    bl_options = {"REGISTER", "UNDO"}  # 不加UNDO撤回的话，会导致用户前一个步骤也撤回

    def execute(self, context):
        obj = context.object

        # 检查是否在编辑模式
        if obj.mode != "EDIT":
            self.report({"WARNING"}, "Please run this in edit mode")
            return {"FINISHED"}

        # 获取依赖图
        depsgraph = context.evaluated_depsgraph_get()

        # 获取网格数据
        bm = bmesh.from_edit_mesh(obj.data)
        bm.verts.ensure_lookup_table()

        # 获取所有选中的顶点
        selected_vertices = [v for v in bm.verts if v.select]
        if not selected_vertices:
            self.report({"WARNING"}, "No Vertices selected")
            return {"FINISHED"}

        # 获取所有可见的网格对象（排除当前对象）
        visible_objects = [
            o
            for o in context.visible_objects
            if o.type == "MESH" and o != obj and o.visible_get()
        ]

        if not visible_objects:
            self.report({"WARNING"}, "no_visible_target_mesh_to_snap")
            return {"FINISHED"}

        # 构建所有可见目标对象的 BVH 树（一次性操作）
        bvh_trees = functions.get_snap_bvh_trees()

        functions.snap_verts_to_surface_along_normal(
            obj,
            bm,
            selected_vertices,
            bvh_trees,
            allow_opposite_direction=False,
        )

        # 将顶点更改应用到网格上，loop_triangles提高效率
        bmesh.update_edit_mesh(obj.data, loop_triangles=True)

        # self.report({"INFO"}, f"Successfully snap {snap_count} vertices")
        return {"FINISHED"}


def get_nearest_boundary_and_loop_by_co_2d(context, co_2d):
    nearest_boundary = None
    nearest_loop = None
    min_distance = float("inf")

    # 获取当前视图区域和区域的3D视图
    region = context.region
    rv3d = context.space_data.region_3d

    # # mid_co方案
    # # 遍历boundary列表
    # for boundary in g.boundaries:
    #     # 获取boundary的mid_co三维坐标
    #     mid_co = boundary.mid_co

    #     # 将三维坐标投影到2D屏幕坐标
    #     screen_coord = view3d_utils.location_3d_to_region_2d(region, rv3d, mid_co)

    #     # 如果投影成功
    #     if screen_coord:
    #         # 计算鼠标位置与投影点的距离
    #         dist = (screen_coord[0] - co_2d[0]) ** 2 + (screen_coord[1] - co_2d[1]) ** 2
    #         if dist < min_distance:
    #             min_distance = dist
    #             nearest_boundary = boundary

    # TODO: 只有一个edge的用mid_co，不然重叠了捕获不到
    # 遍历boundary列表
    for boundary in g.boundaries:
        coords = [v.co for v in boundary.verts] + [boundary.mid_co]
        # 遍历boundary的每一个顶点
        for vert_co in coords:
            # 将三维坐标投影到2D屏幕坐标
            screen_coord = view3d_utils.location_3d_to_region_2d(region, rv3d, vert_co)

            # 如果投影成功
            if screen_coord:
                # 计算鼠标位置与投影点的距离
                dist = (screen_coord[0] - co_2d[0]) ** 2 + (
                    screen_coord[1] - co_2d[1]
                ) ** 2
                if dist < min_distance:
                    min_distance = dist
                    nearest_boundary = boundary

    if nearest_boundary:
        # 获取与nearest_boundary相关的loop
        connected_loops = functions.get_loops_from_boundary(nearest_boundary)
        # print(nearest_boundary, connected_loops)

        if len(connected_loops) == 0:
            nearest_loop = None
        elif len(connected_loops) == 1:
            nearest_loop = connected_loops[0]
        else:
            # 找到距离鼠标位置最近的loop
            min_loop_distance = float("inf")

            for loop in connected_loops:
                # 获取loop的mid_co三维坐标
                loop_mid_co = loop.mid_co

                # 将loop的三维坐标投影到2D屏幕坐标
                loop_screen_coord = view3d_utils.location_3d_to_region_2d(
                    region, rv3d, loop_mid_co
                )

                # 如果loop投影成功
                if loop_screen_coord:
                    # 计算鼠标位置与loop的投影点的距离
                    loop_dist = (loop_screen_coord[0] - co_2d[0]) ** 2 + (
                        loop_screen_coord[1] - co_2d[1]
                    ) ** 2
                    if loop_dist < min_loop_distance:
                        min_loop_distance = loop_dist
                        nearest_loop = loop

    return nearest_boundary, nearest_loop


def get_nearest_boundary_and_loop_by_co_3d(co_3d):
    nearest_boundary = None
    nearest_loop = None
    min_distance = float("inf")

    # # mid_co方案
    # # 遍历boundary列表
    # for boundary in g.boundaries:
    #     mid_co = boundary.mid_co

    #     # 计算给定的3D坐标与boundary的mid_co之间的欧几里得距离
    #     distance = (mid_co - co_3d).length

    #     # 判断是否是最近的boundary
    #     if distance < min_distance:
    #         min_distance = distance
    #         nearest_boundary = boundary

    # verts方案
    for boundary in g.boundaries:
        coords = [v.co for v in boundary.verts] + [boundary.mid_co]
        # 遍历boundary的每一个顶点
        for vert_co in coords:

            # 计算给定的3D坐标与顶点坐标之间的欧几里得距离
            distance = (vert_co - co_3d).length

            # 判断是否是最近的boundary
            if distance < min_distance:
                min_distance = distance
                nearest_boundary = boundary

    if nearest_boundary:
        connected_loops = functions.get_loops_from_boundary(nearest_boundary)
        if len(connected_loops) == 0:
            nearest_loop = None
        elif len(connected_loops) == 1:
            nearest_loop = connected_loops[0]
        else:
            # 多个loop的情况，计算每个loop的mid_co和目标co_3d之间的3D距离
            min_loop_distance = float("inf")

            for loop in connected_loops:
                loop_mid_co = loop.mid_co

                # 计算给定的3D坐标与loop的mid_co之间的欧几里得距离
                loop_distance = (loop_mid_co - co_3d).length

                # 判断是否是最近的loop
                if loop_distance < min_loop_distance:
                    min_loop_distance = loop_distance
                    nearest_loop = loop

    return nearest_boundary, nearest_loop


def update_hover(context, event):
    if g.is_changing_mesh:
        g.hovering_boundary = None
        g.hovering_loop = None

        g.is_redraw_hovering = True
        return

    current_timestamp = time.time()
    if current_timestamp - g.last_update_hovering_timestamp < (1 / 10):
        return

    # 两个方案，1.boundary的mid_co投影到2d,获取最近的，然后loop挑出b相邻的，再根据mid_co取2d最近的 2.ray_cast，没结果则用方案一，有结果，取距离该点最近的boundary的mid_co，然后再相邻取loop

    region = context.region
    rv3d = context.region_data
    mouse_co_2d = (event.mouse_region_x, event.mouse_region_y)
    ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, mouse_co_2d)
    ray_direction = view3d_utils.region_2d_to_vector_3d(region, rv3d, mouse_co_2d)

    closest_co = None
    distance_min = float("inf")
    for bvh_tree in g.snap_bvh_trees:
        ray_cast_co, _, _, distance = bvh_tree.ray_cast(ray_origin, ray_direction)
        if distance and distance < distance_min:
            distance_min = distance
            closest_co = ray_cast_co

    hovering_boundary = None
    hovering_loop = None
    if closest_co:
        hovering_boundary, hovering_loop = get_nearest_boundary_and_loop_by_co_3d(
            closest_co
        )

    else:
        # boundary
        hovering_boundary, hovering_loop = get_nearest_boundary_and_loop_by_co_2d(
            context, mouse_co_2d
        )

    g.hovering_boundary = hovering_boundary
    g.hovering_loop = hovering_loop

    g.last_update_hovering_timestamp = current_timestamp

    g.is_redraw_hovering = True


classes = (EasyPatchModalOperator, EasyPatchSnapSelectedToSurfaceOperator)
