import bpy
import re
import os

def link_bsdf_materials():
    """Link all materials from the BSDF library file"""
    
    library_path = r"R:\Creative\artsy\maya\0 ProjectStructure\1 BlenderAssets\Amazon\MATERIALS_BSDF_pallette_v1.0.blend"
    
    if not os.path.exists(library_path):
        print(f"Warning: Library file not found at {library_path}")
        return []
    
    print(f"Linking materials from: {library_path}")
    
    # Get list of materials before linking
    materials_before = set(bpy.data.materials.keys())
    
    # Link all materials from the library file
    with bpy.data.libraries.load(library_path, link=True) as (data_from, data_to):
        # Link all materials
        data_to.materials = data_from.materials
    
    # Get list of newly linked materials
    materials_after = set(bpy.data.materials.keys())
    newly_linked = materials_after - materials_before
    
    print(f"Linked {len(newly_linked)} materials from library")
    for mat_name in sorted(newly_linked):
        print(f"  - {mat_name}")
    
    return list(newly_linked)

def remap_appended_to_linked():
    """Remap any appended BSDF materials to their linked counterparts"""
    
    print("\nChecking for appended BSDF materials to remap to linked versions...")
    
    materials = bpy.data.materials
    remapping_count = 0
    
    # Group materials by base name (without library suffix)
    material_groups = {}
    
    for mat in materials:
        # Check if it's a BSDF material (from any source)
        if mat.name.startswith("BSDF_") or "BSDF_" in mat.name:
            # Extract base name (remove library reference if present)
            base_name = mat.name.split(".blend")[0] if ".blend" in mat.name else mat.name
            base_name = base_name.split(".")[0] if "." in base_name else base_name
            
            if base_name not in material_groups:
                material_groups[base_name] = []
            material_groups[base_name].append(mat)
    
    # For each group, prefer linked materials over appended ones
    for base_name, mats in material_groups.items():
        if len(mats) > 1:
            # Sort to prefer linked materials (they have library references)
            linked_mats = [m for m in mats if m.library is not None]
            appended_mats = [m for m in mats if m.library is None]
            
            if linked_mats and appended_mats:
                # Use the first linked material as target
                target_material = linked_mats[0]
                
                # Remap all appended materials to the linked one
                for appended_mat in appended_mats:
                    if appended_mat.users > 0:
                        print(f"Remapping appended {appended_mat.name} ({appended_mat.users} users) to linked {target_material.name}")
                        appended_mat.user_remap(target_material)
                        remapping_count += 1
                    
                    # Remove the unused appended material
                    if appended_mat.users == 0:
                        print(f"Removing unused appended material: {appended_mat.name}")
                        bpy.data.materials.remove(appended_mat)
    
    # Also check for any BSDF materials that might be from old paths or different files
    # and try to find matching linked materials
    for mat in materials:
        if mat.library is None and (mat.name.startswith("BSDF_") or "BSDF_" in mat.name):
            # This is an appended BSDF material - look for a linked version
            base_name = mat.name.split(".blend")[0] if ".blend" in mat.name else mat.name
            base_name = base_name.split(".")[0] if "." in base_name else base_name
            
            # Look for any linked material with the same base name
            for linked_mat in materials:
                if (linked_mat.library is not None and 
                    linked_mat.name.startswith("BSDF_") and
                    (linked_mat.name == base_name or 
                     linked_mat.name.startswith(base_name + ".") or
                     linked_mat.name == mat.name)):
                    
                    if mat.users > 0:
                        print(f"Remapping old BSDF {mat.name} ({mat.users} users) to linked {linked_mat.name}")
                        mat.user_remap(linked_mat)
                        remapping_count += 1
                    
                    # Remove the unused material
                    if mat.users == 0:
                        print(f"Removing unused old BSDF material: {mat.name}")
                        bpy.data.materials.remove(mat)
                    break
    
    print(f"Remapped {remapping_count} appended/old BSDF materials to linked versions")
    return remapping_count

def remap_missing_datablocks():
    """Remap materials that have missing/broken library links"""
    
    print("\nChecking for missing datablocks to remap...")
    
    materials = bpy.data.materials
    remapping_count = 0
    
    # Find materials with missing library links
    missing_materials = []
    for mat in materials:
        if mat.library is not None and mat.library.filepath and not os.path.exists(bpy.path.abspath(mat.library.filepath)):
            missing_materials.append(mat)
            print(f"Found missing datablock: {mat.name} (from {mat.library.filepath})")
    
    if not missing_materials:
        print("No missing datablocks found.")
        return 0
    
    # For each missing material, try to find a replacement
    for missing_mat in missing_materials:
        base_name = missing_mat.name.split(".blend")[0] if ".blend" in missing_mat.name else missing_mat.name
        base_name = base_name.split(".")[0] if "." in base_name else base_name
        
        # Look for a replacement material
        replacement_found = False
        
        # First, try to find a linked material with the same name from the current library
        for mat in materials:
            if (mat.library is not None and 
                mat.library.filepath and 
                os.path.exists(bpy.path.abspath(mat.library.filepath)) and
                mat.name == missing_mat.name):
                
                if missing_mat.users > 0:
                    print(f"Remapping missing {missing_mat.name} ({missing_mat.users} users) to valid linked {mat.name}")
                    missing_mat.user_remap(mat)
                    remapping_count += 1
                replacement_found = True
                break
        
        # If no exact match, try to find a BSDF material with similar name
        if not replacement_found and (missing_mat.name.startswith("BSDF_") or "BSDF_" in missing_mat.name):
            for mat in materials:
                if (mat.library is not None and 
                    mat.library.filepath and 
                    os.path.exists(bpy.path.abspath(mat.library.filepath)) and
                    mat.name.startswith("BSDF_") and
                    (mat.name == base_name or 
                     mat.name.startswith(base_name + ".") or
                     base_name in mat.name)):
                    
                    if missing_mat.users > 0:
                        print(f"Remapping missing BSDF {missing_mat.name} ({missing_mat.users} users) to valid linked {mat.name}")
                        missing_mat.user_remap(mat)
                        remapping_count += 1
                    replacement_found = True
                    break
        
        # If still no replacement, try to find any valid linked material with the same base name
        if not replacement_found:
            for mat in materials:
                if (mat.library is not None and 
                    mat.library.filepath and 
                    os.path.exists(bpy.path.abspath(mat.library.filepath)) and
                    mat.name == base_name):
                    
                    if missing_mat.users > 0:
                        print(f"Remapping missing {missing_mat.name} ({missing_mat.users} users) to valid linked {mat.name}")
                        missing_mat.user_remap(mat)
                        remapping_count += 1
                    replacement_found = True
                    break
        
        if not replacement_found:
            print(f"Warning: No replacement found for missing material {missing_mat.name}")
    
    print(f"Remapped {remapping_count} missing datablocks to valid linked materials")
    return remapping_count

def replace_cel_materials():
    """Replace all CEL materials with their BSDF counterparts using Blender's user remapping"""
    
    # First, link BSDF materials from library
    linked_materials = link_bsdf_materials()
    
    # Then, remap any missing datablocks
    missing_remaps = remap_missing_datablocks()
    
    # Then, remap any appended BSDF materials to linked versions
    appended_remaps = remap_appended_to_linked()
    
    print(f"\n=== STARTING MATERIAL REPLACEMENT ===")
    
    # Custom material mappings (source -> target)
    custom_mappings = {
        "bag BLACK (squid ink)": "BSDF_black_SQUID-INK",
        "bag WHITE": "BSDF_WHITE",
        "Wheel-White": "BSDF_WHITE",
        "Bag Colors": "BSDF_Bag Colors",
        "cardboard": "Package_Cardboard",
        "blue (triton)": "BSDF_blue-2_TRITON",
        "gray (snow)": "BSDF_gray-6_SNOW",
        "gray (storm)": "BSDF_gray-2_STORM",
        "gray (summit)": "BSDF_gray-5_SUMMIT",
        "light blue (prime)": "BSDF_blue-4_PRIME",
        "yellow (summer)": "BSDF_orange-5_SUMMER",
        "Accessory_CEL_gray-6_SNOW": "BSDF_gray-6_SNOW",
        "Accessory_CEL_SquidInk": "BSDF_black_SQUID-INK",
        "FingerScanner": "BSDF_black_SQUID-INK",
        "cel BLACK (squid ink)": "BSDF_black_SQUID-INK",
        "cel WHITE": "BSDF_WHITE",
        "gray (stone)": "BSDF_gray-3_STONE",
        "green (oxygen)": "BSDF_green-3_OXYGEN",
        "orange (smile)": "BSDF_orange-3_SMILE",
        "orange (blaze)": "BSDF_orange-1_BLAZE"
    }
    
    # Get all materials in the scene
    materials = bpy.data.materials
    
    # Dictionary to store source -> target material mapping
    material_mapping = {}
    
    # Replace all CEL materials with their BSDF counterparts, ignoring numeric suffixes
    cel_pattern = re.compile(r"^(CEL_.+?)(\.\d{3})?$")
    bsdf_pattern = re.compile(r"^(BSDF_.+?)(\.\d{3})?$")
    
    # Build a mapping from base BSDF name to BSDF material (without suffix)
    bsdf_base_map = {bsdf_pattern.match(mat.name).group(1): mat for mat in materials if bsdf_pattern.match(mat.name)}
    
    # Build a mapping from exact material names to materials
    exact_material_map = {mat.name: mat for mat in materials}

    # Helpers to normalize names (case-insensitive, ignore numeric suffixes and library suffix)
    def normalize_base(name):
        base_name = name.split(".blend")[0] if ".blend" in name else name
        match = re.match(r"^(.*?)(\.\d{3})?$", base_name)
        base_name = match.group(1) if match else base_name
        return base_name.strip().casefold()

    # Map normalized base name -> list of materials
    materials_by_base = {}
    for mat in materials:
        base = normalize_base(mat.name)
        materials_by_base.setdefault(base, []).append(mat)

    # Normalize BSDF base name map for robust target lookups
    bsdf_base_map_normalized = {normalize_base(base): mat for base, mat in bsdf_base_map.items()}
    
    replacements_made = 0
    missing_targets = []
    
    # Process custom mappings first (case/suffix-insensitive)
    for source_name, target_name in custom_mappings.items():
        # Gather source candidates by exact name or base name (handles .001 etc.)
        if source_name in exact_material_map:
            source_candidates = [exact_material_map[source_name]]
        else:
            source_candidates = materials_by_base.get(normalize_base(source_name), [])

        if not source_candidates:
            print(f"Warning: Source material '{source_name}' not found")
            continue

        # Resolve target BSDF by exact or base name
        target_material = exact_material_map.get(target_name)
        if target_material is None:
            target_material = bsdf_base_map_normalized.get(normalize_base(target_name))
        if target_material is None:
            # Final fallback: any BSDF whose base equals target base
            target_base_norm = normalize_base(target_name)
            for mat in materials:
                m = bsdf_pattern.match(mat.name)
                if m and normalize_base(m.group(1)) == target_base_norm:
                    target_material = mat
                    break

        if target_material is None:
            missing_targets.append(f"{source_name} -> {target_name}")
            print(f"Warning: Target material '{target_name}' not found for custom mapping '{source_name}'")
            continue

        for src_mat in source_candidates:
            material_mapping[src_mat] = target_material
            print(f"Found custom mapping: {src_mat.name} -> {target_material.name}")
    
    # Find all CEL materials and their BSDF counterparts
    for mat in materials:
        cel_match = cel_pattern.match(mat.name)
        if cel_match:
            base_cel = cel_match.group(1)
            base_bsdf = base_cel.replace("CEL_", "BSDF_", 1)
            if base_bsdf in bsdf_base_map:
                material_mapping[mat] = bsdf_base_map[base_bsdf]
                print(f"Found CEL mapping: {mat.name} -> {bsdf_base_map[base_bsdf].name}")
            else:
                missing_targets.append(f"{mat.name} -> {base_bsdf}")
                print(f"Warning: No BSDF counterpart found for {mat.name}")

    # Use Blender's built-in user remapping to replace ALL users
    for source_material, target_material in material_mapping.items():
        print(f"Remapping all users of {source_material.name} to {target_material.name}")
        
        # Get user count before remapping
        users_before = source_material.users
        
        # Remap all users of the source material to the target material
        # This catches ALL users including geometry node instances, drivers, etc.
        source_material.user_remap(target_material)
        
        # Get user count after remapping
        users_after = source_material.users
        
        replacements_this_material = users_before - users_after
        replacements_made += replacements_this_material
        
        print(f"  Users before: {users_before}, after: {users_after}")
        print(f"  Remapped {replacements_this_material} users")
    
    # Optional: Remove unused source materials after remapping
    print(f"\nCleaning up unused source materials...")
    removed_materials = 0
    
    for source_material in material_mapping.keys():
        if source_material.users == 0:
            print(f"Removing unused material: {source_material.name}")
            bpy.data.materials.remove(source_material)
            removed_materials += 1
        else:
            print(f"Warning: {source_material.name} still has {source_material.users} users after remapping")
    
    # Summary
    print(f"\n=== REPLACEMENT SUMMARY ===")
    print(f"Materials linked from library: {len(linked_materials)}")
    print(f"Missing datablocks remapped: {missing_remaps}")
    print(f"Appended->Linked remappings: {appended_remaps}")
    print(f"Total materials mapped: {len(material_mapping)}")
    print(f"Successful mappings: {len(material_mapping)}")
    print(f"Total user remappings: {replacements_made}")
    print(f"Source materials removed: {removed_materials}")
    
    if missing_targets:
        print(f"\nMissing target materials for:")
        for mapping in missing_targets:
            print(f"  - {mapping}")
    
    return len(material_mapping), replacements_made

# Run the replacement
if __name__ == "__main__":
    replace_cel_materials()

print("\nRemaining CEL materials in file:")
cel_count = 0
for mat in bpy.data.materials:
    if mat.name.startswith("CEL_"):
        print(f"  {mat.name} ({mat.users} users)")
        cel_count += 1

if cel_count == 0:
    print("  None - all CEL materials have been replaced!")

print("\nBSDF materials in file:")
for mat in bpy.data.materials:
    if mat.name.startswith("BSDF_"):
        print(f"  {mat.name} ({mat.users} users)") 