"""
knot_animation/__init__.py
--------------------------
Blender add-on entry-point for the Pr3tz procedural animation system.

What this add-on does
---------------------
1. Registers a VIEW_3D N-panel ("Pr3tz") with:
   - A per-scene playlist of KnotItem entries, each specifying topology,
     geometry, animation rates, transition settings, and a material preset.
   - Operators for adding, removing, reordering, and randomly generating
     playlist entries.
   - Global playback controls (speed, phase, reactivity) that can be
     keyframed or driven for audio-reactive animation.

2. On every frame change (frame_change_post handler), procedurally rebuilds
   a single reused NURBS curve object ("AnimKnot") in-place using the
   parametric torus-knot equations — no bpy.ops, no edit-mode, no depsgraph
   races.

3. Interpolates geometry and cross-fades shader materials between adjacent
   playlist entries during configurable transition windows.

4. Provides a catalogue of 20 built-in shader presets (GLOSS_BLUE, NEON_GLOW,
   METALLIC, GLASS, HOLOGRAM, LAVA, IRIDESCENT, …) plus support for arbitrary
   project materials.

Module layout
-------------
types.py       — KnotConfig TypedDict (shared data contract)
constants.py   — pure-data defaults (KNOT_CONFIGS, camera/light positions, …)
compat.py      — one-shot compatibility patches (headless align_matrix fix)
materials.py   — 20 shader builders + material cache + blend system
geometry.py    — _make_torus_knot() (parametric NURBS, no bpy.context reads)
handler.py     — @persistent frame-change handler + easing + playlist timing
properties.py  — Blender PropertyGroups (KnotItem, KnotGlobalSettings, …)
operators.py   — KNOT_OT_* operators
ui.py          — KNOT_UL_* lists, draw_knot_properties(), KNOT_PT_Panel
scene_setup.py — setup_scene() (camera, light, world, render settings)

How to use
----------
Requires Blender 5.0 or later.

Option A – Blender Text Editor:
    Open knot_animation/__init__.py, click Run Script (Alt+P).

Option B – Command line:
    blender.exe --python knot_animation/__init__.py

Option C – Persistent add-on:
    Copy the knot_animation/ folder to Blender's addons directory and enable
    it from Edit → Preferences → Add-ons.  The panel appears under the
    "Pr3tz" tab in the 3-D viewport N-panel.
"""

if "bpy" in locals():
    import importlib
    importlib.reload(compat)
    importlib.reload(constants)
    importlib.reload(types)
    importlib.reload(materials)
    importlib.reload(geometry)
    importlib.reload(handler)
    importlib.reload(properties)
    importlib.reload(operators)
    importlib.reload(ui)
    importlib.reload(scene_setup)
    # bake_export may not exist from a prior session — import if needed
    if "bake_export" in locals():
        importlib.reload(bake_export)
    else:
        from . import bake_export
else:
    import bpy
    from . import compat, constants, types, materials, geometry, handler, properties, operators, ui, scene_setup, bake_export

from .compat      import apply_compat_patches
from .properties  import (
    KnotAllowedMaterial,
    KnotGlobalSettings,
    KnotItem,
    KnotGeneratorSettings,
)
from .operators   import (
    KNOT_OT_Add,
    KNOT_OT_Remove,
    KNOT_OT_Move,
    KNOT_OT_Populate,
    KNOT_OT_BakePreview,
    KNOT_OT_SyncGeneratorMaterials,
    KNOT_OT_FitTimeline,
    KNOT_OT_FitPlaylist,
    KNOT_OT_GenerateRandom,
)
from .bake_export import KNOT_OT_BakeExport
from .ui          import (
    KNOT_UL_List,
    KNOT_UL_AllowedMaterialsList,
    KNOT_PT_Panel,
)
from .geometry    import _remove_existing_knot
from .handler     import knot_frame_handler
from .scene_setup import setup_scene
from .constants   import KNOT_MAT_NAME, SHADER_BLEND_MAT_NAME

# ---------------------------------------------------------------------------
# Add-on metadata
# ---------------------------------------------------------------------------

bl_info = {
    "name":        "Pr3tz",
    "author":      "knot_animation project",
    "version":     (2, 0, 0),
    "blender":     (5, 0, 0),
    "location":    "View3D > Sidebar > Pr3tz",
    "description": "Procedural torus-knot animation with playlist, transitions, and 20 shader presets",
    "category":    "Animation",
}

# ---------------------------------------------------------------------------
# Registered Blender classes (order matters for PropertyGroup dependencies)
# ---------------------------------------------------------------------------

classes = (
    KnotAllowedMaterial,
    KnotGlobalSettings,
    KnotItem,
    KnotGeneratorSettings,
    KNOT_UL_List,
    KNOT_UL_AllowedMaterialsList,
    KNOT_OT_SyncGeneratorMaterials,
    KNOT_OT_Add,
    KNOT_OT_Remove,
    KNOT_OT_Move,
    KNOT_OT_Populate,
    KNOT_OT_BakePreview,
    KNOT_OT_FitTimeline,
    KNOT_OT_FitPlaylist,
    KNOT_OT_GenerateRandom,
    KNOT_OT_BakeExport,
    KNOT_PT_Panel,
)


# ---------------------------------------------------------------------------
# Lifecycle
# ---------------------------------------------------------------------------

def register() -> None:
    apply_compat_patches()

    for cls in classes:
        bpy.utils.register_class(cls)

    bpy.types.Scene.knot_list       = bpy.props.CollectionProperty(type=KnotItem)
    bpy.types.Scene.knot_list_index = bpy.props.IntProperty(name="Index", default=0)
    bpy.types.Scene.knot_generator  = bpy.props.PointerProperty(type=KnotGeneratorSettings)
    bpy.types.Scene.knot_globals    = bpy.props.PointerProperty(type=KnotGlobalSettings)

    # Register to frame_change_post so the handler fires *after* Blender has
    # evaluated the new frame, preventing the freshly-built mesh from being
    # overwritten by the depsgraph update.
    handlers = bpy.app.handlers.frame_change_post
    for h in list(handlers):
        if getattr(h, "__name__", "") == knot_frame_handler.__name__:
            handlers.remove(h)
    handlers.append(knot_frame_handler)

    print("[KnotScript] Handler and UI registered.")


def unregister() -> None:
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)

    del bpy.types.Scene.knot_list
    del bpy.types.Scene.knot_list_index
    del bpy.types.Scene.knot_generator
    del bpy.types.Scene.knot_globals

    handlers = bpy.app.handlers.frame_change_post
    for h in list(handlers):
        if getattr(h, "__name__", "") == knot_frame_handler.__name__:
            handlers.remove(h)

    _remove_existing_knot()

    # Purge all cached shader and blend materials on unregister to avoid
    # data-block buildup on script reload.  Only remove zero-user materials.
    for mat in list(bpy.data.materials):
        if (mat.name.startswith("KnotShader_")
                or mat.name.startswith("KnotBlend_")
                or mat.name.startswith("KnotItem_Preset_")
                or mat.name in (KNOT_MAT_NAME, SHADER_BLEND_MAT_NAME)):
            if mat.users == 0:
                bpy.data.materials.remove(mat)

    print("[KnotScript] Handler and UI unregistered.")


# ---------------------------------------------------------------------------
# Entry point (run as script or via --python flag)
# ---------------------------------------------------------------------------

if __name__ == "__main__":
    register()
    setup_scene()

    # Generate the knot for the current frame immediately so the viewport is
    # not empty when the script first runs.
    knot_frame_handler(bpy.context.scene)
    print("[KnotScript] Ready. Start scrubbing the timeline!")
    print("             Press Space to play, or render with Ctrl+F12.")
