import bpy
from mathutils import kdtree

bl_info = {
    "name": "FVNE-Expression-Manager",
    "author": "Blacky",
    "version": (1, 0),
    "blender": (3, 3, 0),
    "location": "Object > Mirror Shape Keys",
    "description": "Add,Split,Combine and Mirror emotes for FVNE",
    "category": "Object"
}

# FVNE Shape Key Preset Groups
EYE_PRESETS = [
    "ue_Eye/Wide",
    "ue_Eye/Squint",
    "ue_Eye/Blink",
    "ue_Eye/CloseHappy",
    "ue_Eye/CloseHard",
]

BROW_PRESETS = [
    "ue_Brow/Up",
    "ue_Brow/Frown",
    "ue_Brow/Anger",
    "ue_Brow/Happy",
    "ue_Brow/Relieve",
    "ue_Brow/Sad",
]

MOUTH_EMOTE_PRESETS = [
    "ue_Mouth/Emote/Smile",
    "ue_Mouth/Emote/Grin",
    "ue_Mouth/Emote/Anger",
    "ue_Mouth/Emote/Fear",
    "ue_Mouth/Emote/Sad",
    "ue_Mouth/Emote/LipBite",
    "ue_Mouth/Emote/OOOH",
    "ue_Mouth/Emote/Open",
    "ue_Mouth/Emote/Close",
]

MOUTH_VISEME_PRESETS = [
    "ue_Mouth/Visemes/Aa",
    "ue_Mouth/Visemes/Ch",
    "ue_Mouth/Visemes/Dd",
    "ue_Mouth/Visemes/Ee",
    "ue_Mouth/Visemes/Ff",
    "ue_Mouth/Visemes/Ih",
    "ue_Mouth/Visemes/Kk",
    "ue_Mouth/Visemes/Nn",
    "ue_Mouth/Visemes/Oh",
    "ue_Mouth/Visemes/Ou",
    "ue_Mouth/Visemes/Pp",
]

FACE_PRESETS = [
    "ue_Face/Happy",
    "ue_Face/Relieved",
    "ue_Face/Surprised",
    "ue_Face/Serious",
    "ue_Face/Sad",
    "ue_Face/Fearful",
    "ue_Face/Angry",
    "ue_Face/Painful",
]

BODY_PRESETS = [
    "ue_Body/ChestBreathing",
    "ue_Body/BellyBreathing",
    "ue_Body/CheekBloat",
]

FULL_PRESET_SET = (
    EYE_PRESETS + BROW_PRESETS + MOUTH_EMOTE_PRESETS + MOUTH_VISEME_PRESETS +
    FACE_PRESETS + BODY_PRESETS
)


# --- Utility Functions ---

def find_basis_key(key_blocks):
    for key in key_blocks:
        if key.name.lower() == "basis":
            return key
    return None

import re

def get_side_from_name(name):
    if re.search(r'_L\b', name):
        return 'L'
    elif re.search(r'_R\b', name):
        return 'R'
    return None


def ensure_r_above_l(obj, r_name, l_name):
    key_blocks = obj.data.shape_keys.key_blocks
    r_index = key_blocks.find(r_name)
    l_index = key_blocks.find(l_name)
    while r_index > l_index:
        obj.active_shape_key_index = r_index
        bpy.ops.object.shape_key_move(type='UP')
        r_index -= 1
        l_index = key_blocks.find(l_name)
        
import bpy

# Function to add "_R" to shape keys that don't already have "_L" or "_R"
def add_R_suffix_to_shape_keys():
    obj = bpy.context.object
    if not obj or not obj.data.shape_keys:
        return

    key_blocks = obj.data.shape_keys.key_blocks
    existing = {kb.name for kb in key_blocks}

    for key in list(key_blocks):
        name = key.name
        # only operate on ue_ keys, and skip any already suffixed
        if "ue_" not in name or name.endswith(("_L", "_R")):
            continue

        target = f"{name}_R"
        # skip if the exact _R version already exists
        if target in existing:
            continue

        key.name = target
        existing.add(target)

# Function to add "_L" to shape keys that don't already have "_L" or "_R"
def add_L_suffix_to_shape_keys():
    obj = bpy.context.object
    if not obj or not obj.data.shape_keys:
        return

    key_blocks = obj.data.shape_keys.key_blocks
    existing = {kb.name for kb in key_blocks}

    for key in list(key_blocks):
        name = key.name
        # only operate on ue_ keys, and skip any already suffixed
        if "ue_" not in name or name.endswith(("_L", "_R")):
            continue

        target = f"{name}_L"
        # skip if the exact _L version already exists
        if target in existing:
            continue

        key.name = target
        existing.add(target)

def sort_shape_keys_smart(obj):
    key_blocks = obj.data.shape_keys.key_blocks
    all_names = [kb.name for kb in key_blocks]

    # 1. Fixed top block (do not move)
    fixed_order = ["basis", "Shape", "i_flatchested", "i_breasts", "i_belly", "i_ass_hips"]
    fixed_present = [n for n in fixed_order if n in all_names]

    # 2. Priority ordered keys (Eyes, Brow, Mouth, Face, Body)
    priority_order = [
        # Eyes
        "ue_Eye/Wide", "ue_Eye/Squint", "ue_Eye/Blink",
        "ue_Eye/CloseHappy", "ue_Eye/CloseHard",

        # Brows
        "ue_Brow/Up", "ue_Brow/Frown", "ue_Brow/Anger",
        "ue_Brow/Happy", "ue_Brow/Relieve", "ue_Brow/Sad",

        # Mouth Emotes
        "ue_Mouth/Emote/Smile", "ue_Mouth/Emote/Grin",
        "ue_Mouth/Emote/Anger", "ue_Mouth/Emote/Fear",
        "ue_Mouth/Emote/Sad", "ue_Mouth/Emote/LipBite",
        "ue_Mouth/Emote/OOOH", "ue_Mouth/Emote/Open",
        "ue_Mouth/Emote/Close",

        # Visemes
        "ue_Mouth/Visemes/Aa", "ue_Mouth/Visemes/Ch", "ue_Mouth/Visemes/Dd",
        "ue_Mouth/Visemes/Ee", "ue_Mouth/Visemes/Ff", "ue_Mouth/Visemes/Ih",
        "ue_Mouth/Visemes/Kk", "ue_Mouth/Visemes/Nn", "ue_Mouth/Visemes/Oh",
        "ue_Mouth/Visemes/Ou", "ue_Mouth/Visemes/Pp",

        # Face Expressions
        "ue_Face/Happy", "ue_Face/Relieved", "ue_Face/Surprised",
        "ue_Face/Serious", "ue_Face/Sad", "ue_Face/Fearful",
        "ue_Face/Angry", "ue_Face/Painful",

        # Body
        "ue_Body/ChestBreathing", "ue_Body/BellyBreathing", "ue_Body/CheekBloat",
    ]
    priority_map = {n.lower(): i for i, n in enumerate(priority_order)}

    # 3. Categorizer for sub-buckets
    def get_subcat(nl):
        if nl.startswith("ue_eye/"):             return 0
        if nl.startswith("ue_brow/"):            return 1
        if nl.startswith("ue_mouth/emote/"):     return 2
        if nl.startswith("ue_Mouth/Visemes/"):   return 3
        if nl.startswith("ue_face/"):            return 4
        if nl.startswith("ue_body/"):            return 5
        return 6  # unknown bucket

    def sort_key(kb):
        nl = kb.name.lower()

        # strip _L/_R suffix
        if nl.endswith("_l") or nl.endswith("_r"):
            base = kb.name[:-2]
            suffix = 1 if nl.endswith("_r") else 2
        else:
            base = kb.name
            suffix = 0

        bl = base.lower()
        subcat = get_subcat(bl)

        # Priority check
        if bl in priority_map:
            rank = (0, priority_map[bl])
        else:
            rank = (1, bl)

        return (subcat, rank, suffix)

    # 4. Filter everything not in fixed order
    rest = [n for n in all_names if n not in fixed_present]
    rest_kb = [key_blocks[n] for n in rest]
    sorted_rest = sorted(rest_kb, key=sort_key)
    sorted_rest_names = [kb.name for kb in sorted_rest]

    # 5. Final target order
    full_order = fixed_present + sorted_rest_names

    # 6. Move into place
    for target_idx, name in enumerate(full_order):
        cur_idx = key_blocks.find(name)
        while cur_idx > target_idx:
            obj.active_shape_key_index = cur_idx
            bpy.ops.object.shape_key_move(type='UP')
            cur_idx -= 1
        while cur_idx < target_idx:
            obj.active_shape_key_index = cur_idx
            bpy.ops.object.shape_key_move(type='DOWN')
            cur_idx += 1

# --- Core Functionality ---

def create_tree(vertices):
    tree = kdtree.KDTree(len(vertices))
    for i, vertex in enumerate(vertices):
        tree.insert(vertex.co.copy(), i)
    tree.balance()
    return tree

def mirror_shape(shape, base_verts, tree, direction, is_exclusive=False):
    shape_verts = shape.data

    for i, base_vert in enumerate(base_verts):
        base_co = base_vert.co.copy()
        shape_co = shape_verts[i].co.copy()

        is_left = base_co.x < -1e-7
        is_right = base_co.x > 1e-7
        is_center = not is_left and not is_right

        if is_center:
            shape_co.x = 0.0
            shape_verts[i].co = shape_co
        elif (direction == 'RL' and is_left) or (direction == 'LR' and is_right):
            mirrored_co = shape_co.copy()
            mirrored_co.x = -mirrored_co.x

            mirrored_Wide = base_co.copy()
            mirrored_Wide.x = -mirrored_Wide.x

            _, target_index, _ = tree.find(mirrored_Wide)
            shape_verts[target_index].co = mirrored_co

            if is_exclusive:
                shape_verts[i].co = base_verts[i].co.copy()
        elif is_exclusive:
            shape_verts[i].co = base_verts[i].co.copy()

def mirror_selected_shape_keys_vertex(direction):
    obj = bpy.context.active_object
    if not obj or not obj.data.shape_keys:
        print("No shape keys found.")
        return

    active_index = obj.active_shape_key_index
    if active_index == 0:
        print("Cannot mirror basis shape key.")
        return

    key_blocks = obj.data.shape_keys.key_blocks
    shape = key_blocks[active_index]
    basis_verts = key_blocks[0].data
    tree = create_tree(basis_verts)

    mirror_shape(shape, basis_verts, tree, direction, is_exclusive=False)

def smart_mirror_shape_keys_by_suffix():
    obj = bpy.context.active_object
    if not obj or not obj.data.shape_keys:
        print("No shape keys found.")
        return

    key_blocks = obj.data.shape_keys.key_blocks
    existing_keys = {k.name for k in key_blocks}
    keys_to_process = [k.name for k in key_blocks[1:]]

    for name in keys_to_process:
        if name.endswith('_L'):
            suffix_from, suffix_to = '_L', '_R'
        elif name.endswith('_R'):
            suffix_from, suffix_to = '_R', '_L'
        else:
            continue

        mirrored_name = name[:-2] + suffix_to
        if mirrored_name in existing_keys:
            continue

        print(f"Mirroring {name} → {mirrored_name}")
        original_index = key_blocks.find(name)
        obj.active_shape_key_index = original_index

        bpy.ops.object.shape_key_add(from_mix=False)
        new_key = obj.active_shape_key
        new_key.name = mirrored_name

        for i, vert in enumerate(key_blocks[name].data):
            new_key.data[i].co = vert.co.copy()

        bpy.ops.object.shape_key_mirror(use_topology=False)

        if suffix_to == '_R':
            ensure_r_above_l(obj, mirrored_name, name)
        else:
            ensure_r_above_l(obj, name, mirrored_name)

import bpy

def find_basis_key(key_blocks):
    for kb in key_blocks:
        if kb.name.lower() == "basis":
            return kb
    return None

def create_combined_symmetric_shape_keys():
    obj = bpy.context.active_object
    if obj is None or obj.type != 'MESH' or obj.data.shape_keys is None:
        return

    key_blocks = obj.data.shape_keys.key_blocks
    key_names = [k.name for k in key_blocks]

    left_keys = [k for k in key_names if k.endswith("_L")]
    basis = find_basis_key(key_blocks)

    if not basis:
        print("Basis key not found!")
        return

    for left_key in left_keys:
        base_name = left_key[:-2]  # Remove "_L"
        right_key = base_name + "_R"

        # Restrict to only shape keys starting with "ue_"
        if not base_name.startswith("ue_"):
            print(f"Skipping {base_name}: does not start with 'ue_'")
            continue

        if right_key not in key_names:
            print(f"Skipping {base_name}: missing right key")
            continue

        if base_name in key_names:
            print(f"Skipping {base_name}: combined shape key already exists")
            continue

        # Create new combined shape key
        obj.active_shape_key_index = key_names.index(left_key)
        bpy.ops.object.shape_key_add(from_mix=False)
        combined_key = obj.active_shape_key
        combined_key.name = base_name

        left_data = key_blocks[left_key].data
        right_data = key_blocks[right_key].data
        combined_data = combined_key.data
        basis_data = basis.data

        for i in range(len(combined_data)):
            base = basis_data[i].co
            delta_l = left_data[i].co - base
            delta_r = right_data[i].co - base
            combined_data[i].co = base + delta_l + delta_r

        print(f"Created symmetric shape key: {base_name}")


import bpy
import mathutils

def split_symmetric_shape_keys():
    obj = bpy.context.object
    if not obj or not obj.data.shape_keys:
        return

    key_blocks = obj.data.shape_keys.key_blocks
    basis = find_basis_key(key_blocks)
    if not basis:
        return

    symmetric_keys = [
    kb for kb in key_blocks
    if kb.name.lower() != "basis"
    and not kb.name.endswith(("_L", "_R"))
    and kb.name.startswith("ue_")
]


    for key in symmetric_keys:
        name = key.name
        verts = obj.data.vertices

        has_L = f"{name}_L" in key_blocks
        has_R = f"{name}_R" in key_blocks
        if has_L and has_R:
            continue

        key_L = key_blocks.get(f"{name}_L") or obj.shape_key_add(name=f"{name}_L", from_mix=False)
        key_R = key_blocks.get(f"{name}_R") or obj.shape_key_add(name=f"{name}_R", from_mix=False)

        for i, v in enumerate(verts):
            base_co = basis.data[i].co
            shape_co = key.data[i].co
            delta = shape_co - base_co

            if base_co.x > 0:
                key_L.data[i].co = shape_co
                key_R.data[i].co = base_co
            elif base_co.x < 0:
                key_R.data[i].co = shape_co
                key_L.data[i].co = base_co
            else:
                # Optional: handle center-line verts symmetrically
                mid = base_co + delta * 0.5
                key_L.data[i].co = mid
                key_R.data[i].co = mid

        print(f"Split “{name}” → “{name}_L” + “{name}_R”")

import bpy
import mathutils

def find_basis_key(key_blocks):
    for kb in key_blocks:
        if kb.name.lower() == "basis":
            return kb
    return None

import bpy
import mathutils

def find_basis_key(key_blocks):
    for kb in key_blocks:
        if kb.name.lower() == "basis":
            return kb
    return None

def split_active_symmetric_shape_key():
    obj = bpy.context.active_object
    if not obj or obj.type != 'MESH':
        return

    keys = obj.data.shape_keys
    if not keys:
        return
    key_blocks = keys.key_blocks

    basis = find_basis_key(key_blocks)
    if not basis:
        print("No Basis key found.")
        return

    idx = obj.active_shape_key_index
    if idx is None or idx == 0:
        print("No active non‑Basis key.")
        return

    active = key_blocks[idx]
    name = active.name
    nl = name.lower()

    if not nl.startswith("ue_"):
        print(f"Skipping '{name}' (must start with 'ue_').")
        return
    if nl.endswith("_l") or nl.endswith("_r"):
        print(f"Skipping '{name}' (already _L/_R).")
        return

    l_name = f"{name}_L"
    r_name = f"{name}_R"
    has_L = l_name in key_blocks
    has_R = r_name in key_blocks
    if has_L and has_R:
        print(f"Both '{l_name}' and '{r_name}' already exist.")
        return

    key_L = key_blocks[l_name] if has_L else obj.shape_key_add(name=l_name, from_mix=False)
    key_R = key_blocks[r_name] if has_R else obj.shape_key_add(name=r_name, from_mix=False)

    verts = obj.data.vertices
    for i, v in enumerate(verts):
        base_co = basis.data[i].co
        key_co  = active.data[i].co
        delta   = key_co - base_co

        if delta.length_squared < 1e-12:
            continue

        # +X (right side of mesh) → character's LEFT (_L)
        if base_co.x > 0:
            key_L.data[i].co = base_co + delta
        # -X → character's RIGHT (_R)
        elif base_co.x < 0:
            key_R.data[i].co = base_co + delta

    print(f"Split '{name}' → '{l_name}' + '{r_name}'")


def apply_shape_key(obj, shape_key_name):
    if shape_key_name not in obj.data.shape_keys.key_blocks:
        obj.shape_key_add(name=shape_key_name, from_mix=False)

def add_shape_keys(obj, key_list):
    for key_name in key_list:
        apply_shape_key(obj, key_name)


# --- Operators ---

class FVNE_OT_smart_mirror_keys(bpy.types.Operator):
    bl_idname = "object.fvne_smart_mirror_keys"
    bl_label = "Create R / L Counterparts"
    bl_description = "Auto-creates the counterpart of already present _R or_L ue shape keys."
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        smart_mirror_shape_keys_by_suffix()
        return {'FINISHED'}
        
class FVNE_OT_mirror_vertex_lr(bpy.types.Operator):
    bl_idname = "object.fvne_mirror_vertex_lr"
    bl_label = "L → R"
    bl_description = "Mirror selected shape key from Left to Right using vertex index"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        mirror_selected_shape_keys_vertex(direction='LR')
        return {'FINISHED'}


class FVNE_OT_mirror_vertex_rl(bpy.types.Operator):
    bl_idname = "object.fvne_mirror_vertex_rl"
    bl_label = "R → L"
    bl_description = "Mirror selected shape key from Right to Left using vertex index"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        mirror_selected_shape_keys_vertex(direction='RL')
        return {'FINISHED'}


class FVNE_OT_split_symmetric_keys(bpy.types.Operator):
    bl_idname = "object.fvne_split_symmetric_keys"
    bl_label = "Split All Main Keys"
    bl_description = "Creates _R and _L shape key versions based on all present ue_ shapekeys (X-Axis)"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        split_symmetric_shape_keys()
        return {'FINISHED'}

class FVNE_OT_split_active_symmetric_key(bpy.types.Operator):
    bl_idname = "object.fvne_split_active_symmetric_key"
    bl_label = "Split Active Key"
    bl_description = "Creates _R and _L shape key versions only for the currently selected ue_ shapekey (X-Axis)"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        split_active_symmetric_shape_key()
        return {'FINISHED'}

class FVNE_OT_combine_symmetric_keys(bpy.types.Operator):
    bl_idname = "object.fvne_combine_symmetric_keys"
    bl_label = "Combine _R & _L to Main Key"
    bl_description = "Combine the values of _R and _L shape keys into one new unified key"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        create_combined_symmetric_shape_keys()
        return {'FINISHED'}

import bpy

# 1) PropertyGroup for each shape key entry
class FVNE_ShapeKeyItem(bpy.types.PropertyGroup):
    name: bpy.props.StringProperty(name="Shape Key Name")
    select: bpy.props.BoolProperty(name="Include")

# 2) UIList to display that collection
import bpy

class FVNE_UL_shape_key_list(bpy.types.UIList):
    """Show full shape-key names left-aligned in our combine dialog."""
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
        # data is the Operator, item is one list item type (with .name, .select)
        split = layout.split(factor=0.05, align=True)
        # draw the checkbox:
        split.prop(item, "select", text="")
        # draw the full name left-aligned:
        split.label(text=item.name)

# 3) The “combine selected” operator
class FVNE_OT_combine_selected_shape_keys(bpy.types.Operator):
    bl_idname = "object.fvne_combine_selected_shape_keys"
    bl_label = "Combine Selected Shape Keys"
    bl_description = "Bake a new key from the mix of selected shape keys"
    bl_options = {'REGISTER','UNDO'}

    keys_list: bpy.props.CollectionProperty(type=FVNE_ShapeKeyItem)
    keys_index: bpy.props.IntProperty()
    new_name: bpy.props.StringProperty(name="New Key Name", default="Combined")

    @classmethod
    def poll(cls, context):
        return context.object and context.object.type == 'MESH' and context.object.data.shape_keys

    def invoke(self, context, event):
        self.keys_list.clear()
        for kb in context.object.data.shape_keys.key_blocks:
            if kb.name.lower() == "basis":
                continue
            item = self.keys_list.add()
            item.name = kb.name
            item.select = False
        return context.window_manager.invoke_props_dialog(self, width=400)

    def draw(self, context):
        layout = self.layout
        layout.label(text="Select shape keys to combine:")
        layout.template_list(
            "FVNE_UL_shape_key_list",   # our new UIList
            "",                         # list_id
            self, "keys_list",          # data, collection
            self, "keys_index",         # data, active_prop
            rows=6
        )
        layout.separator()
        layout.prop(self, "new_name")

    def execute(self, context):
        obj = context.object
        kbs = obj.data.shape_keys.key_blocks

        name = self.new_name.strip()
        if not name:
            self.report({'ERROR'}, "New key needs a name.")
            return {'CANCELLED'}
        if kbs.get(name):
            self.report({'WARNING'}, f"Shape key '{name}' already exists.")
            return {'CANCELLED'}

        to_bake = [item.name for item in self.keys_list if item.select]
        if not to_bake:
            self.report({'WARNING'}, "No shape keys selected.")
            return {'CANCELLED'}

        old_vals = {kb.name: kb.value for kb in kbs}
        for kb in kbs:
            kb.value = 1.0 if kb.name in to_bake else 0.0

        bpy.ops.object.shape_key_add(from_mix=True)
        new_kb = obj.active_shape_key
        new_kb.name = name

        for nm, val in old_vals.items():
            kbs[nm].value = val

        self.report({'INFO'}, f"Created '{name}' from {len(to_bake)} keys.")
        return {'FINISHED'}

class FVNE_OT_mirror_selected_rl(bpy.types.Operator):
    bl_idname = "object.fvne_mirror_rl"
    bl_label = "Mirror Selected (Right → Left)"
    bl_description = "Mirror selected shape key from Right to Left"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        mirror_selected_shape_keys(direction='RL')
        return {'FINISHED'}

class FVNE_OT_mirror_selected_lr(bpy.types.Operator):
    bl_idname = "object.fvne_mirror_lr"
    bl_label = "Mirror Selected (Left → Right)"
    bl_description = "Mirror selected shape key from Left to Right"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        mirror_selected_shape_keys(direction='LR')
        return {'FINISHED'}

class FVNE_OT_add_eye_keys(bpy.types.Operator):
    bl_idname = "object.fvne_add_eye_keys"
    bl_label = "Add Eye Keys"
    bl_description = "Adds preset user expressions for the eyes."
    bl_options = {'REGISTER', 'UNDO'}
    
    def execute(self, context):
        add_shape_keys(context.object, EYE_PRESETS)
        return {'FINISHED'}

class FVNE_OT_add_brow_keys(bpy.types.Operator):
    bl_idname = "object.fvne_add_brow_keys"
    bl_label = "Add Brow Keys"
    bl_description = "Adds preset user expressions for the eyebrows."
    bl_options = {'REGISTER', 'UNDO'}
    
    def execute(self, context):
        add_shape_keys(context.object, BROW_PRESETS)
        return {'FINISHED'}

class FVNE_OT_add_emote_keys(bpy.types.Operator):
    bl_idname = "object.fvne_add_emote_keys"
    bl_label = "Add Mouth Emote Keys"
    bl_description = "Adds preset user expressions for mouth emotes."
    bl_options = {'REGISTER', 'UNDO'}
    
    def execute(self, context):
        add_shape_keys(context.object, MOUTH_EMOTE_PRESETS)
        return {'FINISHED'}

class FVNE_OT_add_viseme_keys(bpy.types.Operator):
    bl_idname = "object.fvne_add_viseme_keys"
    bl_label = "Add Mouth Viseme Keys"
    bl_description = "Adds preset user expressions for mouth visemes."
    bl_options = {'REGISTER', 'UNDO'}
    
    def execute(self, context):
        add_shape_keys(context.object, MOUTH_VISEME_PRESETS)
        return {'FINISHED'}

class FVNE_OT_add_face_keys(bpy.types.Operator):
    bl_idname = "object.fvne_add_face_keys"
    bl_label = "Add Face Keys"
    bl_description = "Adds preset user expressions for the face."
    bl_options = {'REGISTER', 'UNDO'}
    
    def execute(self, context):
        add_shape_keys(context.object, FACE_PRESETS)
        return {'FINISHED'}

class FVNE_OT_add_body_keys(bpy.types.Operator):
    bl_idname = "object.fvne_add_body_keys"
    bl_label = "Add Body Keys"
    bl_description = "Adds preset user expressions for the body."
    bl_options = {'REGISTER', 'UNDO'}
    
    def execute(self, context):
        add_shape_keys(context.object, BODY_PRESETS)
        return {'FINISHED'}

class FVNE_OT_add_full_keyset(bpy.types.Operator):
    bl_idname = "object.fvne_add_full_shape_keys"
    bl_label = "Add Full Set"
    bl_description = "Adds all preset user expressions."
    bl_options = {'REGISTER', 'UNDO'}
    
    def execute(self, context):
        add_shape_keys(context.object, FULL_PRESET_SET)
        return {'FINISHED'}

class FVNE_OT_sort_shape_keys(bpy.types.Operator):
    bl_idname = "object.fvne_sort_keys"
    bl_label = "Sort Shape Keys"
    bl_description = "Sort shape keys in preferred order"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        sort_shape_keys_smart(context.active_object)
        return {'FINISHED'}

class AddRSuffixOperator(bpy.types.Operator):
    bl_idname = "object.add_r_suffix"
    bl_label = "Add _R Suffix to Shape Keys"
    bl_description = "Adds the _R suffix to all present shape keys."
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        add_R_suffix_to_shape_keys()
        return {'FINISHED'}

class AddLSuffixOperator(bpy.types.Operator):
    bl_idname = "object.add_l_suffix"
    bl_label = "Add _L Suffix to Shape Keys"
    bl_description = "Adds the _L suffix to all present shape keys."
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        add_L_suffix_to_shape_keys()
        return {'FINISHED'}

# --- UI Panel ---

class FVNE_PT_expression_tools(bpy.types.Panel):
    bl_label = "FVNE Expression Manager"
    bl_idname = "OBJECT_PT_fvne_expression"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'FVNE'

    def draw(self, context):
        layout = self.layout
        scene = context.scene

        # Presets Section
        box = layout.box()
        row = box.row()
        row.prop(scene, "fvne_show_presets", icon="TRIA_DOWN" if scene.fvne_show_presets else "TRIA_RIGHT", emboss=False, text="Add Presets")
        if scene.fvne_show_presets:
            col = box.column(align=True)

            row = col.row(align=True)
            row.operator("object.fvne_add_eye_keys", text="Eyes")
            row.operator("object.fvne_add_brow_keys", text="Brows")

            row = col.row(align=True)
            row.operator("object.fvne_add_emote_keys", text="Mouth Emotes")
            row.operator("object.fvne_add_viseme_keys", text="Mouth Visemes")

            row = col.row(align=True)
            row.operator("object.fvne_add_face_keys", text="Face")
            row.operator("object.fvne_add_body_keys", text="Body")

            col.separator()
            col.operator("object.fvne_add_full_shape_keys", text="Add Full Set")
            
        # Smart Operations Section
        box = layout.box()
        row = box.row()
        row.prop(scene, "fvne_show_smart_ops", icon="TRIA_DOWN" if scene.fvne_show_smart_ops else "TRIA_RIGHT", emboss=False, text="Smart Operations")
        if scene.fvne_show_smart_ops:
            col = box.column(align=True)
            col.operator("object.fvne_split_symmetric_keys", text="Split All Main Keys")
            col.operator("object.fvne_split_active_symmetric_key", text="Split Only Active Key")
            col.separator()
            col.operator("object.fvne_smart_mirror_keys", text="Create R/L Counterparts")
            col.operator("object.fvne_combine_symmetric_keys", text="Combine _R &_L to Main Key")
            col.separator()
            col.operator("object.fvne_combine_selected_shape_keys", text="Combine Selected Keys")

        # Utilities Section
        box = layout.box()
        row = box.row()
        row.prop(scene, "fvne_show_utilities", icon="TRIA_DOWN" if scene.fvne_show_utilities else "TRIA_RIGHT", emboss=False, text="Utilities")
        if scene.fvne_show_utilities:
            col = box.column(align=True)
            col.operator("object.fvne_sort_keys", text="Sort Shape Keys")
            col.separator()
            
            col.operator("object.add_r_suffix", text="Add _R Suffix to Shape Keys")
            col.operator("object.add_l_suffix", text="Add _L Suffix to Shape Keys")
         
        # Vertex Mirror Section
        box = layout.box()
        row = box.row()
        row.prop(scene, "fvne_show_vertex_mirror", icon="TRIA_DOWN" if scene.fvne_show_vertex_mirror else "TRIA_RIGHT", emboss=False, text="Mirror Shape Key(Vertex)")
        if scene.fvne_show_vertex_mirror:
            col = box.column(align=True)
            col.operator("object.fvne_mirror_vertex_lr", text="Right → Left")
            col.operator("object.fvne_mirror_vertex_rl", text="Left → Right")


# --- Registration ---

classes = [
    FVNE_ShapeKeyItem,
    FVNE_UL_shape_key_list,
    FVNE_OT_combine_selected_shape_keys,
    FVNE_OT_smart_mirror_keys,
    FVNE_OT_split_symmetric_keys,
    FVNE_OT_split_active_symmetric_key,
    FVNE_OT_mirror_vertex_lr,
    FVNE_OT_mirror_vertex_rl,
    FVNE_OT_combine_symmetric_keys,
    FVNE_OT_sort_shape_keys,
    FVNE_OT_add_eye_keys,
    FVNE_OT_add_brow_keys,
    FVNE_OT_add_emote_keys,
    FVNE_OT_add_viseme_keys,
    FVNE_OT_add_face_keys,
    FVNE_OT_add_body_keys,
    FVNE_OT_add_full_keyset,
    AddLSuffixOperator,
    AddRSuffixOperator,
    FVNE_PT_expression_tools,
    
]

def register():
    for cls in classes:
        bpy.utils.register_class(cls)

    # Panel section toggles
    bpy.types.Scene.fvne_show_vertex_mirror = bpy.props.BoolProperty(
        name="Mirror Vertex", default=True)
    bpy.types.Scene.fvne_show_smart_ops = bpy.props.BoolProperty(
        name="Smart Operations", default=True)
    bpy.types.Scene.fvne_show_utilities = bpy.props.BoolProperty(
        name="Utilities", default=True)
    bpy.types.Scene.fvne_show_presets = bpy.props.BoolProperty(
        name="Add Presets", default=True)

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)

    del bpy.types.Scene.fvne_show_vertex_mirror
    del bpy.types.Scene.fvne_show_topology_mirror
    del bpy.types.Scene.fvne_show_smart_ops
    del bpy.types.Scene.fvne_show_utilities
    del bpy.types.Scene.fvne_show_presets

if __name__ == "__main__":
    register()

