# Script to organize texture files by checksum with two-level duplicate detection
# Pass 1: Intra-blendfile duplicates → [blendfile]\common
# Pass 2: Inter-blendfile duplicates → \textures\common
# Usage: .\organize_textures.ps1

# Prompt user for texture folder path
$textureFolderPath = Read-Host "Enter texture folder path"

# Validate the input path
if ([string]::IsNullOrWhiteSpace($textureFolderPath)) {
    Write-Host "Error: No path provided." -ForegroundColor Red
    exit
}

if (-not (Test-Path -Path $textureFolderPath -PathType Container)) {
    Write-Host "Error: Path does not exist or is not a directory: $textureFolderPath" -ForegroundColor Red
    exit
}

# Resolve the full path
$textureFolderPath = (Resolve-Path $textureFolderPath).ProviderPath
Write-Host "Processing texture folder: $textureFolderPath" -ForegroundColor Cyan

# Add required .NET assemblies for image processing (for FlatColors standardization)
Add-Type -AssemblyName System.Drawing

# Function to calculate checksums for files
function Get-FilesWithChecksums {
    param(
        [array]$Files
    )
    
    $throttleLimit = [Math]::Max(1, [Environment]::ProcessorCount)
    $parallelScriptBlock = {
        try {
            $hash = Get-FileHash -Path $_.FullName -Algorithm SHA256
            [PSCustomObject]@{
                File = $_
                Hash = $hash.Hash
            }
        } catch {
            Write-Warning "Failed to calculate checksum for: $($_.FullName) - $($_.Exception.Message)"
            $null
        }
    }
    
    return $Files | ForEach-Object -Parallel $parallelScriptBlock -ThrottleLimit $throttleLimit | Where-Object { $null -ne $_ }
}

# Function to move files to common folder
function Move-FilesToCommon {
    param(
        [array]$Files,
        [string]$CommonPath,
        [string]$DuplicatesPath,
        [hashtable]$FilesInCommon
    )
    
    $movedCount = 0
    $duplicateCount = 0
    
    foreach ($fileObj in $Files) {
        $fileName = $fileObj.Name
        $destinationPath = Join-Path -Path $CommonPath -ChildPath $fileName
        
        # Handle name conflicts
        if ($FilesInCommon.ContainsKey($fileName)) {
            $baseName = [System.IO.Path]::GetFileNameWithoutExtension($fileName)
            $extension = [System.IO.Path]::GetExtension($fileName)
            $counter = 1
            do {
                $newFileName = "${baseName}_${counter}${extension}"
                $destinationPath = Join-Path -Path $CommonPath -ChildPath $newFileName
                $counter++
            } while ($FilesInCommon.ContainsKey($newFileName) -or (Test-Path -Path $destinationPath))
            
            $fileName = $newFileName
        }
        
        try {
            Move-Item -Path $fileObj.FullName -Destination $destinationPath -Force
            $FilesInCommon[$fileName] = $true
            $movedCount++
        } catch {
            Write-Warning "Failed to move file: $($fileObj.FullName) - $($_.Exception.Message)"
        }
    }
    
    return @{
        MovedCount = $movedCount
        DuplicateCount = $duplicateCount
    }
}

# Function to extract suffix after blendfile prefix (e.g., "Demarco_Std_Teeth_ao.jpg" -> "Std_Teeth_ao.jpg")
# Only strips prefixes that are in the $ValidPrefixes list
function Get-FileNameWithoutPrefix {
    param(
        [string]$FileName,
        [string[]]$ValidPrefixes
    )
    
    # Validate input
    if ([string]::IsNullOrWhiteSpace($FileName)) {
        return $FileName
    }
    
    # If no valid prefixes provided, return original filename
    if ($null -eq $ValidPrefixes -or $ValidPrefixes.Count -eq 0) {
        return $FileName
    }
    
    # Use Split instead of regex for robustness (avoids $matches variable issues in loops)
    # Split by the first underscore only (limit to 2 parts)
    $parts = $FileName.Split('_', 2)
    
    if ($parts.Length -eq 2) {
        $prefix = $parts[0]
        $suffix = $parts[1]
        
        # Only strip if the prefix is in the valid prefixes list
        if ($ValidPrefixes -contains $prefix -and -not [string]::IsNullOrWhiteSpace($suffix)) {
            return $suffix
        }
    }
    
    # No valid prefix found or suffix is empty, return original filename
    return $FileName
}

# Function to process duplicate group
function Process-DuplicateGroup {
    param(
        [array]$Files,
        [string]$CommonPath,
        [hashtable]$FilesInCommon,
        [switch]$StripPrefix,
        [string[]]$ValidPrefixes = @(),
        [System.Collections.ArrayList]$MoveLog = $null
    )
    
    $movedCount = 0
    $duplicateCount = 0
    
    if ($Files.Count -eq 1) {
        # Single file - leave in place (will be processed in Pass 2)
        return @{
            MovedCount = 0
            DuplicateCount = 0
        }
    }
    
    # Validate files array
    if ($null -eq $Files -or $Files.Count -eq 0) {
        return @{
            MovedCount = 0
            DuplicateCount = 0
        }
    }
    
    # Multiple files with same checksum (duplicates)
    # Determine the filename to use
    $firstFile = $Files[0].File
    if ($null -eq $firstFile -or [string]::IsNullOrWhiteSpace($firstFile.Name)) {
        Write-Warning "Invalid file object in duplicate group"
        return @{
            MovedCount = 0
            DuplicateCount = 0
        }
    }
    
    $fileName = $firstFile.Name
    
    # If StripPrefix is enabled, always strip prefix from the filename
    if ($StripPrefix) {
        # Always try to strip prefix - if file has a prefix, it will be removed
        $strippedName = Get-FileNameWithoutPrefix -FileName $firstFile.Name -ValidPrefixes $ValidPrefixes
        
        # Use stripped name if it's different from original (prefix was removed)
        # Otherwise keep original (file had no prefix to strip)
        if ($strippedName -ne $firstFile.Name) {
            $fileName = $strippedName
        }
        
        # If we have multiple files with same checksum, try to find common suffix
        # This handles cases where files from different blendfiles share the same texture
        if ($Files.Count -gt 1) {
            $allSuffixes = @()
            foreach ($fileObj in $Files) {
                # $fileObj is a PSCustomObject with .File property, so access .File.Name
                $suffix = Get-FileNameWithoutPrefix -FileName $fileObj.File.Name -ValidPrefixes $ValidPrefixes
                $allSuffixes += $suffix
            }
            
            # If all files strip to the same suffix, use that
            $uniqueSuffixes = @($allSuffixes | Select-Object -Unique)
            if ($uniqueSuffixes.Count -eq 1 -and $uniqueSuffixes[0] -ne $firstFile.Name) {
                $fileName = [string]$uniqueSuffixes[0]
            }
        }
    }
    
    # Ensure fileName is never null or empty
    if ([string]::IsNullOrWhiteSpace($fileName)) {
        $fileName = $firstFile.Name
    }
    
    $destinationPath = Join-Path -Path $CommonPath -ChildPath $fileName
    
    # Handle name conflicts for the first file
    if ($FilesInCommon.ContainsKey($fileName)) {
        $baseName = [System.IO.Path]::GetFileNameWithoutExtension($fileName)
        $extension = [System.IO.Path]::GetExtension($fileName)
        $counter = 1
        do {
            $newFileName = "${baseName}_${counter}${extension}"
            $destinationPath = Join-Path -Path $CommonPath -ChildPath $newFileName
            $counter++
        } while ($FilesInCommon.ContainsKey($newFileName) -or (Test-Path -Path $destinationPath))
        
        $fileName = $newFileName
    }
    
    try {
        Move-Item -Path $firstFile.FullName -Destination $destinationPath -Force
        $FilesInCommon[$fileName] = $true
        $movedCount++
        
        # Log the move
        if ($null -ne $MoveLog) {
            $normalizedOriginal = [System.IO.Path]::GetFullPath($firstFile.FullName)
            $normalizedNew = [System.IO.Path]::GetFullPath($destinationPath)
            $null = $MoveLog.Add([PSCustomObject]@{
                OriginalPath = $normalizedOriginal
                NewPath = $normalizedNew
                Type = "moved"
            })
        }
    } catch {
        Write-Warning "Failed to move first duplicate file: $($firstFile.FullName) - $($_.Exception.Message)"
    }
    
    # Delete remaining duplicate files (they're replaced by the common file)
    for ($i = 1; $i -lt $Files.Count; $i++) {
        $fileObj = $Files[$i].File
        try {
            $originalPath = $fileObj.FullName
            Remove-Item -Path $originalPath -Force
            $duplicateCount++
            
            # Log the deletion (replaced by the moved file)
            if ($null -ne $MoveLog) {
                $normalizedOriginal = [System.IO.Path]::GetFullPath($originalPath)
                $normalizedReplacement = [System.IO.Path]::GetFullPath($destinationPath)
                $null = $MoveLog.Add([PSCustomObject]@{
                    OriginalPath = $normalizedOriginal
                    ReplacedBy = $normalizedReplacement
                    Type = "deleted"
                })
            }
        } catch {
            Write-Warning "Failed to delete duplicate file: $($fileObj.FullName) - $($_.Exception.Message)"
        }
    }
    
    return @{
        MovedCount = $movedCount
        DuplicateCount = $duplicateCount
    }
}

# Function to extract and normalize hex color code from filename (for FlatColors)
# For TGA files, preserves 8-digit codes (RGBA). For others, uses 6-digit codes (RGB).
function Get-NormalizedColorCode {
    param(
        [string]$FileName,
        [string]$Extension = ""
    )
    
    # Remove extension if not provided
    $baseName = [System.IO.Path]::GetFileNameWithoutExtension($FileName)
    if ([string]::IsNullOrWhiteSpace($Extension)) {
        $Extension = [System.IO.Path]::GetExtension($FileName)
    }
    
    # For TGA files, preserve 8-digit codes (RGBA with alpha channel)
    if ($Extension -eq ".tga") {
        # Match 8-digit hex: #RRGGBBAA
        if ($baseName -match '^(#?)([0-9A-Fa-f]{8})') {
            $hexCode = $Matches[2].ToUpper()
            return "#$hexCode"
        }
        # Fallback to 6-digit if 8-digit not found
        if ($baseName -match '^(#?)([0-9A-Fa-f]{6})') {
            $hexCode = $Matches[2].ToUpper()
            return "#$hexCode"
        }
    } else {
        # For non-TGA files, use 6-digit codes (RGB)
        if ($baseName -match '^(#?)([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?') {
            $hexCode = $Matches[2].ToUpper()
            # Return normalized format: #RRGGBB (always with #, always uppercase, no suffix)
            return "#$hexCode"
        }
    }
    
    return $null
}

# Function to convert and resize image to 16x16 JPG (for FlatColors)
function Convert-To16x16Jpg {
    param(
        [string]$SourcePath,
        [string]$DestinationPath
    )
    
    try {
        $image = [System.Drawing.Image]::FromFile($SourcePath)
        
        # Create 16x16 bitmap
        $bitmap = New-Object System.Drawing.Bitmap(16, 16)
        $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
        $graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
        $graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
        $graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
        
        # Draw resized image
        $graphics.DrawImage($image, 0, 0, 16, 16)
        
        # Get JPEG codec
        $jpegCodec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() | Where-Object { $_.MimeType -eq "image/jpeg" }
        $encoderParams = New-Object System.Drawing.Imaging.EncoderParameters(1)
        $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::Quality, 95)
        
        # Save as JPG
        $bitmap.Save($DestinationPath, $jpegCodec, $encoderParams)
        
        # Cleanup
        $graphics.Dispose()
        $bitmap.Dispose()
        $image.Dispose()
        $encoderParams.Dispose()
        
        return $true
    } catch {
        Write-Warning "Failed to convert image: $SourcePath - $($_.Exception.Message)"
        return $false
    }
}

# Function to check if image is 16x16 (for FlatColors)
function Test-ImageSize {
    param([string]$ImagePath)
    
    try {
        $image = [System.Drawing.Image]::FromFile($ImagePath)
        $is16x16 = ($image.Width -eq 16 -and $image.Height -eq 16)
        $image.Dispose()
        return $is16x16
    } catch {
        return $false
    }
}

# ============================================================================
# PASS 1: Intra-Blendfile Processing
# ============================================================================
Write-Host ""
Write-Host "=== PASS 1: Intra-Blendfile Processing ===" -ForegroundColor Cyan

# Get all direct subdirectories of texture folder (blendfile folders)
$blendfileFolders = Get-ChildItem -Path $textureFolderPath -Directory | Where-Object { $_.Name -ne "common" }

if ($null -eq $blendfileFolders -or $blendfileFolders.Count -eq 0) {
    Write-Host "No blendfile folders found. Skipping Pass 1." -ForegroundColor Yellow
} else {
    Write-Host "Found $($blendfileFolders.Count) blendfile folder(s) to process." -ForegroundColor Green
    
    $totalPass1Moved = 0
    $totalPass1Duplicates = 0
    $pass1MoveLog = [System.Collections.ArrayList]::new()
    
    foreach ($blendfileFolder in $blendfileFolders) {
        Write-Host ""
        Write-Host "Processing blendfile: $($blendfileFolder.Name)" -ForegroundColor Yellow
        
        # Get all files in this blendfile folder, excluding \common folders
        $blendfileFiles = Get-ChildItem -Path $blendfileFolder.FullName -Recurse -File | Where-Object { $_.FullName -notlike "*\common\*" }
        
        if ($null -eq $blendfileFiles -or $blendfileFiles.Count -eq 0) {
            Write-Host "  No files found in this blendfile folder." -ForegroundColor Gray
            continue
        }
        
        Write-Host "  Found $($blendfileFiles.Count) files." -ForegroundColor Gray
        
        # Calculate checksums
        Write-Host "  Calculating checksums..." -ForegroundColor Gray
        $filesWithChecksums = Get-FilesWithChecksums -Files $blendfileFiles
        
        # Group by checksum
        $groupedByChecksum = $filesWithChecksums | Group-Object -Property Hash
        
        Write-Host "  Found $($groupedByChecksum.Count) unique checksums." -ForegroundColor Gray
        
        # Create [blendfile]\common directory
        $blendfileCommonPath = Join-Path -Path $blendfileFolder.FullName -ChildPath "common"
        
        if (-not (Test-Path -Path $blendfileCommonPath -PathType Container)) {
            New-Item -ItemType Directory -Path $blendfileCommonPath | Out-Null
        }
        
        # Track filenames already in [blendfile]\common
        $filesInBlendfileCommon = @{}
        
        # Process each checksum group
        $blendfileMoved = 0
        $blendfileDuplicates = 0
        
        foreach ($group in $groupedByChecksum) {
            $result = Process-DuplicateGroup -Files $group.Group -CommonPath $blendfileCommonPath -FilesInCommon $filesInBlendfileCommon -MoveLog $pass1MoveLog
            $blendfileMoved += $result.MovedCount
            $blendfileDuplicates += $result.DuplicateCount
        }
        
        Write-Host "  Moved $blendfileMoved file(s) to \common, deleted $blendfileDuplicates duplicate(s)" -ForegroundColor Green
        $totalPass1Moved += $blendfileMoved
        $totalPass1Duplicates += $blendfileDuplicates
    }
    
    Write-Host ""
    Write-Host "Pass 1 complete: $totalPass1Moved file(s) moved, $totalPass1Duplicates duplicate(s) deleted" -ForegroundColor Green
}

# ============================================================================
# PASS 2: Inter-Blendfile Processing
# ============================================================================
Write-Host ""
Write-Host "=== PASS 2: Inter-Blendfile Processing ===" -ForegroundColor Cyan

# Build list of valid blendfile prefixes from folder names
$validBlendfilePrefixes = @()
$blendfileFoldersForPrefixes = Get-ChildItem -Path $textureFolderPath -Directory | Where-Object { $_.Name -ne "common" }
if ($null -ne $blendfileFoldersForPrefixes) {
    $validBlendfilePrefixes = $blendfileFoldersForPrefixes | ForEach-Object { $_.Name }
    Write-Host "Valid blendfile prefixes: $($validBlendfilePrefixes -join ', ')" -ForegroundColor Gray
}

# Collect FlatColors files separately (including those in \common\FlatColors folders, but not from root \common\FlatColors)
Write-Host "Collecting FlatColors files..." -ForegroundColor Yellow
$rootCommonFlatColorsPath = Join-Path -Path $textureFolderPath -ChildPath "common\FlatColors"
$allFlatColorsFiles = Get-ChildItem -Path $textureFolderPath -Recurse -File | Where-Object { 
    ($_.FullName -like "*\FlatColors\*" -or $_.Name -like "*FlatColors*") -and 
    $_.FullName -notlike "$([regex]::Escape($rootCommonFlatColorsPath))\*" 
}

# Get all remaining files (excluding all \common folders, but we'll handle FlatColors separately)
Write-Host "Collecting remaining files..." -ForegroundColor Yellow
$remainingFiles = Get-ChildItem -Path $textureFolderPath -Recurse -File | Where-Object { $_.FullName -notlike "*\common\*" }

if ($null -eq $remainingFiles -or $remainingFiles.Count -eq 0) {
    Write-Host "No remaining files found to process." -ForegroundColor Yellow
} else {
    Write-Host "Found $($remainingFiles.Count) remaining files to process." -ForegroundColor Green
    
    # Calculate checksums
    Write-Host "Calculating checksums using parallel processing (this may take a while)..." -ForegroundColor Yellow
    $filesWithChecksums = Get-FilesWithChecksums -Files $remainingFiles
    
    Write-Host "Checksum calculation complete." -ForegroundColor Green
    
    # Separate FlatColors files from other files before processing
    Write-Host "Identifying FlatColors files..." -ForegroundColor Yellow
    $flatColorsFiles = $allFlatColorsFiles | Where-Object { (Test-Path -Path $_.FullName) }
    $nonFlatColorsFiles = $remainingFiles | Where-Object { $_.Name -notlike "*FlatColors*" }
    
    Write-Host "Found $($flatColorsFiles.Count) FlatColors file(s) (including from \common\FlatColors folders), $($nonFlatColorsFiles.Count) other file(s)" -ForegroundColor Gray
    
    # Group non-FlatColors files by checksum
    Write-Host "Grouping files by checksum..." -ForegroundColor Yellow
    $nonFlatColorsWithChecksums = $filesWithChecksums | Where-Object { $_.File.Name -notlike "*FlatColors*" }
    $groupedByChecksum = $nonFlatColorsWithChecksums | Group-Object -Property Hash
    
    Write-Host "Found $($groupedByChecksum.Count) unique checksums." -ForegroundColor Green
    
    # Create \textures\common directory
    $rootCommonPath = Join-Path -Path $textureFolderPath -ChildPath "common"
    
    if (-not (Test-Path -Path $rootCommonPath -PathType Container)) {
        New-Item -ItemType Directory -Path $rootCommonPath | Out-Null
        Write-Host "Created directory: $rootCommonPath" -ForegroundColor Green
    }
    
    # Track filenames already in \textures\common
    $filesInRootCommon = @{}
    
    # Process each checksum group (excluding FlatColors)
    Write-Host "Moving files to \common and deleting duplicates..." -ForegroundColor Yellow
    $pass2Moved = 0
    $pass2Duplicates = 0
    $pass2MoveLog = [System.Collections.ArrayList]::new()
    
    foreach ($group in $groupedByChecksum) {
        $files = $group.Group
        
        if ($files.Count -eq 1) {
            # Single file - leave in place (unique file)
            continue
        }
        
        # Multiple files with same checksum (duplicates across blendfiles)
        # Use StripPrefix to remove blendfile prefixes (e.g., "Demarco_", "Chan_") when all files share the same suffix
        $result = Process-DuplicateGroup -Files $files -CommonPath $rootCommonPath -FilesInCommon $filesInRootCommon -StripPrefix -ValidPrefixes $validBlendfilePrefixes -MoveLog $pass2MoveLog
        $pass2Moved += $result.MovedCount
        $pass2Duplicates += $result.DuplicateCount
    }
    
    # Process FlatColors files: standardize to 16x16 JPG (except TGA), merge duplicates by color code
    Write-Host "Processing FlatColors files (standardizing and merging duplicates)..." -ForegroundColor Yellow
    
    if ($null -ne $flatColorsFiles -and $flatColorsFiles.Count -gt 0) {
        Write-Host "Found $($flatColorsFiles.Count) FlatColors file(s) to process" -ForegroundColor Gray
        
        # Group files by normalized color code (instead of checksum)
        Write-Host "Grouping FlatColors files by color code..." -ForegroundColor Yellow
        $filesByColor = @{}
        
        foreach ($file in $flatColorsFiles) {
            if (-not (Test-Path -Path $file.FullName)) {
                continue
            }
            
            # Use composite key: color code + extension to separate TGA (8-digit) from JPG (6-digit)
            $colorCode = Get-NormalizedColorCode -FileName $file.Name -Extension $file.Extension
            if ($null -eq $colorCode) {
                Write-Warning "Could not extract color code from: $($file.Name)"
                continue
            }
            
            # Create composite key: colorCode_extension to keep TGA and JPG separate
            $groupKey = "$colorCode$($file.Extension)"
            
            if (-not $filesByColor.ContainsKey($groupKey)) {
                $filesByColor[$groupKey] = @()
            }
            $filesByColor[$groupKey] += $file
        }
        
        Write-Host "Found $($filesByColor.Count) unique color code(s)" -ForegroundColor Gray
        
        # Create \textures\common\FlatColors directory
        $flatColorsPath = Join-Path -Path $rootCommonPath -ChildPath "FlatColors"
        if (-not (Test-Path -Path $flatColorsPath -PathType Container)) {
            New-Item -ItemType Directory -Path $flatColorsPath | Out-Null
            Write-Host "Created directory: $flatColorsPath" -ForegroundColor Green
        }
        
        # Track filenames already in \common\FlatColors
        $filesInFlatColors = @{}
        $flatColorsMoved = 0
        $flatColorsDuplicates = 0
        $flatColorsConverted = 0
        $flatColorsResized = 0
        $flatColorsMoveLog = [System.Collections.ArrayList]::new()
        
        foreach ($groupKey in $filesByColor.Keys) {
            $files = $filesByColor[$groupKey]
            
            # Extract color code and extension from group key
            # Group key format: #RRGGBB.ext or #RRGGBBAA.ext
            if ($groupKey -match '^(#[0-9A-F]{6,8})(\.[^.]+)$') {
                $colorCode = $Matches[1]
                $targetExtension = $Matches[2]
            } else {
                Write-Warning "Invalid group key format: $groupKey"
                continue
            }
            
            $targetFileName = "$colorCode$targetExtension"
            
            # Find the best file to keep
            $fileToKeep = $null
            
            # First, check if there's already a correct file in target location
            $existingTarget = Join-Path -Path $flatColorsPath -ChildPath $targetFileName
            if (Test-Path -Path $existingTarget) {
                $existingFile = Get-Item -Path $existingTarget
                # Check if it's already correct
                if ($existingFile.Extension -eq ".tga") {
                    $fileToKeep = $existingFile
                } elseif ($existingFile.Extension -eq ".jpg" -and (Test-ImageSize -ImagePath $existingFile.FullName)) {
                    $fileToKeep = $existingFile
                }
            }
            
            # If no existing correct file, find best source file
            if ($null -eq $fileToKeep) {
                # Prefer TGA file first (preserves transparency)
                $tgaFile = $files | Where-Object { 
                    $_.Extension -eq ".tga" -and 
                    (Test-Path -Path $_.FullName)
                } | Select-Object -First 1
                
                if ($tgaFile) {
                    $fileToKeep = $tgaFile
                } else {
                    # If no TGA, prefer existing JPG file
                    $jpgFile = $files | Where-Object { 
                        ($_.Extension -eq ".jpg" -or $_.Extension -eq ".jpeg") -and 
                        (Test-Path -Path $_.FullName)
                    } | Select-Object -First 1
                    
                    if ($jpgFile) {
                        $fileToKeep = $jpgFile
                    } else {
                        # Otherwise, use the first available file
                        $fileToKeep = $files | Where-Object { Test-Path -Path $_.FullName } | Select-Object -First 1
                    }
                }
            }
            
            if ($null -eq $fileToKeep) {
                Write-Warning "  ${colorCode}: No valid file found to process"
                continue
            }
            
            $targetPath = Join-Path -Path $flatColorsPath -ChildPath $targetFileName
            
            # If target is TGA, preserve as-is (don't convert)
            if ($targetExtension -eq ".tga") {
                if ($fileToKeep.FullName -ne $targetPath) {
                    # Move TGA to target location
                    try {
                        if (Test-Path -Path $targetPath) {
                            Remove-Item -Path $targetPath -Force
                        }
                        $originalPath = $fileToKeep.FullName
                        Move-Item -Path $originalPath -Destination $targetPath -Force
                        $filesInFlatColors[$targetFileName] = $true
                        $flatColorsMoved++
                        
                        # Log the move
                        $normalizedOriginal = [System.IO.Path]::GetFullPath($originalPath)
                        $normalizedNew = [System.IO.Path]::GetFullPath($targetPath)
                        $null = $flatColorsMoveLog.Add([PSCustomObject]@{
                            OriginalPath = $normalizedOriginal
                            NewPath = $normalizedNew
                            Type = "moved"
                        })
                    } catch {
                        Write-Warning "  ${colorCode}: Failed to move TGA: $($_.Exception.Message)"
                    }
                }
            } else {
                # Target is JPG - convert/resize if needed
                $needsConversion = $false
                $needsResize = $false
                
                if ($fileToKeep.Extension -ne ".jpg" -and $fileToKeep.Extension -ne ".jpeg") {
                    $needsConversion = $true
                }
                
                if (-not (Test-ImageSize -ImagePath $fileToKeep.FullName)) {
                    $needsResize = $true
                }
                
                # Check if target already exists and is correct
                if ((Test-Path -Path $targetPath) -and 
                    (Get-Item -Path $targetPath).Extension -eq ".jpg" -and
                    (Test-ImageSize -ImagePath $targetPath)) {
                    # Target already exists and is correct, skip
                } elseif ($needsConversion -or $needsResize -or $fileToKeep.FullName -ne $targetPath) {
                    # Convert/resize to target
                    if (Convert-To16x16Jpg -SourcePath $fileToKeep.FullName -DestinationPath $targetPath) {
                        if ($needsConversion -and $needsResize) {
                            $flatColorsConverted++
                            $flatColorsResized++
                        } elseif ($needsConversion) {
                            $flatColorsConverted++
                        } elseif ($needsResize) {
                            $flatColorsResized++
                        } else {
                            $flatColorsMoved++
                        }
                        
                        $filesInFlatColors[$targetFileName] = $true
                        
                        # Log the conversion/move
                        $normalizedOriginal = [System.IO.Path]::GetFullPath($fileToKeep.FullName)
                        $normalizedNew = [System.IO.Path]::GetFullPath($targetPath)
                        $null = $flatColorsMoveLog.Add([PSCustomObject]@{
                            OriginalPath = $normalizedOriginal
                            NewPath = $normalizedNew
                            Type = "moved"
                        })
                    } else {
                        Write-Warning "  ${colorCode}: Failed to convert/resize"
                        continue
                    }
                } else {
                    # File is already correct format and size
                    if ($fileToKeep.FullName -ne $targetPath) {
                        try {
                            if (Test-Path -Path $targetPath) {
                                Remove-Item -Path $targetPath -Force
                            }
                            $originalPath = $fileToKeep.FullName
                            Move-Item -Path $originalPath -Destination $targetPath -Force
                            $filesInFlatColors[$targetFileName] = $true
                            $flatColorsMoved++
                            
                            # Log the move
                            $normalizedOriginal = [System.IO.Path]::GetFullPath($originalPath)
                            $normalizedNew = [System.IO.Path]::GetFullPath($targetPath)
                            $null = $flatColorsMoveLog.Add([PSCustomObject]@{
                                OriginalPath = $normalizedOriginal
                                NewPath = $normalizedNew
                                Type = "moved"
                            })
                        } catch {
                            Write-Warning "  ${colorCode}: Failed to move: $($_.Exception.Message)"
                        }
                    }
                }
            }
            
            # Delete duplicate files (all files in the group except the target)
            foreach ($file in $files) {
                if ($file.FullName -ne $targetPath -and (Test-Path -Path $file.FullName)) {
                    try {
                        $originalPath = $file.FullName
                        Remove-Item -Path $originalPath -Force
                        $flatColorsDuplicates++
                        
                        # Log the deletion
                        $normalizedOriginal = [System.IO.Path]::GetFullPath($originalPath)
                        $normalizedReplacement = [System.IO.Path]::GetFullPath($targetPath)
                        $null = $flatColorsMoveLog.Add([PSCustomObject]@{
                            OriginalPath = $normalizedOriginal
                            ReplacedBy = $normalizedReplacement
                            Type = "deleted"
                        })
                    } catch {
                        Write-Warning "  Failed to delete duplicate: $($file.FullName) - $($_.Exception.Message)"
                    }
                }
            }
        }
        
        Write-Host "Processed FlatColors: $flatColorsMoved moved, $flatColorsConverted converted, $flatColorsResized resized, $flatColorsDuplicates duplicate(s) deleted" -ForegroundColor Green
    } else {
        Write-Host "No FlatColors files found to process." -ForegroundColor Gray
    }
    
    Write-Host ""
    Write-Host "Pass 2 complete: $pass2Moved file(s) moved to \common, $pass2Duplicates duplicate(s) deleted" -ForegroundColor Green
}

# ============================================================================
# Save Move Log and Find Blend Files
# ============================================================================
Write-Host ""
Write-Host "Saving move log and finding blend files..." -ForegroundColor Yellow

# Combine all move logs
$allMoves = @()
if ($null -ne $pass1MoveLog -and $pass1MoveLog.Count -gt 0) {
    $allMoves += $pass1MoveLog
}
if ($null -ne $pass2MoveLog -and $pass2MoveLog.Count -gt 0) {
    $allMoves += $pass2MoveLog
}
if ($null -ne $flatColorsMoveLog -and $flatColorsMoveLog.Count -gt 0) {
    $allMoves += $flatColorsMoveLog
}

# Get parent directory of texture folder
$blendFileParentDir = Split-Path -Path $textureFolderPath -Parent

# Find matching blend files
# Match logic: blendfile folder name (e.g., "Beth") should appear in blend file name (e.g., "AM_Beth_v3.2.blend")
$blendFileMappings = @()
if ($null -ne $blendfileFolders -and $blendfileFolders.Count -gt 0) {
    # Get blendfile folder names
    $blendfileFolderNames = $blendfileFolders | ForEach-Object { $_.Name }
    
    # Find all .blend files in parent directory
    $blendFiles = Get-ChildItem -Path $blendFileParentDir -Filter "*.blend" -File -ErrorAction SilentlyContinue
    
    if ($null -ne $blendFiles -and $blendFiles.Count -gt 0) {
        foreach ($blendFile in $blendFiles) {
            $blendFileName = $blendFile.BaseName
            
            # Check if any blendfile folder name appears in the blend file name
            foreach ($folderName in $blendfileFolderNames) {
                # Match folder name when surrounded by underscores, dots, hyphens, or at start/end
                # This avoids partial matches (e.g., "Beth" in "Bethany") while allowing "Beth" in "AM_Beth_v3.2"
                $escapedFolderName = [regex]::Escape($folderName)
                if ($blendFileName -match "(^|[._-])$escapedFolderName([._-]|$)") {
                    $blendFileMappings += [PSCustomObject]@{
                        BlendFile = [System.IO.Path]::GetFullPath($blendFile.FullName)
                        BlendfileFolder = $folderName
                    }
                    Write-Host "Found matching blend file: $($blendFile.Name) -> $folderName" -ForegroundColor Gray
                    break  # Only match once per blend file
                }
            }
        }
    }
}

# Create move log object
$moveLogData = [PSCustomObject]@{
    TextureFolderPath = $textureFolderPath
    Timestamp = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ")
    TotalMoves = ($allMoves | Where-Object { $_.Type -eq "moved" }).Count
    TotalDeletes = ($allMoves | Where-Object { $_.Type -eq "deleted" }).Count
    Moves = $allMoves
    BlendFileMappings = $blendFileMappings
}

# Save to JSON file
$moveLogPath = Join-Path -Path $blendFileParentDir -ChildPath "texture_moves.json"
try {
    $moveLogData | ConvertTo-Json -Depth 10 | Set-Content -Path $moveLogPath -Encoding UTF8
    Write-Host "Move log saved to: $moveLogPath" -ForegroundColor Green
    Write-Host "  Total moves: $($moveLogData.TotalMoves), Total deletes: $($moveLogData.TotalDeletes)" -ForegroundColor Gray
    Write-Host "  Blend files found: $($blendFileMappings.Count)" -ForegroundColor Gray
} catch {
    Write-Warning "Failed to save move log: $($_.Exception.Message)"
}

# ============================================================================
# Remap Texture Paths in Blend Files
# ============================================================================
if ($blendFileMappings.Count -gt 0 -and (Test-Path -Path $moveLogPath)) {
    Write-Host ""
    Write-Host "Remapping texture paths in blend files..." -ForegroundColor Yellow
    
    # Find Blender executable from PATH
    $blenderExe = $null
    $blenderInPath = Get-Command blender -ErrorAction SilentlyContinue
    if ($null -ne $blenderInPath) {
        $blenderExe = $blenderInPath.Source
    }
    
    if ($null -eq $blenderExe) {
        Write-Warning "Blender executable not found. Skipping texture path remapping."
        Write-Host "  Please install Blender or add it to your PATH." -ForegroundColor Yellow
    } else {
        Write-Host "Found Blender: $blenderExe" -ForegroundColor Gray
        
        # Get the remap script path (should be in the same directory as this script)
        $scriptDir = Split-Path -Path $MyInvocation.MyCommand.Path -Parent
        $remapScriptPath = Join-Path -Path $scriptDir -ChildPath "remap_texture_paths.py"
        
        if (-not (Test-Path -Path $remapScriptPath)) {
            Write-Warning "Remap script not found: $remapScriptPath"
            Write-Warning "Skipping texture path remapping."
        } else {
            $processedCount = 0
            $failedCount = 0
            
            Write-Host "Processing $($blendFileMappings.Count) blend file(s)..." -ForegroundColor Gray
            
            foreach ($mapping in $blendFileMappings) {
                $blendFilePath = $mapping.BlendFile
                $blendFileName = Split-Path -Path $blendFilePath -Leaf
                
                Write-Host "  Processing: $blendFileName" -ForegroundColor Cyan
                
                # Check if blend file exists
                if (-not (Test-Path -Path $blendFilePath)) {
                    Write-Warning "    Blend file not found: $blendFilePath"
                    $failedCount++
                    continue
                }
                
                # Run Blender with the remapping script (one file at a time)
                # Delete old output file
                Remove-Item -Path "blender_output.txt" -ErrorAction SilentlyContinue
                
                try {
                    # Run Blender and capture output while displaying it (like compress_blend_files.bat)
                    # Build command with proper argument array
                    $blenderArgsArray = @(
                        "--background",
                        "--factory-startup",
                        "--python", $remapScriptPath,
                        "--",
                        $blendFilePath,
                        $moveLogPath
                    )
                    
                    # Execute and pipe output through Tee-Object to display and save
                    & $blenderExe $blenderArgsArray 2>&1 | Tee-Object -FilePath "blender_output.txt" | ForEach-Object {
                        Write-Host "    $_"
                    }
                    
                    # Check exit code
                    $exitCode = $LASTEXITCODE
                    
                    if ($exitCode -eq 0) {
                        Write-Host "    Successfully remapped texture paths" -ForegroundColor Green
                        $processedCount++
                    } else {
                        Write-Warning "    Failed to remap texture paths (exit code: $exitCode)"
                        $failedCount++
                    }
                } catch {
                    Write-Warning "    Exception while processing blend file: $($_.Exception.Message)"
                    $failedCount++
                }
            }
            
            # Clean up temporary output files
            Remove-Item -Path "blender_output.txt" -ErrorAction SilentlyContinue
            Remove-Item -Path "blender_error.txt" -ErrorAction SilentlyContinue
            
            Write-Host ""
            if ($processedCount -gt 0 -or $failedCount -gt 0) {
                Write-Host "Blend file remapping complete: $processedCount succeeded, $failedCount failed" -ForegroundColor $(if ($failedCount -eq 0) { "Green" } else { "Yellow" })
            }
        }
    }
} else {
    if ($blendFileMappings.Count -eq 0) {
        Write-Host ""
        Write-Host "No matching blend files found. Skipping texture path remapping." -ForegroundColor Gray
    }
}

# Function to remove empty folders recursively
function Remove-EmptyFolders {
    param(
        [string]$Path,
        [string]$RootPath
    )
    
    $removedCount = 0
    
    # Get all subdirectories
    $subdirs = Get-ChildItem -Path $Path -Directory -ErrorAction SilentlyContinue
    
    if ($null -ne $subdirs) {
        foreach ($subdir in $subdirs) {
            # Recursively process subdirectories first
            $removedCount += Remove-EmptyFolders -Path $subdir.FullName -RootPath $RootPath
            
            # Check if this directory is now empty (after processing subdirectories)
            $items = Get-ChildItem -Path $subdir.FullName -ErrorAction SilentlyContinue
            if ($null -eq $items -or $items.Count -eq 0) {
                # Don't remove the root path
                if ($subdir.FullName -ne $RootPath) {
                    try {
                        Remove-Item -Path $subdir.FullName -Force -ErrorAction Stop
                        $removedCount++
                    } catch {
                        Write-Warning "Failed to remove empty folder: $($subdir.FullName) - $($_.Exception.Message)"
                    }
                }
            }
        }
    }
    
    return $removedCount
}

# Remove empty folders
Write-Host ""
Write-Host "Removing empty folders..." -ForegroundColor Yellow
$emptyFoldersRemoved = Remove-EmptyFolders -Path $textureFolderPath -RootPath $textureFolderPath
if ($emptyFoldersRemoved -gt 0) {
    Write-Host "Removed $emptyFoldersRemoved empty folder(s)" -ForegroundColor Green
} else {
    Write-Host "No empty folders found to remove." -ForegroundColor Gray
}

Write-Host ""
Write-Host "File organization complete!" -ForegroundColor Green
