# ZELLEREND MATERIAL SWITCHER — for My Shoe
# A minimal, focused add-on that ONLY switches materials.
# It assigns BOTH Shoe_diffuse_shader_<color> and Surface_diffuse_shader_<color>
# in one click. It does NOT create or modify objects or UVs.
#
# Install in Blender: Edit > Preferences > Add-ons > Install... (select this .py)
# Panel: 3D Viewport > N > ZELLEREND > "Material Switcher"

bl_info = {
    "name": "ZELLEREND Material Switcher",
    "author": "Darijan Kalauzovic",
    "version": (1, 0, 0),
    "blender": (3, 0, 0),
    "location": "View3D > Sidebar (N) > ZELLEREND",
    "description": "One-click color buttons to assign both shoe and surface materials already in the scene.",
    "category": "3D View",
}

import bpy
from bpy.types import Operator, Panel, AddonPreferences
from bpy.props import EnumProperty, StringProperty, BoolProperty

# -----------------------------------------------------------------------------
# Preferences (so you can tweak collection/name tokens without editing code)
# -----------------------------------------------------------------------------

class ZMSW_AddonPreferences(AddonPreferences):
    bl_idname = __name__

    shoe_name_token: StringProperty(
        name="Shoe name token",
        description="Objects whose names contain this (case-insensitive) will receive the Shoe_* material",
        default="shoe",
    )

    surfaces_collection_name: StringProperty(
        name="Surfaces collection name",
        description="Collection that holds surface meshes (case-insensitive match). If empty/missing, fallback to name token below",
        default="surfaces",
    )

    surface_name_token: StringProperty(
        name="Surface name token (fallback)",
        description="If collection above is missing, any mesh with this token in its name will receive the Surface_* material",
        default="surface",
    )

    prefer_collection: BoolProperty(
        name="Prefer collection for surfaces",
        description="If enabled, assign surfaces by collection first; if not found, use name token fallback",
        default=True,
    )

    def draw(self, context):
        lay = self.layout
        lay.label(text="Assignment Rules")
        col = lay.column(align=True)
        col.prop(self, "shoe_name_token")
        col.prop(self, "surfaces_collection_name")
        col.prop(self, "surface_name_token")
        col.prop(self, "prefer_collection")


def _prefs(context):
    return context.preferences.addons.get(__name__).preferences if __name__ in context.preferences.addons else None


# -----------------------------------------------------------------------------
# Utilities
# -----------------------------------------------------------------------------

def mat_by_name(name: str):
    return bpy.data.materials.get(name)

def find_collection_ci(name_ci: str):
    if not name_ci:
        return None
    low = name_ci.lower()
    for coll in bpy.data.collections:
        if coll.name.lower() == low:
            return coll
    return None

def iter_objects_in_collection(coll: bpy.types.Collection):
    """Yield objects in a collection, including children recursively (unique by name)."""
    seen = set()
    def _walk(c):
        for obj in c.objects:
            if obj.name not in seen:
                seen.add(obj.name)
                yield obj
        for child in c.children:
            yield from _walk(child)
    if coll:
        yield from _walk(coll)

def assign_material_to_shoes(target_mat: bpy.types.Material, name_token: str):
    if not target_mat:
        return 0
    token = (name_token or "shoe").lower()
    total = 0
    for obj in bpy.data.objects:
        if obj.type == 'MESH' and token in obj.name.lower():
            if getattr(obj.data, "materials", None) is not None:
                if len(obj.data.materials) == 0:
                    obj.data.materials.append(target_mat)
                else:
                    obj.data.materials[0] = target_mat
                total += 1
    return total

def assign_material_to_surfaces(target_mat: bpy.types.Material, coll_name: str, name_token: str, prefer_collection: bool = True):
    if not target_mat:
        return 0
    total = 0
    coll = find_collection_ci(coll_name) if prefer_collection else None

    if coll:
        for obj in iter_objects_in_collection(coll):
            if obj.type == "MESH" and getattr(obj.data, "materials", None) is not None:
                if len(obj.data.materials) == 0:
                    obj.data.materials.append(target_mat)
                else:
                    obj.data.materials[0] = target_mat
                total += 1
    else:
        token = (name_token or "surface").lower()
        for obj in bpy.data.objects:
            if obj.type == "MESH" and token in obj.name.lower() and getattr(obj.data, "materials", None) is not None:
                if len(obj.data.materials) == 0:
                    obj.data.materials.append(target_mat)
                else:
                    obj.data.materials[0] = target_mat
                total += 1
    return total


# -----------------------------------------------------------------------------
# Operator: Apply Color Preset
# -----------------------------------------------------------------------------

COLOR_ITEMS = [
    ('beige',  "Beige",  "Apply Beige shoe+surface materials"),
    ('black',  "Black",  "Apply Black shoe+surface materials"),
    ('blue',   "Blue",   "Apply Blue shoe+surface materials"),
    ('orange', "Orange", "Apply Orange shoe+surface materials"),
    ('red',    "Red",    "Apply Red shoe+surface materials"),
]

class ZMSW_OT_ApplyColorPreset(Operator):
    """Assign BOTH Shoe_diffuse_shader_{color} and Surface_diffuse_shader_{color}.
    Materials must already exist in the .blend file.
    """
    bl_idname = "zmswitch.apply_color_preset"
    bl_label = "Apply Color Preset (Shoe + Surface)"
    bl_options = {'REGISTER', 'UNDO'}

    color: EnumProperty(
        name="Color",
        items=COLOR_ITEMS,
        description="Which color preset to apply",
        default='blue'
    )

    def execute(self, context):
        prefs = _prefs(context)
        shoe_token = prefs.shoe_name_token if prefs else "shoe"
        surf_coll  = prefs.surfaces_collection_name if prefs else "surfaces"
        surf_token = prefs.surface_name_token if prefs else "surface"
        prefer_coll = prefs.prefer_collection if prefs else True

        color = self.color.lower()
        shoe_name = f"Shoe_diffuse_shader_{color}"
        surf_name = f"Surface_diffuse_shader_{color}"

        shoe_mat = mat_by_name(shoe_name)
        surf_mat = mat_by_name(surf_name)

        if not shoe_mat and not surf_mat:
            self.report({'ERROR'}, f"Materials not found: {shoe_name} and {surf_name}")
            return {'CANCELLED'}

        assigned_shoe = assign_material_to_shoes(shoe_mat, shoe_token) if shoe_mat else 0
        assigned_surf = assign_material_to_surfaces(surf_mat, surf_coll, surf_token, prefer_coll) if surf_mat else 0

        msg = []
        msg.append(f"Shoe → {shoe_name if shoe_mat else '[missing]'} : {assigned_shoe}")
        msg.append(f"Surface → {surf_name if surf_mat else '[missing]'} : {assigned_surf}")
        self.report({'INFO'}, " | ".join(msg))
        return {'FINISHED'}


# -----------------------------------------------------------------------------
# UI
# -----------------------------------------------------------------------------

class VIEW3D_PT_ZellerendMaterialSwitcher(Panel):
    bl_label = "Material Switcher"
    bl_idname = "VIEW3D_PT_zellerend_material_switcher"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "ZELLEREND"

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

        box = layout.box()
        box.label(text="Quick Color Apply (uses existing materials)", icon='BRUSH_DATA')

        row = box.row(align=True)
        for key, label, tip in COLOR_ITEMS[:3]:
            op = row.operator("zmswitch.apply_color_preset", text=label)
            op.color = key

        row2 = box.row(align=True)
        for key, label, tip in COLOR_ITEMS[3:]:
            op = row2.operator("zmswitch.apply_color_preset", text=label)
            op.color = key

        layout.separator()
        layout.label(text="Names assumed:", icon='INFO')
        col = layout.column(align=False)
        col.label(text="• Shoe materials:  Shoe_diffuse_shader_<color>")
        col.label(text="• Surface materials: Surface_diffuse_shader_<color>")
        col.label(text="(Change tokens in Add-on Preferences if needed)")


# -----------------------------------------------------------------------------
# Registration
# -----------------------------------------------------------------------------

classes = (
    ZMSW_AddonPreferences,
    ZMSW_OT_ApplyColorPreset,
    VIEW3D_PT_ZellerendMaterialSwitcher,
)

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

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

if __name__ == "__main__":
    register()
