bl_info = {
    "name": "Ruffy Wonder Level Tools",
    "author": "Ruffy Wonder Team",
    "version": (0, 1, 1),
    "blender": (5, 0, 0),
    "location": "3D Viewport → Sidebar → Ruffy Wonder",
    "description": "Prefab preview, level export, and custom property helpers",
    "category": "Game",
}

import bpy
from pathlib import Path

PREVIEW_MARKER = "_ruffy_preview"


def level_dir():
    if bpy.data.filepath:
        return Path(bpy.data.filepath).parent
    return None


def is_preview(obj):
    return obj.get(PREVIEW_MARKER, False)


def clear_previews():
    for obj in list(bpy.data.objects):
        if is_preview(obj):
            bpy.data.objects.remove(obj, do_unlink=True)


def find_prefab_empties():
    result = []
    for obj in bpy.data.objects:
        if obj.type != "EMPTY":
            continue
        prefab = obj.get("prefab", None)
        if prefab and isinstance(prefab, str) and prefab.strip():
            result.append(obj)
    return result


def import_glb(filepath):
    before = set(bpy.data.objects)
    bpy.ops.import_scene.gltf(filepath=str(filepath))
    after = set(bpy.data.objects)
    return list(after - before)


def refresh_all(context=None):
    clear_previews()
    empties = find_prefab_empties()
    base = level_dir()

    if base is None:
        msg = "Save the .blend file first (File → Save)"
        print(f"[Ruffy] {msg}")
        if context:
            context.window_manager.popup_menu(
                lambda self, ctx: self.layout.label(text=msg),
                title="Ruffy Wonder",
            )
        return 0, 0, [msg]

    loaded = 0
    missing = 0
    errors = []

    for empty in empties:
        prefab = empty["prefab"]
        glb = base / prefab
        if not glb.exists():
            missing += 1
            errors.append(f"Not found: {prefab} (looked in {base})")
            continue

        try:
            imported = import_glb(glb)
        except Exception as e:
            errors.append(f"{prefab}: {e}")
            continue

        for obj in imported:
            obj.parent = empty
            obj.matrix_world = empty.matrix_world
            obj[PREVIEW_MARKER] = True
            obj.hide_select = True
        loaded += 1

    msg = f"Loaded {loaded} prefabs"
    if missing:
        msg += f", {missing} missing"
    if errors:
        msg += "\n" + "\n".join(errors)
        if context:
            context.window_manager.popup_menu(
                lambda self, ctx: self.layout.label(text=msg),
                title="Ruffy Wonder",
            )

    print(f"[Ruffy] {msg}")
    return loaded, missing, errors


class RUFFY_OT_refresh_prefabs(bpy.types.Operator):
    bl_idname = "ruffy.refresh_prefabs"
    bl_label = "Refresh Prefab Previews"
    bl_description = "Load GLB previews for all empties with prefab property"
    bl_options = {"REGISTER", "UNDO"}

    def execute(self, context):
        refresh_all(context)
        return {"FINISHED"}


class RUFFY_OT_export_level(bpy.types.Operator):
    bl_idname = "ruffy.export_level"
    bl_label = "Export Level"
    bl_description = "Export GLB with custom properties, hiding preview objects"
    bl_options = {"REGISTER", "UNDO"}

    filepath: bpy.props.StringProperty(subtype="FILE_PATH")  # noqa: F722,F821

    def invoke(self, context, event):
        if bpy.data.filepath:
            default = str(Path(bpy.data.filepath).with_suffix(".glb"))
        else:
            default = "//level.glb"
        self.filepath = default
        context.window_manager.fileselect_add(self)
        return {"RUNNING_MODAL"}

    def execute(self, context):
        refresh_all(context)
        filepath = self.filepath

        preview_objs = [o for o in bpy.data.objects if is_preview(o)]

        for obj in preview_objs:
            obj.hide_render = True
            obj.hide_viewport = True

        try:
            bpy.ops.export_scene.gltf(
                filepath=filepath,
                export_format="GLB",
                export_apply=True,
                export_extras=True,
                use_selection=False,
            )
        finally:
            for obj in preview_objs:
                obj.hide_render = False
                obj.hide_viewport = False

        self.report({"INFO"}, f"Exported {filepath}")
        return {"FINISHED"}


class RUFFY_OT_add_prefab(bpy.types.Operator):
    bl_idname = "ruffy.add_prefab"
    bl_label = "Add Prefab Empty"
    bl_description = "Add an empty with prefab property at 3D cursor"
    bl_options = {"REGISTER", "UNDO"}

    prefab_path: bpy.props.StringProperty(  # noqa: F722,F821
        name="Prefab",
        description="GLB file path relative to level directory",
        default="star.glb",
    )

    def execute(self, context):
        bpy.ops.object.empty_add(
            type="PLAIN_AXES", location=context.scene.cursor.location
        )
        empty = context.active_object
        empty["prefab"] = self.prefab_path
        empty.name = f"Prefab_{self.prefab_path.replace('.glb', '')}"
        return {"FINISHED"}

    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self)


class RUFFY_PT_panel(bpy.types.Panel):
    bl_label = "Ruffy Wonder"
    bl_idname = "RUFFY_PT_panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Ruffy Wonder"

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

        empties = find_prefab_empties()
        preview_count = sum(1 for o in bpy.data.objects if is_preview(o))
        base = level_dir()

        box = layout.box()
        if base is None:
            box.label(text="Save .blend first!", icon="ERROR")
        else:
            box.label(text=f"Dir: {base}")
        box.label(text=f"Prefabs: {len(empties)}  |  Previews: {preview_count}")

        row = box.row()
        row.operator("ruffy.add_prefab", text="Add Prefab", icon="ADD")
        row.operator("ruffy.refresh_prefabs", text="Refresh", icon="FILE_REFRESH")

        layout.separator()
        layout.operator("ruffy.export_level", text="Export Level", icon="EXPORT")

        if empties:
            layout.separator()
            layout.label(text="Scene Prefabs:")
            for empty in empties:
                row = layout.row()
                prefab = empty.get("prefab", "?")
                row.label(text=f"  {empty.name} → {prefab}")
                op = row.operator(
                    "ruffy.remove_prefab", text="", icon="X", emboss=False
                )
                op.empty_name = empty.name


class RUFFY_OT_remove_prefab(bpy.types.Operator):
    bl_idname = "ruffy.remove_prefab"
    bl_label = "Remove Prefab"
    bl_options = {"REGISTER", "UNDO"}

    empty_name: bpy.props.StringProperty()  # noqa: F722,F821

    def execute(self, context):
        obj = bpy.data.objects.get(self.empty_name)
        if obj:
            for child in list(obj.children):
                if is_preview(child):
                    bpy.data.objects.remove(child, do_unlink=True)
            del obj["prefab"]
        return {"FINISHED"}


def deferred_refresh():
    refresh_all()
    return None


@bpy.app.handlers.persistent
def load_post_handler(dummy):
    bpy.app.timers.register(deferred_refresh, first_interval=0.5)


def register():
    bpy.utils.register_class(RUFFY_OT_refresh_prefabs)
    bpy.utils.register_class(RUFFY_OT_export_level)
    bpy.utils.register_class(RUFFY_OT_add_prefab)
    bpy.utils.register_class(RUFFY_OT_remove_prefab)
    bpy.utils.register_class(RUFFY_PT_panel)
    bpy.app.handlers.load_post.append(load_post_handler)


def unregister():
    bpy.app.handlers.load_post.remove(load_post_handler)
    bpy.utils.unregister_class(RUFFY_PT_panel)
    bpy.utils.unregister_class(RUFFY_OT_remove_prefab)
    bpy.utils.unregister_class(RUFFY_OT_add_prefab)
    bpy.utils.unregister_class(RUFFY_OT_export_level)
    bpy.utils.unregister_class(RUFFY_OT_refresh_prefabs)


if __name__ == "__main__":
    register()
