# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
from collections.abc import Set as AbstractSet
from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional

import bpy
from bpy.types import Context, Operator

from .utils import (
    AUTO_CONVERT_MATERIALS,
    PROCESSING_ORDER,
    MaterialInfo,
    find_body_mesh,
    find_face_mesh,
    generate_unique_name,
    get_material_face_count,
    get_target_mesh_name,
    is_retained_material,
    is_separable_material,
    is_skin_material,
    isolate_material_faces,
    load_edge_uv_data,
    select_edges_by_uv,
)

if TYPE_CHECKING:
    from bpy.types import Object


@dataclass
class ProcessResult:
    """材质处理结果"""
    material_name: str
    mesh_name: str
    edges_selected: int
    edges_dissolved: int
    success: bool
    error_message: Optional[str] = None


@dataclass
class MergeOperationState:
    """操作状态，用于错误恢复"""
    original_mode: str
    original_active_object: Optional["Object"]
    original_selected_objects: list["Object"]
    processed_materials: list[str]


class VRM_OT_separate_non_skin_faces(Operator):
    """将Body网格中非皮肤材质的面分离为独立对象"""

    bl_idname = "vrm.separate_non_skin_faces"
    bl_label = "Separate Non-Skin Faces"
    bl_description = "将Body网格中可分离材质（CLOTH和其他）的面合并分离为单个独立对象"
    bl_options: AbstractSet[str] = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context: Context) -> bool:
        """检查是否可以执行分离操作"""
        armature = cls._get_armature(context)
        if armature is None:
            return False

        body_mesh = find_body_mesh(armature)
        if body_mesh is None:
            return False

        # 检查是否有可分离材质（使用is_separable_material）
        mesh_data = body_mesh.data
        if mesh_data is None:
            return False

        for material in mesh_data.materials:
            if material is not None and is_separable_material(material.name):
                return True

        return False

    @classmethod
    def _get_armature(cls, context: Context) -> Optional["Object"]:
        """获取当前选中的Armature对象"""
        active_object = context.active_object
        if active_object is None:
            return None

        if active_object.type == "ARMATURE":
            return active_object

        # 如果选中的是网格，检查其父对象是否为Armature
        if active_object.parent is not None and active_object.parent.type == "ARMATURE":
            return active_object.parent

        return None

    def execute(self, context: Context) -> set[str]:
        """执行分离操作"""
        try:
            return self._execute_separation(context)
        except Exception as e:
            self.report({"ERROR"}, f"分离操作失败: {str(e)}")
            return {"CANCELLED"}

    def _execute_separation(self, context: Context) -> set[str]:
        """执行分离操作的核心逻辑"""
        armature = self._get_armature(context)
        if armature is None:
            self.report({"ERROR"}, "未找到Armature对象")
            return {"CANCELLED"}

        body_mesh = find_body_mesh(armature)
        if body_mesh is None:
            self.report({"ERROR"}, "未找到Body网格对象")
            return {"CANCELLED"}

        mesh_data = body_mesh.data
        if mesh_data is None:
            self.report({"ERROR"}, "Body网格数据无效")
            return {"CANCELLED"}

        # 收集可分离材质信息（使用is_separable_material）
        separable_materials: list[MaterialInfo] = []
        for i, material in enumerate(mesh_data.materials):
            if material is not None and is_separable_material(material.name):
                face_count = get_material_face_count(mesh_data, i)
                if face_count > 0:
                    separable_materials.append(
                        MaterialInfo(
                            name=material.name,
                            index=i,
                            face_count=face_count,
                            is_skin=False,
                        )
                    )

        if not separable_materials:
            self.report({"INFO"}, "没有找到可分离材质的面")
            return {"FINISHED"}

        # 获取现有对象名称集合
        existing_names = set(obj.name for obj in context.blend_data.objects)

        # 将所有可分离材质的面合并分离为单个对象
        material_indices = [mat_info.index for mat_info in separable_materials]
        new_obj = self._separate_all_material_faces(
            context, body_mesh, armature, material_indices, existing_names
        )

        if new_obj is not None:
            total_faces = sum(mat_info.face_count for mat_info in separable_materials)
            material_names = [mat_info.name for mat_info in separable_materials]
            self.report(
                {"INFO"},
                f"成功分离了 {len(separable_materials)} 个材质的 {total_faces} 个面到 {new_obj.name}"
            )
            return {"FINISHED"}
        else:
            self.report({"WARNING"}, "没有成功分离任何面")
            return {"CANCELLED"}

    def _separate_all_material_faces(
        self,
        context: Context,
        body_obj: "Object",
        armature: "Object",
        material_indices: list[int],
        existing_names: set[str],
    ) -> Optional["Object"]:
        """将所有指定材质的面合并分离为单个新对象"""
        mesh_data = body_obj.data
        if mesh_data is None:
            return None

        # 确保在对象模式
        if context.mode != "OBJECT":
            bpy.ops.object.mode_set(mode="OBJECT")

        # 取消所有选择
        bpy.ops.object.select_all(action="DESELECT")

        # 选择Body网格并设为活动对象
        body_obj.select_set(True)
        context.view_layer.objects.active = body_obj

        # 进入编辑模式
        bpy.ops.object.mode_set(mode="EDIT")

        # 取消所有选择
        bpy.ops.mesh.select_all(action="DESELECT")

        # 切换到面选择模式
        bpy.ops.mesh.select_mode(type="FACE")

        # 返回对象模式以访问网格数据
        bpy.ops.object.mode_set(mode="OBJECT")

        # 选择所有使用可分离材质的面
        material_index_set = set(material_indices)
        for polygon in mesh_data.polygons:
            if polygon.material_index in material_index_set:
                polygon.select = True

        # 返回编辑模式执行分离
        bpy.ops.object.mode_set(mode="EDIT")

        # 执行分离操作
        bpy.ops.mesh.separate(type="SELECTED")

        # 返回对象模式
        bpy.ops.object.mode_set(mode="OBJECT")

        # 查找新创建的对象
        new_obj = self._find_new_separated_object(context, body_obj)
        if new_obj is None:
            return None

        # 使用固定名称"Body_Separated"，处理命名冲突
        base_name = "Body_Separated"
        unique_name = generate_unique_name(base_name, existing_names)
        new_obj.name = unique_name

        # 设置父子关系（保持与原Armature的关系）
        new_obj.parent = armature
        new_obj.parent_type = "OBJECT"

        return new_obj

    def _find_new_separated_object(
        self, context: Context, original_obj: "Object"
    ) -> Optional["Object"]:
        """查找分离操作后新创建的对象"""
        # 分离后，新对象会被选中，原对象也会被选中
        for obj in context.selected_objects:
            if obj != original_obj and obj.type == "MESH":
                return obj
        return None


class VRM_OT_merge_uv_edges(Operator):
    """根据UV坐标融并边，将三角面转四边面"""

    bl_idname = "vrm.merge_uv_edges"
    bl_label = "Merge UV Edges"
    bl_description = "根据UV坐标选中并融并边，将三角面转换为四边面"
    bl_options: AbstractSet[str] = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context: Context) -> bool:
        """
        检查是否可以执行操作

        Requirements 1.1, 1.2, 1.3, 1.4:
        - 检测Armature对象或其子网格对象
        - 检测Body和Face网格是否存在
        - 在任意Blender模式下工作
        """
        armature = cls._get_armature(context)
        if armature is None:
            return False

        # Requirements 1.1, 1.2: 检查Body和Face网格是否都存在
        body_mesh = find_body_mesh(armature)
        face_mesh = find_face_mesh(armature)

        return body_mesh is not None and face_mesh is not None

    @classmethod
    def _get_armature(cls, context: Context) -> Optional["Object"]:
        """
        获取当前选中的Armature对象

        Requirements 1.1: 支持选中Armature或其子网格对象
        """
        active_object = context.active_object
        if active_object is None:
            return None

        if active_object.type == "ARMATURE":
            return active_object

        # 如果选中的是网格，检查其父对象是否为Armature
        if active_object.parent is not None and active_object.parent.type == "ARMATURE":
            return active_object.parent

        return None

    def execute(self, context: Context) -> set[str]:
        """
        执行边融并操作

        Requirements 5.3, 5.4, 6.1, 6.2:
        - 保存原始状态（模式、选择、活动对象）
        - 按PROCESSING_ORDER遍历材质
        - 调用_process_material处理每个材质
        - 恢复状态并报告结果
        """
        # 保存原始状态
        state = self._save_state(context)

        try:
            return self._execute_merge(context, state)
        except Exception as e:
            # 发生错误时恢复状态
            self._restore_state(context, state)
            self.report({"ERROR"}, f"边融并操作失败: {str(e)}")
            return {"CANCELLED"}

    def _save_state(self, context: Context) -> MergeOperationState:
        """保存当前Blender状态"""
        original_mode = context.object.mode if context.object else "OBJECT"
        original_active = context.view_layer.objects.active
        original_selected = list(context.selected_objects)

        return MergeOperationState(
            original_mode=original_mode,
            original_active_object=original_active,
            original_selected_objects=original_selected,
            processed_materials=[],
        )

    def _restore_state(self, context: Context, state: MergeOperationState) -> None:
        """
        恢复原始Blender状态

        Requirements 7.4: 错误时恢复原始状态
        """
        try:
            # 确保在对象模式
            if context.object and context.object.mode != "OBJECT":
                bpy.ops.object.mode_set(mode="OBJECT")

            # 取消所有选择
            bpy.ops.object.select_all(action="DESELECT")

            # 恢复原始选择
            for obj in state.original_selected_objects:
                if obj and obj.name in context.blend_data.objects:
                    obj.select_set(True)

            # 恢复原始活动对象
            if state.original_active_object and \
               state.original_active_object.name in context.blend_data.objects:
                context.view_layer.objects.active = state.original_active_object

            # 恢复原始模式
            if context.object and state.original_mode != "OBJECT":
                try:
                    bpy.ops.object.mode_set(mode=state.original_mode)
                except RuntimeError:
                    pass  # 某些模式可能无法恢复
        except Exception:
            pass  # 恢复失败时静默处理

    def _execute_merge(
        self, context: Context, state: MergeOperationState
    ) -> set[str]:
        """
        执行边融并的核心逻辑

        Requirements 6.1, 6.2: 按顺序处理材质，先Body后Face
        """
        armature = self._get_armature(context)
        if armature is None:
            self.report({"ERROR"}, "未找到Armature对象")
            return {"CANCELLED"}

        body_mesh = find_body_mesh(armature)
        face_mesh = find_face_mesh(armature)

        if body_mesh is None or face_mesh is None:
            self.report({"ERROR"}, "未找到Body或Face网格对象")
            return {"CANCELLED"}

        # 确保在对象模式开始
        if context.object and context.object.mode != "OBJECT":
            bpy.ops.object.mode_set(mode="OBJECT")

        # 处理结果统计
        results: list[ProcessResult] = []
        total_edges_dissolved = 0
        materials_processed = 0
        materials_skipped = 0

        # 按PROCESSING_ORDER遍历材质 (Requirements 6.1, 6.2)
        for material_name in PROCESSING_ORDER:
            # 确定目标网格
            target_mesh_name = get_target_mesh_name(material_name)
            if target_mesh_name is None:
                continue

            target_mesh = body_mesh if target_mesh_name == "Body" else face_mesh

            # 处理材质
            result = self._process_material(context, target_mesh, material_name)
            results.append(result)
            state.processed_materials.append(material_name)

            if result.success:
                materials_processed += 1
                total_edges_dissolved += result.edges_dissolved
            else:
                materials_skipped += 1
                if result.error_message:
                    self.report({"WARNING"}, result.error_message)

        # 确保返回对象模式 (Requirements 5.4)
        if context.object and context.object.mode != "OBJECT":
            bpy.ops.object.mode_set(mode="OBJECT")

        # 报告结果 (Requirements 6.4)
        if materials_processed > 0:
            self.report(
                {"INFO"},
                f"处理完成: {materials_processed} 个材质, "
                f"融并 {total_edges_dissolved} 条边"
            )
            return {"FINISHED"}
        else:
            self.report({"WARNING"}, f"没有成功处理任何材质 (跳过 {materials_skipped} 个)")
            return {"CANCELLED"}

    def _process_material(
        self,
        context: Context,
        mesh_obj: "Object",
        material_name: str,
    ) -> ProcessResult:
        """
        处理单个材质的边融并

        Requirements 5.1, 5.2, 6.3, 7.1, 7.2:
        - 确定目标网格（Body或Face）
        - 加载JSON边数据
        - 调用isolate_material_faces隔离材质
        - 调用select_edges_by_uv选择边
        - 执行dissolve_edges融并
        - 取消隐藏（Alt+H）
        - 处理缺失JSON文件 (7.1)
        - 处理无匹配边情况 (7.2)
        """
        import bmesh

        mesh_name = mesh_obj.name if mesh_obj else "Unknown"

        # 检查是否使用自动转换（tris_convert_to_quads）
        if material_name in AUTO_CONVERT_MATERIALS:
            return self._process_material_auto_convert(context, mesh_obj, material_name)

        try:
            # 加载JSON边数据 (Requirements 5.1, 7.1)
            target_uvs = load_edge_uv_data(material_name)
            if target_uvs is None:
                # Requirements 7.1: 记录警告并跳过
                return ProcessResult(
                    material_name=material_name,
                    mesh_name=mesh_name,
                    edges_selected=0,
                    edges_dissolved=0,
                    success=False,
                    error_message=f"跳过: 未找到材质 {material_name} 的JSON文件",
                )

            if len(target_uvs) == 0:
                return ProcessResult(
                    material_name=material_name,
                    mesh_name=mesh_name,
                    edges_selected=0,
                    edges_dissolved=0,
                    success=False,
                    error_message=f"跳过: 材质 {material_name} 的JSON文件中没有边数据",
                )

            # 隔离材质面 (Requirements 5.2)
            if not isolate_material_faces(context, mesh_obj, material_name):
                return ProcessResult(
                    material_name=material_name,
                    mesh_name=mesh_name,
                    edges_selected=0,
                    edges_dissolved=0,
                    success=False,
                    error_message=f"跳过: 无法隔离材质 {material_name} 的面",
                )

            # 此时应该在编辑模式，边选择模式
            # 取消所有边选择
            bpy.ops.mesh.select_all(action="DESELECT")

            # 使用BMesh选择边
            mesh_data = mesh_obj.data
            bm = bmesh.from_edit_mesh(mesh_data)

            # 获取UV层
            uv_layer = bm.loops.layers.uv.active
            if uv_layer is None:
                # 取消隐藏并返回
                bpy.ops.mesh.reveal()
                bpy.ops.object.mode_set(mode="OBJECT")
                return ProcessResult(
                    material_name=material_name,
                    mesh_name=mesh_name,
                    edges_selected=0,
                    edges_dissolved=0,
                    success=False,
                    error_message=f"跳过: 网格 {mesh_name} 没有UV层",
                )

            # 选择匹配UV的边 (Requirements 5.2)
            edges_selected = select_edges_by_uv(bm, uv_layer, target_uvs)

            # 更新BMesh
            bmesh.update_edit_mesh(mesh_data)

            # Requirements 7.2: 处理无匹配边情况
            if edges_selected == 0:
                # 取消隐藏并返回
                bpy.ops.mesh.reveal()
                bpy.ops.object.mode_set(mode="OBJECT")
                return ProcessResult(
                    material_name=material_name,
                    mesh_name=mesh_name,
                    edges_selected=0,
                    edges_dissolved=0,
                    success=False,
                    error_message=(
                        f"跳过: 材质 {material_name} 没有找到匹配的边 "
                        f"(目标边数: {len(target_uvs)})"
                    ),
                )

            # 执行边融并 (Requirements 5.1)
            edges_dissolved = 0
            try:
                bpy.ops.mesh.dissolve_edges()
                edges_dissolved = edges_selected
            except RuntimeError as e:
                # 融并失败，可能是边不可融并，但不算完全失败
                self.report({"WARNING"}, f"材质 {material_name} 融并边时出现问题: {str(e)}")

            # 取消隐藏 (Alt+H equivalent) (Requirements 5.2)
            bpy.ops.mesh.reveal()

            # 返回对象模式 (Requirements 6.3)
            bpy.ops.object.mode_set(mode="OBJECT")

            return ProcessResult(
                material_name=material_name,
                mesh_name=mesh_name,
                edges_selected=edges_selected,
                edges_dissolved=edges_dissolved,
                success=True,
                error_message=None,
            )

        except Exception as e:
            # Requirements 7.4: 捕获异常，尝试恢复状态
            try:
                # 尝试取消隐藏
                if context.object and context.object.mode == "EDIT":
                    bpy.ops.mesh.reveal()
                    bpy.ops.object.mode_set(mode="OBJECT")
            except Exception:
                pass

            return ProcessResult(
                material_name=material_name,
                mesh_name=mesh_name,
                edges_selected=0,
                edges_dissolved=0,
                success=False,
                error_message=f"处理材质 {material_name} 时发生错误: {str(e)}",
            )

    def _process_material_auto_convert(
        self,
        context: Context,
        mesh_obj: "Object",
        material_name: str,
    ) -> ProcessResult:
        """
        使用Blender自带的tris_convert_to_quads处理规律网格的材质

        对于网格结构规律的材质，直接使用Blender的自动转换功能，
        不需要基于UV坐标的边选择。
        """
        mesh_name = mesh_obj.name if mesh_obj else "Unknown"

        try:
            # 隔离材质面
            if not isolate_material_faces(context, mesh_obj, material_name):
                return ProcessResult(
                    material_name=material_name,
                    mesh_name=mesh_name,
                    edges_selected=0,
                    edges_dissolved=0,
                    success=False,
                    error_message=f"跳过: 无法隔离材质 {material_name} 的面",
                )

            # 切换到面选择模式
            bpy.ops.mesh.select_mode(type="FACE")

            # 选择所有可见的面
            bpy.ops.mesh.select_all(action="SELECT")

            # 使用Blender自带的三角面转四边面
            bpy.ops.mesh.tris_convert_to_quads()

            # 取消隐藏
            bpy.ops.mesh.reveal()

            # 返回对象模式
            bpy.ops.object.mode_set(mode="OBJECT")

            return ProcessResult(
                material_name=material_name,
                mesh_name=mesh_name,
                edges_selected=0,
                edges_dissolved=0,
                success=True,
                error_message=None,
            )

        except Exception as e:
            # 捕获异常，尝试恢复状态
            try:
                if context.object and context.object.mode == "EDIT":
                    bpy.ops.mesh.reveal()
                    bpy.ops.object.mode_set(mode="OBJECT")
            except Exception:
                pass

            return ProcessResult(
                material_name=material_name,
                mesh_name=mesh_name,
                edges_selected=0,
                edges_dissolved=0,
                success=False,
                error_message=f"处理材质 {material_name} 时发生错误: {str(e)}",
            )
