import traceback
import bpy
import math
from typing import Set, List
from bpy.types import Operator, Context, Armature, EditBone
from ...core.translations import t
from ...core.logging_setup import logger
from ...core.common import get_active_armature, get_all_meshes, get_vertex_weights, transfer_vertex_weights, store_breaking_settings_armature, restore_breaking_settings_armature
from ...core.armature_validation import validate_armature
import traceback

class AvatarToolkit_OT_ConnectBones(Operator):
    """Connect disconnected bones in chain"""
    bl_idname = "avatar_toolkit.connect_bones"
    bl_label = t("Tools.connect_bones")
    bl_description = t("Tools.connect_bones_desc")
    bl_options = {'REGISTER', 'UNDO'}
    
    @classmethod
    def poll(cls, context: Context) -> bool:
        armature = get_active_armature(context)
        if not armature:
            return False
        valid, _, _ = validate_armature(armature)
        return valid
        
    def execute(self, context: Context) -> Set[str]:
        armature = get_active_armature(context)
        data_breaking = store_breaking_settings_armature(armature)
        try:
            
            

            logger.info("Starting bone connection operation")
            
            bpy.ops.object.mode_set(mode='EDIT')
            edit_bones = armature.data.edit_bones
            bones_connected = 0
            min_distance = context.scene.avatar_toolkit.connect_bones_min_distance
            
            excluded_bones = {'LeftEye', 'RightEye', 'Head', 'Hips'}
            
            for bone in edit_bones:
                if len(bone.children) == 1 and bone.name not in excluded_bones:
                    child = bone.children[0]
                    distance = math.dist(bone.tail, child.head)
                    
                    if distance > min_distance:
                        logger.debug(f"Connecting bone {bone.name} to {child.name}")
                        bone.tail = child.head
                        if bone.parent and len(bone.parent.children) == 1:
                            bone.use_connect = True
                        bones_connected += 1
                        
            bpy.ops.object.mode_set(mode='OBJECT')
            restore_breaking_settings_armature(armature, data_breaking)
            self.report({'INFO'}, t("Tools.connect_bones_success", count=bones_connected))
            return {'FINISHED'}
            
        except Exception:
            logger.error(f"Failed to connect bones: {traceback.format_exc()}")
            self.report({'ERROR'}, traceback.format_exc())
            restore_breaking_settings_armature(armature, data_breaking)
            return {'CANCELLED'}

class AvatarToolkit_OT_MergeToActive(Operator):
    """Merge selected bones into active bone and transfer weights"""
    bl_idname = "avatar_toolkit.merge_to_active"
    bl_label = t("Tools.merge_to_active")
    bl_description = t("Tools.merge_to_active_desc")
    bl_options = {'REGISTER', 'UNDO'}
    
    @classmethod
    def poll(cls, context: Context) -> bool:
        armature = get_active_armature(context)
        if not armature:
            return False
        return (context.mode == 'EDIT_ARMATURE' or context.mode == 'POSE') and context.active_bone
        
    def execute(self, context: Context) -> Set[str]:
        armature = get_active_armature(context)
        data_breaking = store_breaking_settings_armature(armature)
        
        try:
            bpy.ops.object.mode_set(mode='EDIT')
            
            active_bone = context.active_bone
            selected_bones = [b for b in context.selected_editable_bones if b != active_bone]
            
            if not selected_bones:
                self.report({'WARNING'}, t("Tools.no_bones_selected"))
                return {'CANCELLED'}
                
            logger.info(f"Merging {len(selected_bones)} bones into {active_bone.name}")
            
            # Store weights before merging
            meshes = get_all_meshes(context)
            weight_data = {}
            for bone in selected_bones:
                for mesh in meshes:
                    if bone.name in mesh.vertex_groups:
                        weights = get_vertex_weights(mesh, bone.name)
                        weight_data.setdefault(mesh.name, {})[bone.name] = weights
            
            # Transfer weights to active bone
            threshold = context.scene.avatar_toolkit.merge_weights_threshold
            for mesh_name, bone_weights in weight_data.items():
                mesh = bpy.data.objects[mesh_name]
                for bone_name, weights in bone_weights.items():
                    transfer_vertex_weights(mesh, bone_name, active_bone.name, threshold)
            
            # Delete merged bones
            for bone in selected_bones:
                armature.data.edit_bones.remove(bone)
            
            self.report({'INFO'}, t("Tools.merge_to_active_success", count=len(selected_bones)))
            restore_breaking_settings_armature(armature, data_breaking)
            return {'FINISHED'}
            
        except Exception:
            logger.error(f"Failed to merge bones: {traceback.format_exc()}")
            self.report({'ERROR'}, traceback.format_exc())
            restore_breaking_settings_armature(armature, data_breaking)
            return {'CANCELLED'}

class AvatarToolkit_OT_MergeToParent(Operator):
    """Merge selected bones into their respective parents and transfer weights"""
    bl_idname = "avatar_toolkit.merge_to_parent"
    bl_label = t("Tools.merge_to_parent")
    bl_description = t("Tools.merge_to_parent_desc")
    bl_options = {'REGISTER', 'UNDO'}
    
    @classmethod
    def poll(cls, context: Context) -> bool:
        armature = get_active_armature(context)
        if not armature:
            return False
        return (context.mode == 'EDIT_ARMATURE' or context.mode == 'POSE')
        
    def execute(self, context: Context) -> Set[str]:
        armature = get_active_armature(context)
        data_breaking = store_breaking_settings_armature(armature)
        try:
            bpy.ops.object.mode_set(mode='EDIT')
            selected_bones = [b for b in context.selected_editable_bones if b.parent]
            
            if not selected_bones:
                self.report({'WARNING'}, t("Tools.no_bones_with_parent"))
                return {'CANCELLED'}
                
            logger.info(f"Merging {len(selected_bones)} bones to their parents")
            
            # Store weights before merging
            meshes = get_all_meshes(context)
            merged_count = 0
            threshold = context.scene.avatar_toolkit.merge_weights_threshold
            
            for bone in selected_bones:
                parent = bone.parent
                if not parent:
                    continue
                    
                # Transfer weights to parent
                for mesh in meshes:
                    if bone.name in mesh.vertex_groups:
                        transfer_vertex_weights(mesh, bone.name, parent.name, threshold)
                
                # Delete merged bone
                armature.data.edit_bones.remove(bone)
                merged_count += 1
            
            restore_breaking_settings_armature(armature, data_breaking)
            self.report({'INFO'}, t("Tools.merge_to_parent_success", count=merged_count))
            return {'FINISHED'}
            
        except Exception:
            logger.error(f"Failed to merge bones: {traceback.format_exc()}")
            self.report({'ERROR'}, traceback.format_exc())
            restore_breaking_settings_armature(armature, data_breaking)
            return {'CANCELLED'}
