import bpy
import mathutils

ACTION_PROPS = ["name", "action", "action_text", "height", "radius", "tags"]
FURNITURE_PROPS = ["name", "furniture_tags"]

test_tracking = {"lib_name": None}

class TEST_OT_LinkAndPlay(bpy.types.Operator):
    bl_idname = "test.link_and_play"
    bl_label = "Link and Play Sitting"

    filepath: bpy.props.StringProperty()
    rig_name: bpy.props.StringProperty()
    action_name: bpy.props.StringProperty()

    def execute(self, context):
        target = context.active_object
        path = bpy.path.abspath(self.filepath)
        mesh_name = "Body"
        with bpy.data.libraries.load(path, link=True) as (data_from, data_to):
            # We request all three components
            data_to.objects = [n for n in [self.rig_name, mesh_name] if n in data_from.objects]
            data_to.actions = [self.action_name] if self.action_name in data_from.actions else []

        # 2. Add to scene and create Override
        rig_obj = None
        for obj in data_to.objects:
            offset = mathutils.Matrix.Translation((0, -0.1, 0))
            bpy.context.collection.objects.link(obj)
            obj.matrix_world = target.matrix_world @ offset
            if obj.type == 'ARMATURE':
                rig_obj = obj
            if obj.library:
                test_tracking["lib_name"] = obj.library.name


        if rig_obj:
            if not rig_obj.animation_data:
                rig_obj.animation_data_create()
            if data_to.actions:
                rig_obj.animation_data.action = data_to.actions[0]
        bpy.ops.screen.animation_play()
        return {'FINISHED'}

class TEST_OT_Cleanup(bpy.types.Operator):
    bl_idname = "test.cleanup"
    bl_label = "Stop & Cleanup"

    def execute(self, context):
        if context.screen.is_animation_playing:
            bpy.ops.screen.animation_cancel(restore_frame=True)

        lib_name = test_tracking.get("lib_name")
        if lib_name and lib_name in bpy.data.libraries:
            lib = bpy.data.libraries[lib_name]
            bpy.data.batch_remove(ids=(lib,))
            test_tracking["lib_name"] = None

        # Deep purge orphans (meshes, armatures, etc)
        bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
        return {'FINISHED'}

class TEST_PT_ObjectPanel(bpy.types.Panel):
    """Only appears in Object Properties when a 'position-' Empty is selected"""
    bl_label = "Character Placement Test"
    bl_idname = "TEST_PT_object_panel"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    @classmethod
    def poll(cls, context):
        obj = context.active_object
        return obj and obj.type == 'EMPTY' and obj.name.startswith("position-")

    def draw(self, context):
        layout = self.layout
        layout.label(text="Test Controls:", icon='OUTLINER_OB_EMPTY')

        # Link button with preset arguments
        row = layout.row(align=True)
        op = row.operator("test.link_and_play", text="Link & Sit", icon='PLAY')
        op.filepath = "//../../characters/edited-normal-male.blend" # SET YOUR FILEPATH
        op.rig_name = "male"           # SET YOUR RIG NAME
        op.action_name = "sitting-chair"              # SET YOUR ACTION NAME

        # Cleanup button
        layout.operator("test.cleanup", text="Stop & Clear", icon='TRASH')


class OBJECT_OT_CreateActionProps(bpy.types.Operator):
    """Initializes custom properties on the selected action- empty"""
    bl_idname = "object.create_action_props"
    bl_label = "Create Action"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        obj = context.object
        for p in ACTION_PROPS:
            if p not in obj:
                if p in ["height"]:
                    obj[p] = 1.0
                elif p in ["radius"]:
                    obj[p] = 0.5
                elif p == "name":
                    obj[p] = obj.name.replace("action-","")
                elif p == "action_text":
                    if "action" in obj:
                        obj[p] = obj["action"].capitalize()
                    else:
                        obj[p] = ""
                else:
                    obj[p] = ""  # Initialize as empty text string
        return {'FINISHED'}

class OBJECT_OT_CreateFurnitureProps(bpy.types.Operator):
    """Initializes custom properties on the selected furniture- empty"""
    bl_idname = "object.create_furniture_props"
    bl_label = "Create Furniture"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        obj = context.object
        for p in FURNITURE_PROPS:
            if p not in obj:
                if p == "name":
                    obj[p] = obj.name.replace("furniture-","")
                elif p == "furniture_tags":
                        obj[p] = ""
                        otags = []
                        if obj.name.find("toilet") >= 0:
                            otags.append("wc")
                            otags.append("bathroom")
                            otags.append("toilet")
                        elif obj.name.find("bed") >= 0:
                            otags.append("bed")
                            otags.append("bedroom")
                        obj[p] = ", ".join(otags)
                else:
                    obj[p] = ""  # Initialize as empty text string
        return {'FINISHED'}

class OBJECT_OT_CreateChildEmpties(bpy.types.Operator):
    """Creates two child empties"""
    bl_idname = "object.create_child_empties"
    bl_label = "Create Child Nodes"
    bl_options = {'REGISTER', 'UNDO'}

    target_val: bpy.props.StringProperty()
    def execute(self, context):
        parent = context.object
        # Define offsets and their corresponding labels for the name
        configs = []
        configs_enter = [((0, 0.5, -parent.location.z), "enter")]
        configs_exit = [((0, -0.5, -parent.location.z), "exit")]
        if self.target_val == "enter":
            configs = configs_enter
        elif self.target_val == "exit":
            configs = configs_exit
        elif self.target_val == "all":
            configs = configs_enter + configs_exit
        
        for offset, label in configs:
            # Create new Empty with the "position-" prefix
            child_name = f"position-{label}.000"
            child = bpy.data.objects.new(child_name, None)
            
            # Link to the current collection
            context.collection.objects.link(child)
            
            # Set parent and local offset
            child.parent = parent
            child.location = offset
            child["name"] = label
            
        return {'FINISHED'}

class OBJECT_OT_CreateChildActionNode(bpy.types.Operator):
    """Creates child action node"""
    bl_idname = "object.create_child_action_node"
    bl_label = "Create Child Action Node"
    bl_options = {'REGISTER', 'UNDO'}
    target_val: bpy.props.StringProperty()

    def execute(self, context):
        parent = context.object
        # Define offsets and their corresponding labels for the name
        configs = [((0, 0.1, -parent.location.z + 0.5), self.target_val)]
        
        for offset, label in configs:
            # Create new Empty with the "position-" prefix
            child_name = f"action-{label}.000"
            child = bpy.data.objects.new(child_name, None)
            
            # Link to the current collection
            context.collection.objects.link(child)
            
            # Set parent and local offset
            child.parent = parent
            child.location = offset
            child["name"] = label
            child["action"] = label
            for p in ACTION_PROPS:
                if p not in child:
                    if p in ["height"]:
                        child[p] = 1.0
                    elif p in ["radius"]:
                        child[p] = 0.5
                    elif p == "name":
                        child[p] = child.name.replace("action-","")
                    elif p == "action_text":
                        if "action" in child:
                            child[p] = child["action"].capitalize()
                        else:
                            child[p] = ""
                    else:
                        child[p] = ""  # Initialize as empty text string
            
        return {'FINISHED'}

class OBJECT_OT_DestroyChildByProp(bpy.types.Operator):
    """Deletes children starting with 'position-' where custom prop 'name' matches value"""
    bl_idname = "object.destroy_child_by_prop"
    bl_label = "Destroy Child"
    bl_options = {'REGISTER', 'UNDO'}

    target_val: bpy.props.StringProperty()

    def execute(self, context):
        parent = context.object
        # Collect children to delete to avoid modifying list during iteration
        to_delete = [
            child for child in parent.children
            if child.name.startswith("position-") and child.get("name") == self.target_val
        ]

        for child in to_delete:
            bpy.data.objects.remove(child, do_unlink=True)

        return {'FINISHED'}

class OBJECT_PT_ActionEmptyPanel(bpy.types.Panel):
    """Custom GUI panel for 'action-' prefixed Empties"""
    bl_label = "Action Controls"
    bl_idname = "OBJECT_PT_action_empty_panel"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    @classmethod
    def poll(cls, context):
        obj = context.object
        return (obj is not None and
                obj.type == 'EMPTY' and
                obj.name.startswith("action-"))

    def draw(self, context):
        layout = self.layout
        obj = context.object

        # Check if all required properties already exist
        props_exist = all(p in obj for p in ACTION_PROPS)

        if not props_exist:
            # Show ONLY the button if properties are missing
            layout.operator("object.create_action_props", icon='PLUS')
        else:
            # Show the text fields if properties exist
            box = layout.box()
            box.label(text="Action Settings", icon='PROPERTIES')
            for p in ACTION_PROPS:
                box.prop(obj, f'["{p}"]', text=p.replace("_", " ").title())
        layout.separator()
        have_enter = False
        have_exit = False
        for c in obj.children:
            if c.name.startswith("position-") and "name" in c and c["name"] == "enter":
                have_enter = True
                break
        for c in obj.children:
            if c.name.startswith("position-") and "name" in c and c["name"] == "exit":
                have_exit = True
                break
        if not have_enter:
            op_enter = layout.operator("object.create_child_empties", icon='EMPTY_DATA', text="Add Enter Position")
            op_enter.target_val = "enter"
        else:
            op_enter = layout.operator("object.destroy_child_by_prop", text="Destroy 'Enter' Node", icon='X')
            op_enter.target_val = "enter"
        if not have_exit:
            op_exit = layout.operator("object.create_child_empties", icon='EMPTY_DATA', text="Add Exit Position")
            op_exit.target_val = "exit"
        else:
            op_exit = layout.operator("object.destroy_child_by_prop", text="Destroy 'Exit' Node", icon='X')
            op_exit.target_val = "exit"


class OBJECT_PT_FurnitureEmptyPanel(bpy.types.Panel):
    """Custom GUI panel for 'furniture-' prefixed Empties"""
    bl_label = "Furniture Controls"
    bl_idname = "OBJECT_PT_action_furniture_panel"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    @classmethod
    def poll(cls, context):
        obj = context.object
        return (obj is not None and
                obj.type == 'EMPTY' and
                obj.name.startswith("furniture-"))

    def draw(self, context):
        layout = self.layout
        obj = context.object

        # Check if all required properties already exist
        props_exist = all(p in obj for p in FURNITURE_PROPS)

        if not props_exist:
            # Show ONLY the button if properties are missing
            layout.operator("object.create_furniture_props", icon='PLUS')
        else:
            # Show the text fields if properties exist
            box = layout.box()
            box.label(text="Furnitore Settings", icon='PROPERTIES')
            for p in FURNITURE_PROPS:
                box.prop(obj, f'["{p}"]', text=p.replace("_", " ").title())
            op_use = layout.operator("object.create_child_action_node", icon='EMPTY_DATA', text="Add Use Action Node")
            op_use.target_val = "use"
            op_use = layout.operator("object.create_child_action_node", icon='EMPTY_DATA', text="Add Sit Action Node")
            op_use.target_val = "sit"

def register():
    bpy.utils.register_class(OBJECT_OT_CreateActionProps)
    bpy.utils.register_class(OBJECT_OT_CreateFurnitureProps)
    bpy.utils.register_class(OBJECT_PT_ActionEmptyPanel)
    bpy.utils.register_class(OBJECT_OT_CreateChildEmpties)
    bpy.utils.register_class(OBJECT_OT_DestroyChildByProp)
    bpy.utils.register_class(OBJECT_PT_FurnitureEmptyPanel)
    bpy.utils.register_class(OBJECT_OT_CreateChildActionNode)
    bpy.utils.register_class(TEST_OT_LinkAndPlay)
    bpy.utils.register_class(TEST_OT_Cleanup)
    bpy.utils.register_class(TEST_PT_ObjectPanel)


def unregister():
    bpy.utils.register_class(OBJECT_OT_CreateActionProps)
    bpy.utils.register_class(OBJECT_OT_CreateFurnitureProps)
    bpy.utils.unregister_class(OBJECT_PT_ActionEmptyPanel)
    bpy.utils.unregister_class(OBJECT_OT_CreateChildEmpties)
    bpy.utils.unregister_class(OBJECT_OT_DestroyChildByProp)
    bpy.utils.unregister_class(OBJECT_PT_FurnitureEmptyPanel)
    bpy.utils.unregister_class(OBJECT_OT_CreateChildActionNode)
    bpy.utils.unregister_class(TEST_OT_LinkAndPlay)
    bpy.utils.unregister_class(TEST_OT_Cleanup)
    bpy.utils.unregister_class(TEST_PT_ObjectPanel)

if __name__ == "__main__":
    register()

