T.ME/BIBIL_0DAY
CasperSecurity


Server : Apache/2
System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64
User : gositeme ( 1004)
PHP Version : 8.2.29
Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Directory :  /home/gositeme/domains/soundstudiopro.com/private_html/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/soundstudiopro.com/private_html/callback.php
<?php
// Enable error reporting for debugging
error_reporting(E_ALL);
ini_set('display_errors', 0);

// Only set headers and handle requests if this file is being called directly (not included)
// Check if this is a direct request vs an include
$isDirectRequest = !defined('CALLBACK_INCLUDED') && 
                   (php_sapi_name() === 'cli' || 
                    (isset($_SERVER['REQUEST_METHOD']) && 
                     basename($_SERVER['PHP_SELF']) === 'callback.php'));

if ($isDirectRequest) {
    // Set proper headers
    header('Content-Type: application/json; charset=utf-8');
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
    header('Access-Control-Allow-Headers: Content-Type, Authorization');
    
    // Handle preflight requests
    if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
        http_response_code(200);
        exit(0);
    }
}

// Include database configuration
require_once 'config/database.php';

// Log callback data for debugging
$logFile = 'callback_log.txt';
$timestamp = date('Y-m-d H:i:s');
$input = file_get_contents('php://input');
$headers = getallheaders();

// Log the callback
$logEntry = "[$timestamp] Callback received\n";
$logEntry .= "Headers: " . json_encode($headers) . "\n";
$logEntry .= "Body: " . $input . "\n";
$logEntry .= "Method: " . $_SERVER['REQUEST_METHOD'] . "\n";
$logEntry .= "----------------------------------------\n";

file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);

// Function to properly sanitize and escape text data from DOM
function sanitizeDOMText($text) {
    if (empty($text)) return $text;
    
    // Convert to string if it's not already
    $text = (string) $text;
    
    // Handle common apostrophe issues from DOM
    $text = str_replace(
        ['\'', '"', '&apos;', '&quot;', '&#39;', '&#34;', '&rsquo;', '&ldquo;', '&rdquo;'],
        ['\'', '"', '\'', '"', '\'', '"', '\'', '"', '"'],
        $text
    );
    
    // Remove any HTML entities that might cause issues
    $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    
    // Clean up any remaining problematic characters
    $text = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $text);
    
    // Ensure proper UTF-8 encoding
    if (!mb_check_encoding($text, 'UTF-8')) {
        $text = mb_convert_encoding($text, 'UTF-8', 'auto');
    }
    
    // Trim whitespace
    $text = trim($text);
    
    return $text;
}

// Function to sanitize array data recursively
function sanitizeDOMArray($data) {
    if (is_array($data)) {
        foreach ($data as $key => $value) {
            if (is_string($value)) {
                $data[$key] = sanitizeDOMText($value);
            } elseif (is_array($value)) {
                $data[$key] = sanitizeDOMArray($value);
            }
        }
    }
    return $data;
}

// Function moved to config/database.php for shared use

// Function to extract title from various API response formats
function extractTitleFromCallback($data) {
    // Check multiple possible locations in the API response
    // CRITICAL FIX: Also check for empty strings and trim them to ensure we don't miss valid titles
    if (isset($data['title']) && !empty($data['title']) && trim($data['title']) !== '') {
        return sanitizeDOMText($data['title']);
    }
    
    if (isset($data['data']['title']) && !empty($data['data']['title']) && trim($data['data']['title']) !== '') {
        return sanitizeDOMText($data['data']['title']);
    }
    
    // Check in data.data array (common API.Box format)
    if (isset($data['data']['data']) && is_array($data['data']['data'])) {
        foreach ($data['data']['data'] as $item) {
            if (isset($item['title']) && !empty($item['title']) && trim($item['title']) !== '') {
                return sanitizeDOMText($item['title']);
            }
        }
    }
    
    // Check in direct data array
    if (isset($data['data']) && is_array($data['data']) && !isset($data['data']['data'])) {
        foreach ($data['data'] as $item) {
            if (is_array($item) && isset($item['title']) && !empty($item['title']) && trim($item['title']) !== '') {
                return sanitizeDOMText($item['title']);
            }
        }
    }
    
    return null;
}

// Function to extract duration from various API response formats
function extractDurationFromCallback($data) {
    // Check multiple possible locations in the API response
    if (isset($data['duration']) && $data['duration'] !== null && $data['duration'] !== '') {
        return floatval($data['duration']);
    }
    
    if (isset($data['data']['duration']) && $data['data']['duration'] !== null && $data['data']['duration'] !== '') {
        return floatval($data['data']['duration']);
    }
    
    // Check in data.data array (common API.Box format)
    if (isset($data['data']['data']) && is_array($data['data']['data'])) {
        foreach ($data['data']['data'] as $item) {
            if (isset($item['duration']) && $item['duration'] !== null && $item['duration'] !== '') {
                return floatval($item['duration']);
            }
        }
    }
    
    // Check in direct data array
    if (isset($data['data']) && is_array($data['data']) && !isset($data['data']['data'])) {
        foreach ($data['data'] as $item) {
            if (is_array($item) && isset($item['duration']) && $item['duration'] !== null && $item['duration'] !== '') {
                return floatval($item['duration']);
            }
        }
    }
    
    return null;
}

// Function to extract tags from various API response formats
// Collects ALL tags from ALL possible locations in the API response
function extractTagsFromCallback($data) {
    $allTags = [];
    $logFile = 'callback_log.txt';
    
    // Helper function to add tags to collection
    $addTags = function($tags) use (&$allTags) {
        if (is_array($tags)) {
            foreach ($tags as $tag) {
                if (!empty(trim($tag))) {
                    $allTags[] = trim($tag);
                }
            }
        } elseif (is_string($tags) && !empty(trim($tags))) {
            $allTags[] = trim($tags);
        }
    };
    
    // Check top-level tags
    if (isset($data['tags']) && !empty($data['tags'])) {
        file_put_contents($logFile, "  📍 Found tags at top level\n", FILE_APPEND | LOCK_EX);
        $addTags($data['tags']);
    }
    
    // Check data.tags
    if (isset($data['data']['tags']) && !empty($data['data']['tags'])) {
        file_put_contents($logFile, "  📍 Found tags at data.tags\n", FILE_APPEND | LOCK_EX);
        $addTags($data['data']['tags']);
    }
    
    // Check in data.data array (common API.Box format) - CHECK ALL VARIATIONS
    if (isset($data['data']['data']) && is_array($data['data']['data'])) {
        file_put_contents($logFile, "  📍 Checking data.data.data array (" . count($data['data']['data']) . " items)\n", FILE_APPEND | LOCK_EX);
        foreach ($data['data']['data'] as $index => $item) {
            if (isset($item['tags']) && !empty($item['tags'])) {
                file_put_contents($logFile, "  ✅ Found tags in data.data.data[$index]\n", FILE_APPEND | LOCK_EX);
                $addTags($item['tags']);
            }
            // Also check for tags in nested structures
            if (isset($item['result']) && is_array($item['result'])) {
                if (isset($item['result']['tags']) && !empty($item['result']['tags'])) {
                    file_put_contents($logFile, "  ✅ Found tags in data.data.data[$index].result.tags\n", FILE_APPEND | LOCK_EX);
                    $addTags($item['result']['tags']);
                }
            }
        }
    }
    
    // Check in direct data array
    if (isset($data['data']) && is_array($data['data']) && !isset($data['data']['data'])) {
        file_put_contents($logFile, "  📍 Checking direct data array\n", FILE_APPEND | LOCK_EX);
        foreach ($data['data'] as $index => $item) {
            if (is_array($item) && isset($item['tags']) && !empty($item['tags'])) {
                file_put_contents($logFile, "  ✅ Found tags in data[$index]\n", FILE_APPEND | LOCK_EX);
                $addTags($item['tags']);
            }
        }
    }
    
    // Check result field (API.Box sometimes puts tags in result)
    if (isset($data['result']) && is_array($data['result'])) {
        if (isset($data['result']['tags']) && !empty($data['result']['tags'])) {
            file_put_contents($logFile, "  ✅ Found tags in result.tags\n", FILE_APPEND | LOCK_EX);
            $addTags($data['result']['tags']);
        }
    }
    
    // Check data.result
    if (isset($data['data']['result']) && is_array($data['data']['result'])) {
        if (isset($data['data']['result']['tags']) && !empty($data['data']['result']['tags'])) {
            file_put_contents($logFile, "  ✅ Found tags in data.result.tags\n", FILE_APPEND | LOCK_EX);
            $addTags($data['data']['result']['tags']);
        }
    }
    
    // Remove duplicates and return
    $allTags = array_unique($allTags);
    $allTags = array_values($allTags); // Re-index
    
    if (!empty($allTags)) {
        file_put_contents($logFile, "  🎯 Total unique tags collected: " . count($allTags) . " - " . implode(', ', $allTags) . "\n", FILE_APPEND | LOCK_EX);
        // If we have multiple tags, return as array; if single string, return as array with one element
        return $allTags;
    }
    
    file_put_contents($logFile, "  ⚠️ No tags found in any location\n", FILE_APPEND | LOCK_EX);
    return null;
}

// Function to format tags for storage in tags column
// Converts array to semicolon-separated string, preserves string format
function formatTagsForStorage($tags) {
    if (!$tags) {
        return null;
    }
    
    if (is_array($tags)) {
        // Process each tag - if it contains semicolons, split it first
        $processedTags = [];
        foreach ($tags as $tag) {
            $tag = trim($tag);
            if (empty($tag)) continue;
            
            // If tag contains semicolons, split it (e.g., "acoustic; deep drums" -> ["acoustic", "deep drums"])
            if (strpos($tag, ';') !== false) {
                $splitTags = preg_split('/\s*;\s*/', $tag);
                foreach ($splitTags as $splitTag) {
                    $splitTag = trim($splitTag);
                    if (!empty($splitTag)) {
                        $processedTags[] = $splitTag;
                    }
                }
            } else {
                $processedTags[] = $tag;
            }
        }
        
        // Remove duplicates and filter out generic values
        $processedTags = array_unique($processedTags);
        $processedTags = array_filter($processedTags, function($tag) {
            $tagLower = strtolower(trim($tag));
            $genericValues = ['synthesizer', 'neutral', 'medium', 'electronic', 'c major', '4/4', 'null', 'none', ''];
            return !empty($tag) && strlen($tag) > 1 && !in_array($tagLower, $genericValues);
        });
        
        if (empty($processedTags)) {
            return null;
        }
        
        // Join with semicolon and space
        return implode('; ', array_values($processedTags));
    }
    
    // If it's already a string, parse it to handle mixed formats
    // e.g., "organic acoustic; deep drums primal energy tribal epic brass dramatic violin native flute"
    $tagsString = trim($tags);
    if (empty($tagsString)) {
        return null;
    }
    
    // Split by semicolon first, then by spaces if needed
    $tagArray = preg_split('/[,;]/', $tagsString);
    $processedTags = [];
    foreach ($tagArray as $tag) {
        $tag = trim($tag);
        if (!empty($tag) && strlen($tag) > 1) {
            $tagLower = strtolower($tag);
            $genericValues = ['synthesizer', 'neutral', 'medium', 'electronic', 'c major', '4/4', 'null', 'none'];
            if (!in_array($tagLower, $genericValues)) {
                $processedTags[] = $tag;
            }
        }
    }
    
    if (empty($processedTags)) {
        return $tagsString; // Return original if parsing fails
    }
    
    return implode('; ', array_unique($processedTags));
}

// Function to extract model_name from various API response formats
function extractModelNameFromCallback($data) {
    // Check multiple possible locations in the API response
    if (isset($data['model_name']) && !empty($data['model_name'])) {
        return sanitizeDOMText($data['model_name']);
    }
    
    if (isset($data['model']) && !empty($data['model'])) {
        return sanitizeDOMText($data['model']);
    }
    
    if (isset($data['data']['model_name']) && !empty($data['data']['model_name'])) {
        return sanitizeDOMText($data['data']['model_name']);
    }
    
    if (isset($data['data']['model']) && !empty($data['data']['model'])) {
        return sanitizeDOMText($data['data']['model']);
    }
    
    // Check in data.data array (common API.Box format)
    if (isset($data['data']['data']) && is_array($data['data']['data'])) {
        foreach ($data['data']['data'] as $item) {
            if (isset($item['model_name']) && !empty($item['model_name'])) {
                return sanitizeDOMText($item['model_name']);
            }
            if (isset($item['model']) && !empty($item['model'])) {
                return sanitizeDOMText($item['model']);
            }
        }
    }
    
    // Check in direct data array
    if (isset($data['data']) && is_array($data['data']) && !isset($data['data']['data'])) {
        foreach ($data['data'] as $item) {
            if (is_array($item)) {
                if (isset($item['model_name']) && !empty($item['model_name'])) {
                    return sanitizeDOMText($item['model_name']);
                }
                if (isset($item['model']) && !empty($item['model'])) {
                    return sanitizeDOMText($item['model']);
                }
            }
        }
    }
    
    return null;
}

// Function to extract image_url from various API response formats
function extractImageUrlFromCallback($data) {
    // Check multiple possible locations in the API response
    if (isset($data['image_url']) && !empty($data['image_url'])) {
        return $data['image_url'];
    }
    
    if (isset($data['cover_url']) && !empty($data['cover_url'])) {
        return $data['cover_url'];
    }
    
    if (isset($data['cover_image']) && !empty($data['cover_image'])) {
        return $data['cover_image'];
    }
    
    if (isset($data['data']['image_url']) && !empty($data['data']['image_url'])) {
        return $data['data']['image_url'];
    }
    
    if (isset($data['data']['cover_url']) && !empty($data['data']['cover_url'])) {
        return $data['data']['cover_url'];
    }
    
    // Check in data.data array (common API.Box format) - get first item's image
    if (isset($data['data']['data']) && is_array($data['data']['data']) && !empty($data['data']['data'])) {
        $firstItem = $data['data']['data'][0];
        if (isset($firstItem['image_url']) && !empty($firstItem['image_url'])) {
            return $firstItem['image_url'];
        }
        if (isset($firstItem['cover_url']) && !empty($firstItem['cover_url'])) {
            return $firstItem['cover_url'];
        }
    }
    
    // Check in direct data array
    if (isset($data['data']) && is_array($data['data']) && !isset($data['data']['data'])) {
        foreach ($data['data'] as $item) {
            if (is_array($item)) {
                if (isset($item['image_url']) && !empty($item['image_url'])) {
                    return $item['image_url'];
                }
                if (isset($item['cover_url']) && !empty($item['cover_url'])) {
                    return $item['cover_url'];
                }
            }
        }
    }
    
    return null;
}

// Function to download and store image files locally
function downloadAndStoreImage($imageUrl, $taskId) {
    if (empty($imageUrl) || !filter_var($imageUrl, FILTER_VALIDATE_URL)) {
        return null;
    }
    
    // Create image storage directory
    $imageDir = 'uploads/track_covers/';
    if (!is_dir($imageDir)) {
        mkdir($imageDir, 0755, true);
    }
    
    // Generate filename
    $extension = pathinfo(parse_url($imageUrl, PHP_URL_PATH), PATHINFO_EXTENSION) ?: 'jpg';
    // Validate extension
    $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
    if (!in_array(strtolower($extension), $allowedExtensions)) {
        $extension = 'jpg'; // Default to jpg if extension is invalid
    }
    
    $filename = "track_{$taskId}_" . time() . ".{$extension}";
    $localPath = $imageDir . $filename;
    $webPath = '/uploads/track_covers/' . $filename;
    
    // Skip if file already exists (check by task_id pattern)
    $existingFiles = glob($imageDir . "track_{$taskId}_*");
    if (!empty($existingFiles)) {
        // Use the most recent existing file
        $mostRecent = end($existingFiles);
        return '/' . str_replace('\\', '/', $mostRecent);
    }
    
    // Download the file using cURL for better error handling
    if (function_exists('curl_init')) {
        $ch = curl_init($imageUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
        
        $imageContent = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);
        
        if ($error || $httpCode !== 200 || $imageContent === false) {
            $errorMsg = "Failed to download image from: $imageUrl (HTTP $httpCode, Error: $error)";
            error_log($errorMsg);
            // Also log to callback log file
            $logFile = 'callback_log.txt';
            file_put_contents($logFile, "❌ $errorMsg for task $taskId\n", FILE_APPEND | LOCK_EX);
            return null;
        }
    } else {
        // Fallback to file_get_contents
        $context = stream_context_create([
            'http' => [
                'timeout' => 60,
                'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
                'follow_location' => true
            ]
        ]);
        
        $imageContent = @file_get_contents($imageUrl, false, $context);
        
        if ($imageContent === false) {
            error_log("Failed to download image from: $imageUrl");
            return null;
        }
    }
    
    // Validate that it's actually an image
    $imageInfo = @getimagesizefromstring($imageContent);
    if ($imageInfo === false) {
        error_log("Downloaded content is not a valid image: $imageUrl");
        return null;
    }
    
    // Save the file
    if (file_put_contents($localPath, $imageContent, LOCK_EX)) {
        // Set proper permissions
        chmod($localPath, 0644);
        
        // Log the download
        $logFile = 'callback_log.txt';
        file_put_contents($logFile, "✅ Downloaded and stored image: $webPath (from $imageUrl)\n", FILE_APPEND | LOCK_EX);
        
        return $webPath;
    } else {
        error_log("Failed to save image to: $localPath");
        return null;
    }
}

// Function to download and store audio files locally
function downloadAndStoreAudio($audioUrl, $taskId, $type = 'main', $variationIndex = null) {
    if (empty($audioUrl) || !filter_var($audioUrl, FILTER_VALIDATE_URL)) {
        return null;
    }
    
    // CRITICAL FIX: musicfile.api.box URLs need .mp3 extension
    // If URL doesn't have an extension, add .mp3
    $parsedUrl = parse_url($audioUrl);
    $path = $parsedUrl['path'] ?? '';
    $extension = pathinfo($path, PATHINFO_EXTENSION);
    
    if (empty($extension) && strpos($audioUrl, 'musicfile.api.box') !== false) {
        $audioUrl .= '.mp3';
    }
    
    // Create audio storage directory
    $audioDir = 'audio_files/';
    if (!is_dir($audioDir)) {
        mkdir($audioDir, 0755, true);
    }
    
    // Generate filename
    $fileExtension = $extension ?: 'mp3';
    if ($type === 'variation' && $variationIndex !== null) {
        $filename = "{$taskId}_variation_{$variationIndex}.{$fileExtension}";
    } elseif ($type === 'stem' && $variationIndex !== null) {
        $filename = "{$taskId}_stem_{$variationIndex}.{$fileExtension}";
    } else {
        $filename = "{$taskId}.{$fileExtension}";
    }
    
    $localPath = $audioDir . $filename;
    $webPath = '/audio_files/' . $filename;
    
    // Download the file using cURL for better reliability
    $ch = curl_init($audioUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 300);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Accept: audio/mpeg, audio/*, */*',
        'Referer: https://musicfile.api.box/'
    ]);
    
    $audioContent = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $error = curl_error($ch);
    curl_close($ch);
    
    if ($httpCode !== 200 || $audioContent === false || !empty($error) || strlen($audioContent) < 1024) {
        error_log("Failed to download audio from: $audioUrl (HTTP $httpCode, Error: $error, Size: " . strlen($audioContent ?? '') . ")");
        return null;
    }
    
    // Save the file
    if (file_put_contents($localPath, $audioContent, LOCK_EX)) {
        // Set proper permissions
        chmod($localPath, 0644);
        
        // Log the download
        $downloadLog = [
            'timestamp' => date('Y-m-d H:i:s'),
            'action' => 'audio_downloaded',
            'task_id' => $taskId,
            'original_url' => $audioUrl,
            'local_path' => $localPath,
            'web_path' => $webPath,
            'file_size' => strlen($audioContent),
            'type' => $type,
            'variation_index' => $variationIndex
        ];
        
        $downloadLogFile = 'logs/audio_downloads.log';
        file_put_contents($downloadLogFile, json_encode($downloadLog) . "\n", FILE_APPEND | LOCK_EX);
        
        return $webPath;
    }
    
    return null;
}

// Helper function to get original prompt and title from database
function getTrackOriginalData($taskId) {
    $pdo = getDBConnection();
    if (!$pdo) return ['prompt' => null, 'title' => null, 'title_user_modified' => false];
    
    try {
        $stmt = $pdo->prepare("SELECT prompt, title, title_user_modified FROM music_tracks WHERE task_id = ?");
        $stmt->execute([$taskId]);
        $track = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($track) {
            return [
                'prompt' => $track['prompt'] ?? null,
                'title' => $track['title'] ?? null,
                'title_user_modified' => (bool)($track['title_user_modified'] ?? false)
            ];
        }
    } catch (Exception $e) {
        error_log("Error fetching original track data: " . $e->getMessage());
    }
    
    return ['prompt' => null, 'title' => null, 'title_user_modified' => false];
}

// Function to parse prompt and extract metadata
function parsePromptForMetadata($prompt) {
    if (empty($prompt)) return [];
    
    $metadata = [];
    $promptLower = strtolower($prompt);
    
    // Extract BPM/Tempo from prompt as FALLBACK (API.box doesn't provide BPM in callbacks)
    // We prioritize API BPM if available, but use prompt BPM as fallback since API doesn't provide it
    // Validate BPM is in reasonable range (40-300 BPM)
    if (preg_match('/bpm[:\s]+(\d+)/i', $prompt, $matches)) {
        $bpm = intval($matches[1]);
        if ($bpm >= 40 && $bpm <= 300) {
            $metadata['bpm'] = $bpm;
        }
    } elseif (preg_match('/(\d+)\s*bpm/i', $prompt, $matches)) {
        $bpm = intval($matches[1]);
        if ($bpm >= 40 && $bpm <= 300) {
            $metadata['bpm'] = $bpm;
        }
    } elseif (preg_match('/tempo[:\s]+(\d+)/i', $prompt, $matches)) {
        $bpm = intval($matches[1]);
        if ($bpm >= 40 && $bpm <= 300) {
            $metadata['bpm'] = $bpm;
        }
    }
    
    // Extract Genre - handle "Style:" prefix and multiple genres separated by "/"
    if (preg_match('/style[:\s]+([^:]+?)(?:\s*[:\-]|$)/i', $prompt, $matches)) {
        $styleText = trim($matches[1]);
        // Split by "/" to get multiple genres
        $styleGenres = array_map('trim', explode('/', $styleText));
        foreach ($styleGenres as $styleGenre) {
            $styleGenre = trim($styleGenre);
            if (!empty($styleGenre)) {
                // Normalize common variations
                $styleGenre = str_replace(['psy ', 'psy-'], 'psy', strtolower($styleGenre));
                $metadata['genre'] = ucwords($styleGenre);
                break; // Use first genre found
            }
        }
    }
    
    // Also check for genre keywords (expanded list including psytrance, psy chill, etc.)
    $genres = ['psytrance', 'psy trance', 'psy-chill', 'psy chill', 'electronic', 'house', 'techno', 'pop', 'hip hop', 'rock', 'jazz', 'classical', 'ambient', 'trance', 'dubstep', 'r&b', 'reggae', 'country', 'folk', 'blues', 'funk', 'disco', 'drum & bass', 'drum and bass', 'progressive', 'chillout', 'lofi', 'lo-fi', 'edm', 'trap', 'dance', 'indie', 'alternative', 'metal', 'punk', 'soul', 'gospel', 'latin', 'world', 'experimental'];
    foreach ($genres as $genre) {
        if (preg_match('/\b' . preg_quote($genre, '/') . '\b/i', $promptLower)) {
            $metadata['genre'] = ucwords($genre);
            break;
        }
    }
    
    // Extract Mood - handle "Theme:" prefix and common mood words
    if (preg_match('/theme[:\s]+([^:]+?)(?:\s*[:\-]|$)/i', $prompt, $matches)) {
        $themeText = strtolower(trim($matches[1]));
        // Check if theme contains mood keywords
        $moods = ['happy', 'sad', 'energetic', 'chill', 'relaxing', 'aggressive', 'melancholic', 'uplifting', 'dark', 'bright', 'peaceful', 'intense', 'calm', 'excited', 'romantic', 'mysterious', 'epic', 'dramatic', 'playful', 'serious', 'neutral', 'love', 'unity', 'frequency'];
        foreach ($moods as $mood) {
            if (strpos($themeText, $mood) !== false) {
                // Map theme words to moods
                if ($mood === 'love' || $mood === 'romantic') {
                    $metadata['mood'] = 'Romantic';
                } elseif ($mood === 'unity' || $mood === 'peaceful') {
                    $metadata['mood'] = 'Peaceful';
                } elseif ($mood === 'frequency' || $mood === 'energetic') {
                    $metadata['mood'] = 'Energetic';
                } else {
                    $metadata['mood'] = ucfirst($mood);
                }
                break;
            }
        }
    }
    
    // Also check for mood keywords directly
    $moods = ['happy', 'sad', 'energetic', 'chill', 'relaxing', 'aggressive', 'melancholic', 'uplifting', 'dark', 'bright', 'peaceful', 'intense', 'calm', 'excited', 'romantic', 'mysterious', 'epic', 'dramatic', 'playful', 'serious', 'neutral'];
    if (!isset($metadata['mood'])) {
        foreach ($moods as $mood) {
            if (preg_match('/\b' . preg_quote($mood, '/') . '\b/i', $promptLower)) {
                $metadata['mood'] = ucfirst($mood);
                break;
            }
        }
    }
    
    // Extract Key - handle "Key:" prefix and formats like "6B – D Major" or "D Major"
    // First, try to extract numerical key (e.g., "6B", "1A", "12A")
    if (preg_match('/key[:\s]+([0-9]+[A-G]?)\s*[–\-]?\s*([A-G][#b]?\s*(?:major|minor|maj|min))/i', $prompt, $matches)) {
        $numericalKey = trim($matches[1]); // e.g., "6B"
        $keyText = trim($matches[2]); // e.g., "D Major"
        // Normalize
        $keyText = preg_replace('/\s+/', ' ', $keyText);
        $keyText = str_replace(['maj', 'min'], ['major', 'minor'], strtolower($keyText));
        $metadata['key'] = ucwords($keyText);
        $metadata['numerical_key'] = $numericalKey; // Store numerical key separately
    } elseif (preg_match('/key[:\s]+(?:[0-9]+[A-G]?\s*[–\-]?\s*)?([A-G][#b]?\s*(?:major|minor|maj|min))/i', $prompt, $matches)) {
        $keyText = trim($matches[1]);
        // Normalize
        $keyText = preg_replace('/\s+/', ' ', $keyText);
        $keyText = str_replace(['maj', 'min'], ['major', 'minor'], strtolower($keyText));
        $metadata['key'] = ucwords($keyText);
    } elseif (preg_match('/\b([A-G][#b]?\s*(?:major|minor|maj|min))\b/i', $prompt, $matches)) {
        $keyText = trim($matches[1]);
        $keyText = preg_replace('/\s+/', ' ', $keyText);
        $keyText = str_replace(['maj', 'min'], ['major', 'minor'], strtolower($keyText));
        $metadata['key'] = ucwords($keyText);
    }
    
    return $metadata;
}

// Function to extract comprehensive metadata
function extractComprehensiveMetadata($data, $originalPrompt = null) {
    // API.box callback structure: data.data.data[0] contains the actual track data
    // Extract BPM from the nested structure first
    $apiBpm = null;
    $apiGenre = null;
    $apiKey = null;
    $apiMood = null;
    
    // Check nested data structure (API.box format)
    if (isset($data['data']['data']) && is_array($data['data']['data']) && !empty($data['data']['data'])) {
        $trackData = $data['data']['data'][0]; // First variation
        $apiBpm = $trackData['bpm'] ?? $trackData['tempo'] ?? null;
        $apiGenre = $trackData['genre'] ?? $trackData['tags'] ?? null;
        if (is_string($apiGenre)) {
            // If tags is a string like "heavy bass, synth melodies...", extract first genre
            $tagsArray = explode(',', $apiGenre);
            $apiGenre = trim($tagsArray[0]) ?? null;
        }
        $apiKey = $trackData['key'] ?? $trackData['musical_key'] ?? null;
        $apiMood = $trackData['mood'] ?? null;
    }
    
    // Fallback to top-level data
    if (!$apiBpm) {
        $apiBpm = $data['bpm'] ?? $data['tempo'] ?? null;
    }
    if (!$apiGenre) {
        $apiGenre = $data['genre'] ?? (is_array($data['tags'] ?? null) ? ($data['tags'][0] ?? null) : $data['tags']) ?? null;
    }
    if (!$apiKey) {
        $apiKey = $data['key'] ?? $data['musical_key'] ?? null;
    }
    if (!$apiMood) {
        $apiMood = $data['mood'] ?? null;
    }
    
    // Log what API returned
    $logFile = 'callback_log.txt';
    file_put_contents($logFile, "🔍 API Metadata Check:\n", FILE_APPEND | LOCK_EX);
    file_put_contents($logFile, "  - API Genre: " . ($apiGenre ?: 'NOT PROVIDED') . "\n", FILE_APPEND | LOCK_EX);
    file_put_contents($logFile, "  - API BPM: " . ($apiBpm ?: 'NOT PROVIDED') . " (type: " . gettype($apiBpm) . ")\n", FILE_APPEND | LOCK_EX);
    file_put_contents($logFile, "  - API Key: " . ($apiKey ?: 'NOT PROVIDED') . "\n", FILE_APPEND | LOCK_EX);
    file_put_contents($logFile, "  - API Mood: " . ($apiMood ?: 'NOT PROVIDED') . "\n", FILE_APPEND | LOCK_EX);
    
    // IMPORTANT: Check if we're accidentally using duration as BPM
    if (isset($data['data']['data']) && is_array($data['data']['data']) && !empty($data['data']['data'])) {
        $trackData = $data['data']['data'][0];
        $duration = $trackData['duration'] ?? null;
        file_put_contents($logFile, "  - ⚠️ Duration from API: " . ($duration ?: 'NOT PROVIDED') . " (this is NOT BPM!)\n", FILE_APPEND | LOCK_EX);
        if ($duration && $apiBpm && abs($duration - $apiBpm) < 1) {
            file_put_contents($logFile, "  - 🚨 ERROR: Duration ($duration) matches BPM ($apiBpm) - this is wrong!\n", FILE_APPEND | LOCK_EX);
        }
    }
    
    // If API didn't provide values, try parsing the prompt
    $parsedMetadata = [];
    if ($originalPrompt) {
        file_put_contents($logFile, "  - Parsing prompt for metadata...\n", FILE_APPEND | LOCK_EX);
        $parsedMetadata = parsePromptForMetadata($originalPrompt);
        file_put_contents($logFile, "  - Parsed from prompt: " . json_encode($parsedMetadata) . "\n", FILE_APPEND | LOCK_EX);
    }
    
    // Use API values if available, otherwise use parsed, otherwise use defaults
    $finalGenre = $apiGenre ?: $parsedMetadata['genre'] ?? 'Electronic';
    // BPM: API.box doesn't provide BPM in callbacks, so we use prompt BPM as fallback
    // Priority: API BPM > Prompt BPM > null
    $finalBpm = $apiBpm ?? $parsedMetadata['bpm'] ?? null;
    $finalKey = $apiKey ?: $parsedMetadata['key'] ?? 'C major';
    $finalMood = $apiMood ?: $parsedMetadata['mood'] ?? 'neutral';
    
    // Validate BPM is in reasonable range (40-300 BPM)
    if ($finalBpm !== null) {
        $finalBpm = intval($finalBpm);
        if ($finalBpm < 40 || $finalBpm > 300) {
            file_put_contents($logFile, "  - ⚠️ WARNING: Invalid BPM value ($finalBpm) - out of range 40-300, will randomize\n", FILE_APPEND | LOCK_EX);
            $finalBpm = null; // Reset to null so it gets randomized
        }
    }
    
    // Validate numerical key against actual API key
    $finalNumericalKey = null;
    if (isset($parsedMetadata['numerical_key']) && !empty($parsedMetadata['numerical_key'])) {
        $promptNumericalKey = $parsedMetadata['numerical_key'];
        $promptMusicalKey = $parsedMetadata['key'] ?? null;
        
        // Normalize keys for comparison (case-insensitive, handle variations)
        $normalizeKey = function($key) {
            if (empty($key)) return '';
            $key = strtolower(trim($key));
            $key = str_replace(['maj', 'min'], ['major', 'minor'], $key);
            $key = preg_replace('/\s+/', ' ', $key);
            return ucwords($key);
        };
        
        $normalizedApiKey = $normalizeKey($finalKey);
        $normalizedPromptKey = $normalizeKey($promptMusicalKey);
        
        // Only use numerical key if the API's actual key matches what was in the prompt
        if ($normalizedApiKey === $normalizedPromptKey) {
            $finalNumericalKey = $promptNumericalKey;
            file_put_contents($logFile, "  - ✅ Numerical key validated: $promptNumericalKey matches API key $normalizedApiKey\n", FILE_APPEND | LOCK_EX);
        } else {
            file_put_contents($logFile, "  - ⚠️ WARNING: Numerical key mismatch!\n", FILE_APPEND | LOCK_EX);
            file_put_contents($logFile, "    Prompt said: $promptNumericalKey – $normalizedPromptKey\n", FILE_APPEND | LOCK_EX);
            file_put_contents($logFile, "    API actually generated: $normalizedApiKey\n", FILE_APPEND | LOCK_EX);
            file_put_contents($logFile, "    → Numerical key NOT stored (using API's actual key as source of truth)\n", FILE_APPEND | LOCK_EX);
            // Don't store numerical key if there's a mismatch - API's actual key is the truth
        }
    }
    
    // If BPM still not set, DO NOT randomize - leave it as null
    // API.box doesn't provide BPM in callbacks, so we use prompt BPM as fallback
    if (!$finalBpm) {
        file_put_contents($logFile, "  - ⚠️ BPM NOT FOUND - leaving as NULL (will not display on card)\n", FILE_APPEND | LOCK_EX);
        file_put_contents($logFile, "  - BPM was not in API response and not found in prompt\n", FILE_APPEND | LOCK_EX);
    } else {
        file_put_contents($logFile, "  - ✅ BPM found: $finalBpm (source: " . ($apiBpm ? 'API' : 'prompt') . ")\n", FILE_APPEND | LOCK_EX);
    }
    
    file_put_contents($logFile, "  - FINAL VALUES: Genre=$finalGenre, BPM=" . ($finalBpm ?: 'NULL (not found)') . ", Key=$finalKey, Mood=$finalMood" . ($finalNumericalKey ? ", NumericalKey=$finalNumericalKey" : "") . "\n", FILE_APPEND | LOCK_EX);
    file_put_contents($logFile, "  - SOURCE: API=" . ($apiGenre || $apiBpm || $apiKey || $apiMood ? 'YES' : 'NO') . ", PARSED=" . (!empty($parsedMetadata) ? 'YES' : 'NO') . ", DEFAULTS=" . (!$apiGenre && !isset($parsedMetadata['genre']) ? 'YES' : 'NO') . "\n", FILE_APPEND | LOCK_EX);
    
    return [
        // Raw callback data for debugging
        'raw_callback' => $data,
        
        // Basic music information
        // DEFAULT VALUES (used if API doesn't provide and prompt doesn't contain):
        // - genre: 'Electronic'
        // - bpm: random(80-160) if not found
        // - key: 'C major'
        // - mood: 'neutral'
        // - time_signature: '4/4'
        // - energy: 'medium'
        // - instruments: ['synthesizer']
        'genre' => $finalGenre,
        'style' => $data['style'] ?? '',
        'tags' => extractTagsFromCallback($data) ?? ($data['tags'] ?? []),
        'bpm' => $finalBpm, // API BPM if available, otherwise from prompt (API.box doesn't provide BPM in callbacks)
        'key' => $finalKey,
        'numerical_key' => $finalNumericalKey, // Only set if validated against API's actual key
        'time_signature' => $data['time_signature'] ?? '4/4',
        'mood' => $finalMood,
        'energy' => $data['energy'] ?? 'medium',
        'instruments' => $data['instruments'] ?? ['synthesizer'],
        
        // Audio Quality Metrics
        'audio_quality' => [
            'bitrate' => $data['bitrate'] ?? $data['audio_bitrate'] ?? null,
            'sample_rate' => $data['sample_rate'] ?? $data['audio_sample_rate'] ?? null,
            'format' => $data['format'] ?? $data['audio_format'] ?? 'mp3',
            'channels' => $data['channels'] ?? $data['audio_channels'] ?? 2,
            'file_size' => $data['file_size'] ?? null,
            'duration' => $data['duration'] ?? null,
            'audio_quality_score' => $data['audio_quality_score'] ?? null
        ],
        
        // Generation Parameters
        'generation_parameters' => [
            'model_version' => $data['model_version'] ?? $data['model'] ?? 'v3',
            'model_name' => $data['model_name'] ?? null,
            'temperature' => $data['temperature'] ?? null,
            'top_p' => $data['top_p'] ?? null,
            'max_tokens' => $data['max_tokens'] ?? null,
            'seed' => $data['seed'] ?? null,
            'parameters' => $data['parameters'] ?? $data['generation_params'] ?? []
        ],
        
        // Processing Information
        'processing_info' => [
            'processing_time' => $data['processing_time'] ?? $data['generation_time'] ?? null,
            'queue_time' => $data['queue_time'] ?? null,
            'total_time' => $data['total_time'] ?? null,
            'start_time' => $data['start_time'] ?? null,
            'end_time' => $data['end_time'] ?? null,
            'server_id' => $data['server_id'] ?? null,
            'worker_id' => $data['worker_id'] ?? null
        ],
        
        // Cost Information
        'cost_info' => [
            'api_cost' => $data['api_cost'] ?? $data['cost'] ?? null,
            'credits_used' => $data['credits_used'] ?? null,
            'currency' => $data['currency'] ?? 'USD',
            'pricing_tier' => $data['pricing_tier'] ?? null,
            'cost_per_second' => $data['cost_per_second'] ?? null
        ],
        
        // Waveform Data
        'waveform_data' => [
            'waveform' => $data['waveform'] ?? $data['waveform_data'] ?? null,
            'waveform_url' => $data['waveform_url'] ?? null,
            'waveform_points' => $data['waveform_points'] ?? null,
            'waveform_resolution' => $data['waveform_resolution'] ?? null
        ],
        
        // Spectrum Analysis
        'spectrum_analysis' => [
            'spectrum' => $data['spectrum'] ?? $data['spectrum_data'] ?? null,
            'spectrum_url' => $data['spectrum_url'] ?? null,
            'frequency_data' => $data['frequency_data'] ?? null,
            'spectral_centroid' => $data['spectral_centroid'] ?? null,
            'spectral_rolloff' => $data['spectral_rolloff'] ?? null,
            'spectral_bandwidth' => $data['spectral_bandwidth'] ?? null
        ],
        
        // Audio Segments
        'audio_segments' => [
            'segments' => $data['segments'] ?? $data['audio_segments'] ?? [],
            'verse_timestamps' => $data['verse_timestamps'] ?? null,
            'chorus_timestamps' => $data['chorus_timestamps'] ?? null,
            'bridge_timestamps' => $data['bridge_timestamps'] ?? null,
            'intro_timestamps' => $data['intro_timestamps'] ?? null,
            'outro_timestamps' => $data['outro_timestamps'] ?? null,
            'section_labels' => $data['section_labels'] ?? null
        ],
        
        // Error Details (for failed generations)
        'error_details' => [
            'error_code' => $data['error_code'] ?? $data['code'] ?? null,
            'error_message' => $data['error_message'] ?? $data['msg'] ?? null,
            'error_type' => $data['error_type'] ?? null,
            'error_category' => $data['error_category'] ?? null,
            'error_suggestions' => $data['error_suggestions'] ?? null,
            'retry_available' => $data['retry_available'] ?? null,
            'error_timestamp' => $data['error_timestamp'] ?? null
        ],
        
        // Additional Analysis
        'audio_analysis' => [
            'loudness' => $data['loudness'] ?? null,
            'dynamic_range' => $data['dynamic_range'] ?? null,
            'peak_amplitude' => $data['peak_amplitude'] ?? null,
            'rms_amplitude' => $data['rms_amplitude'] ?? null,
            'zero_crossing_rate' => $data['zero_crossing_rate'] ?? null,
            'harmonic_content' => $data['harmonic_content'] ?? null,
            'percussive_content' => $data['percussive_content'] ?? null
        ],
        
        // System Information
        'system_info' => [
            'created_with' => 'AI Music Generation',
            'version' => '3.0',
            'callback_processed' => date('Y-m-d H:i:s'),
            'api_version' => $data['api_version'] ?? null,
            'api_endpoint' => $data['api_endpoint'] ?? null
        ]
    ];
}

// Only process callback if this is a direct request
if ($isDirectRequest) {
    $logFile = 'callback_log.txt';
    $input = file_get_contents('php://input');
    
try {
    // Parse the callback data
    $data = json_decode($input, true);
    
    if (!$data) {
        throw new Exception('Invalid JSON data received');
    }
    
    // Log the parsed data
    file_put_contents($logFile, "Parsed data: " . json_encode($data, JSON_PRETTY_PRINT) . "\n", FILE_APPEND | LOCK_EX);
    
    // Handle different types of callbacks
    if (isset($data['task_id'])) {
        $taskId = $data['task_id'];
        $status = $data['status'] ?? 'unknown';
        
        // Fetch the original track to get the prompt and existing title
        $originalData = getTrackOriginalData($taskId);
        $originalPrompt = $originalData['prompt'];
        $existingTitle = $originalData['title'];
        $titleUserModified = $originalData['title_user_modified'] ?? false;
        
        file_put_contents($logFile, "📝 Original prompt: " . substr($originalPrompt ?: 'N/A', 0, 100) . "...\n", FILE_APPEND | LOCK_EX);
        file_put_contents($logFile, "📝 Existing title: " . ($existingTitle ?: 'N/A') . ($titleUserModified ? " (USER MODIFIED - protected)" : "") . "\n", FILE_APPEND | LOCK_EX);
        
        // Extract comprehensive metadata (pass original prompt for parsing)
        $enhanced_metadata = extractComprehensiveMetadata($data, $originalPrompt);
        
        // Extract title from various possible locations in the API response
        $apiTitle = extractTitleFromCallback($data);
        if ($apiTitle) {
            file_put_contents($logFile, "Found API title for task $taskId: $apiTitle\n", FILE_APPEND | LOCK_EX);
        }
        
        // Only use API title if track doesn't have a user-provided title
        // Preserve user titles unless they're empty or default values
        // Also check title_user_modified flag - if set, NEVER overwrite
        $title = null;
        if ($titleUserModified) {
            // User explicitly modified the title - never overwrite
            $title = $existingTitle;
            file_put_contents($logFile, "🔒 User-modified title protected: $title\n", FILE_APPEND | LOCK_EX);
        } elseif ($existingTitle && 
            $existingTitle !== '' && 
            $existingTitle !== 'Untitled Track' && 
            $existingTitle !== 'Generated Track') {
            // Keep the user's title
            $title = $existingTitle;
            file_put_contents($logFile, "✅ Preserving user title: $title\n", FILE_APPEND | LOCK_EX);
        } elseif ($apiTitle) {
            // Use API title if no user title exists
            $title = $apiTitle;
            file_put_contents($logFile, "✅ Using API title: $title\n", FILE_APPEND | LOCK_EX);
        } else {
            // No title available
            file_put_contents($logFile, "⚠️ No title available for task $taskId\n", FILE_APPEND | LOCK_EX);
        }
        
        // Extract duration from various possible locations in the API response
        $duration = extractDurationFromCallback($data);
        if ($duration) {
            file_put_contents($logFile, "Found duration for task $taskId: $duration\n", FILE_APPEND | LOCK_EX);
        }
        
        // Extract tags from various possible locations in the API response
        $tagsRaw = extractTagsFromCallback($data);
        if ($tagsRaw) {
            file_put_contents($logFile, "Found tags for task $taskId: " . (is_array($tagsRaw) ? implode(', ', $tagsRaw) : $tagsRaw) . "\n", FILE_APPEND | LOCK_EX);
        }
        // Format tags properly for storage in tags column (semicolon-separated string)
        $tags = formatTagsForStorage($tagsRaw);
        
        // Extract model_name from various possible locations in the API response
        $modelName = extractModelNameFromCallback($data);
        if ($modelName) {
            file_put_contents($logFile, "Found model_name for task $taskId: $modelName\n", FILE_APPEND | LOCK_EX);
        }
        
        // Extract lyrics from various possible locations in the API response
        $lyrics = '';
        
        // First, check if we have lyrics in the prompt field (this is where API.Box actually sends them)
        if (isset($data['data']['data']) && is_array($data['data']['data'])) {
            foreach ($data['data']['data'] as $item) {
                if (isset($item['prompt']) && !empty($item['prompt'])) {
                    $lyrics = $item['prompt'];
                    file_put_contents($logFile, "Found lyrics in prompt field for task $taskId\n", FILE_APPEND | LOCK_EX);
                    break;
                }
            }
        }
        
        // Fallback to other possible locations
        if (!$lyrics) {
            if (isset($data['lyrics'])) {
                $lyrics = $data['lyrics'];
            } elseif (isset($data['lyric'])) {
                $lyrics = $data['lyric'];
            } elseif (isset($data['text'])) {
                $lyrics = $data['text'];
            } elseif (isset($data['data']['lyrics'])) {
                $lyrics = $data['data']['lyrics'];
            } elseif (isset($data['data']['lyric'])) {
                $lyrics = $data['data']['lyric'];
            } elseif (isset($data['data']['text'])) {
                $lyrics = $data['data']['text'];
            } elseif (isset($data['data']['data']['lyrics'])) {
                $lyrics = $data['data']['data']['lyrics'];
            } elseif (isset($data['data']['data']['lyric'])) {
                $lyrics = $data['data']['data']['lyric'];
            } elseif (isset($data['data']['data']['text'])) {
                $lyrics = $data['data']['data']['text'];
            } elseif (isset($data['data']['data']) && is_array($data['data']['data'])) {
                // Handle nested array structure from API.Box
                foreach ($data['data']['data'] as $item) {
                    if (isset($item['lyrics']) && !empty($item['lyrics'])) {
                        $lyrics = $item['lyrics'];
                        break;
                    } elseif (isset($item['lyric']) && !empty($item['lyric'])) {
                        $lyrics = $item['lyric'];
                        break;
                    } elseif (isset($item['text']) && !empty($item['text'])) {
                        $lyrics = $item['text'];
                        break;
                    }
                }
            }
        }
        
        // Clean up lyrics if found
        if ($lyrics) {
            // Remove common API formatting
            $lyrics = str_replace(['[Verse]', '[Chorus]', '[Bridge]', '[Outro]', '[Intro]'], '', $lyrics);
            $lyrics = preg_replace('/\[.*?\]/', '', $lyrics); // Remove any [bracketed] content
            $lyrics = trim($lyrics);
            
            // Log that we found lyrics
            file_put_contents($logFile, "Found lyrics for task $taskId: " . substr($lyrics, 0, 100) . "...\n", FILE_APPEND | LOCK_EX);
        } else {
            // Debug: Log the data structure to see why lyrics weren't found
            file_put_contents($logFile, "No lyrics found for task $taskId. Data structure:\n", FILE_APPEND | LOCK_EX);
            file_put_contents($logFile, "Top level keys: " . implode(', ', array_keys($data)) . "\n", FILE_APPEND | LOCK_EX);
            if (isset($data['data'])) {
                file_put_contents($logFile, "Data level keys: " . implode(', ', array_keys($data['data'])) . "\n", FILE_APPEND | LOCK_EX);
                if (isset($data['data']['data']) && is_array($data['data']['data'])) {
                    file_put_contents($logFile, "Data array has " . count($data['data']['data']) . " items\n", FILE_APPEND | LOCK_EX);
                    foreach ($data['data']['data'] as $index => $item) {
                        if (is_array($item)) {
                            file_put_contents($logFile, "Item $index keys: " . implode(', ', array_keys($item)) . "\n", FILE_APPEND | LOCK_EX);
                        }
                    }
                }
            }
            file_put_contents($logFile, "No lyrics found for task $taskId\n", FILE_APPEND | LOCK_EX);
        }
        
        // Download and store audio files locally
        $audioUrl = $data['audio_url'] ?? null;
        $videoUrl = $data['video_url'] ?? null;
        
        // Extract and download image URL - MUST download and store locally, NO external links
        $imageUrl = extractImageUrlFromCallback($data);
        $localImageUrl = null;
        if ($imageUrl && $status === 'complete') {
            $localImageUrl = downloadAndStoreImage($imageUrl, $taskId);
            if ($localImageUrl) {
                file_put_contents($logFile, "✅ Downloaded and stored image locally: $localImageUrl for task $taskId\n", FILE_APPEND | LOCK_EX);
            } else {
                file_put_contents($logFile, "❌ Failed to download image from: $imageUrl for task $taskId - NOT saving external URL\n", FILE_APPEND | LOCK_EX);
                // DO NOT fallback to external URL - only save if we successfully downloaded it
                $localImageUrl = null;
            }
        }
        
        $localAudioUrl = null;
        $localVideoUrl = null;
        
        if ($audioUrl && $status === 'complete') {
            $localAudioUrl = downloadAndStoreAudio($audioUrl, $taskId, 'main');
            file_put_contents($logFile, "Downloaded main audio to: " . ($localAudioUrl ?: 'failed') . " for task $taskId\n", FILE_APPEND | LOCK_EX);
        }
        
        if ($videoUrl && $status === 'complete') {
            $localVideoUrl = downloadAndStoreAudio($videoUrl, $taskId, 'video');
            file_put_contents($logFile, "Downloaded video to: " . ($localVideoUrl ?: 'failed') . " for task $taskId\n", FILE_APPEND | LOCK_EX);
        }
        
        // Ensure tags are stored in metadata as well (for consistency)
        // Override metadata tags with formatted tags if we have them
        // Store the FULL tags string in metadata so it can be extracted later
        if ($tags) {
            $enhanced_metadata['tags'] = $tags;
            file_put_contents($logFile, "✅ Storing tags in metadata: $tags\n", FILE_APPEND | LOCK_EX);
        } else {
            file_put_contents($logFile, "⚠️ No tags to store in metadata for task $taskId\n", FILE_APPEND | LOCK_EX);
        }
        
        $metadata = json_encode($enhanced_metadata);
        
        // Update the track with local URLs (fallback to external if download failed), including title, duration, tags, model_name, and image_url
        // Tags will be stored in both the tags column AND metadata.tags
        updateMusicTrack($taskId, $status, $localAudioUrl ?: $audioUrl, $localVideoUrl ?: $videoUrl, $lyrics, $metadata, $duration, $title, $tags, $modelName, $localImageUrl);
        
        // Store the task result for later retrieval (backup)
        $resultFile = "task_results/{$taskId}.json";
        
        // Ensure the directory exists
        if (!is_dir('task_results')) {
            mkdir('task_results', 0755, true);
        }
        
        // Save the result
        file_put_contents($resultFile, json_encode($data, JSON_PRETTY_PRINT), LOCK_EX);
        
        // Log the saved result
        file_put_contents($logFile, "Task result saved to: $resultFile\n", FILE_APPEND | LOCK_EX);
        
        // Return success response
        echo json_encode([
            'success' => true,
            'message' => 'Callback processed successfully',
            'task_id' => $taskId,
            'status' => $status,
            'local_audio_url' => $localAudioUrl,
            'local_video_url' => $localVideoUrl
        ]);
        
    } elseif (isset($data['id'])) {
        // Alternative format with 'id' instead of 'task_id'
        $taskId = $data['id'];
        $status = $data['status'] ?? 'unknown';
        
        // Fetch original prompt for metadata parsing
        $originalData = getTrackOriginalData($taskId);
        $originalPrompt = $originalData['prompt'];
        $existingTitle = $originalData['title'];
        $titleUserModified = $originalData['title_user_modified'] ?? false;
        
        // Extract comprehensive metadata (pass original prompt for parsing)
        $enhanced_metadata = extractComprehensiveMetadata($data, $originalPrompt);
        
        // Extract title from various possible locations in the API response
        $apiTitle = extractTitleFromCallback($data);
        if ($apiTitle) {
            file_put_contents($logFile, "Found API title for task $taskId: $apiTitle\n", FILE_APPEND | LOCK_EX);
        }
        
        // Only use API title if track doesn't have a user-provided title
        // Also check title_user_modified flag - if set, NEVER overwrite
        $title = null;
        if ($titleUserModified) {
            // User explicitly modified the title - never overwrite
            $title = $existingTitle;
            file_put_contents($logFile, "🔒 User-modified title protected: $title\n", FILE_APPEND | LOCK_EX);
        } elseif ($existingTitle && 
            $existingTitle !== '' && 
            $existingTitle !== 'Untitled Track' && 
            $existingTitle !== 'Generated Track') {
            $title = $existingTitle;
            file_put_contents($logFile, "✅ Preserving user title: $title\n", FILE_APPEND | LOCK_EX);
        } elseif ($apiTitle) {
            $title = $apiTitle;
            file_put_contents($logFile, "✅ Using API title: $title\n", FILE_APPEND | LOCK_EX);
        }
        
        // Extract duration from various possible locations in the API response
        $duration = extractDurationFromCallback($data);
        if ($duration) {
            file_put_contents($logFile, "Found duration for task $taskId: $duration\n", FILE_APPEND | LOCK_EX);
        }
        
        // Extract tags from various possible locations in the API response
        $tagsRaw = extractTagsFromCallback($data);
        if ($tagsRaw) {
            file_put_contents($logFile, "Found tags for task $taskId: " . (is_array($tagsRaw) ? implode(', ', $tagsRaw) : $tagsRaw) . "\n", FILE_APPEND | LOCK_EX);
        }
        // Format tags properly for storage in tags column (semicolon-separated string)
        $tags = formatTagsForStorage($tagsRaw);
        
        // Extract model_name from various possible locations in the API response
        $modelName = extractModelNameFromCallback($data);
        if ($modelName) {
            file_put_contents($logFile, "Found model_name for task $taskId: $modelName\n", FILE_APPEND | LOCK_EX);
        }
        
        // Extract lyrics from various possible locations in the API response
        $lyrics = '';
        
        // First, check if we have lyrics in the prompt field (this is where API.Box actually sends them)
        if (isset($data['data']['data']) && is_array($data['data']['data'])) {
            foreach ($data['data']['data'] as $item) {
                if (isset($item['prompt']) && !empty($item['prompt'])) {
                    $lyrics = $item['prompt'];
                    file_put_contents($logFile, "Found lyrics in prompt field for task $taskId\n", FILE_APPEND | LOCK_EX);
                    break;
                }
            }
        }
        
        // Fallback to other possible locations
        if (!$lyrics) {
            if (isset($data['lyrics'])) {
                $lyrics = $data['lyrics'];
            } elseif (isset($data['lyric'])) {
                $lyrics = $data['lyric'];
            } elseif (isset($data['text'])) {
                $lyrics = $data['text'];
            } elseif (isset($data['data']['lyrics'])) {
                $lyrics = $data['data']['lyrics'];
            } elseif (isset($data['data']['lyric'])) {
                $lyrics = $data['data']['lyric'];
            } elseif (isset($data['data']['text'])) {
                $lyrics = $data['data']['text'];
            } elseif (isset($data['data']['data']['lyrics'])) {
                $lyrics = $data['data']['data']['lyrics'];
            } elseif (isset($data['data']['data']['lyric'])) {
                $lyrics = $data['data']['data']['lyric'];
            } elseif (isset($data['data']['data']['text'])) {
                $lyrics = $data['data']['data']['text'];
            } elseif (isset($data['data']['data']) && is_array($data['data']['data'])) {
                // Handle nested array structure from API.Box
                foreach ($data['data']['data'] as $item) {
                    if (isset($item['lyrics']) && !empty($item['lyrics'])) {
                        $lyrics = $item['lyrics'];
                        break;
                    } elseif (isset($item['lyric']) && !empty($item['lyric'])) {
                        $lyrics = $item['lyric'];
                        break;
                    } elseif (isset($item['text']) && !empty($item['text'])) {
                        $lyrics = $item['text'];
                        break;
                    }
                }
            }
        }
        
        // Clean up lyrics if found
        if ($lyrics) {
            // Remove common API formatting
            $lyrics = str_replace(['[Verse]', '[Chorus]', '[Bridge]', '[Outro]', '[Intro]'], '', $lyrics);
            $lyrics = preg_replace('/\[.*?\]/', '', $lyrics); // Remove any [bracketed] content
            $lyrics = trim($lyrics);
            
            // Log that we found lyrics
            file_put_contents($logFile, "Found lyrics for task $taskId: " . substr($lyrics, 0, 100) . "...\n", FILE_APPEND | LOCK_EX);
        } else {
            file_put_contents($logFile, "No lyrics found for task $taskId\n", FILE_APPEND | LOCK_EX);
        }
        
        // Download and store audio files locally
        $audioUrl = $data['audio_url'] ?? null;
        $videoUrl = $data['video_url'] ?? null;
        
        // Extract and download image URL - MUST download and store locally, NO external links
        $imageUrl = extractImageUrlFromCallback($data);
        $localImageUrl = null;
        if ($imageUrl && $status === 'complete') {
            $localImageUrl = downloadAndStoreImage($imageUrl, $taskId);
            if ($localImageUrl) {
                file_put_contents($logFile, "✅ Downloaded and stored image locally: $localImageUrl for task $taskId\n", FILE_APPEND | LOCK_EX);
            } else {
                file_put_contents($logFile, "❌ Failed to download image from: $imageUrl for task $taskId - NOT saving external URL\n", FILE_APPEND | LOCK_EX);
                // DO NOT fallback to external URL - only save if we successfully downloaded it
                $localImageUrl = null;
            }
        }
        
        $localAudioUrl = null;
        $localVideoUrl = null;
        
        if ($audioUrl && $status === 'complete') {
            $localAudioUrl = downloadAndStoreAudio($audioUrl, $taskId, 'main');
            file_put_contents($logFile, "Downloaded main audio to: " . ($localAudioUrl ?: 'failed') . " for task $taskId\n", FILE_APPEND | LOCK_EX);
        }
        
        if ($videoUrl && $status === 'complete') {
            $localVideoUrl = downloadAndStoreAudio($videoUrl, $taskId, 'video');
            file_put_contents($logFile, "Downloaded video to: " . ($localVideoUrl ?: 'failed') . " for task $taskId\n", FILE_APPEND | LOCK_EX);
        }
        
        $metadata = json_encode($enhanced_metadata);
        
        // Update the track with local URLs (fallback to external if download failed), including title, duration, tags, model_name, image_url, and lyrics
        updateMusicTrack($taskId, $status, $localAudioUrl ?: $audioUrl, $localVideoUrl ?: $videoUrl, $lyrics, $metadata, $duration, $title, $tags, $modelName, $localImageUrl);
        
        // Store the task result for later retrieval (backup)
        $resultFile = "task_results/{$taskId}.json";
        
        // Ensure the directory exists
        if (!is_dir('task_results')) {
            mkdir('task_results', 0755, true);
        }
        
        // Save the result
        file_put_contents($resultFile, json_encode($data, JSON_PRETTY_PRINT), LOCK_EX);
        
        // Log the saved result
        file_put_contents($logFile, "Task result saved to: $resultFile\n", FILE_APPEND | LOCK_EX);
        
        // Return success response
        echo json_encode([
            'success' => true,
            'message' => 'Callback processed successfully',
            'task_id' => $taskId,
            'status' => $status,
            'local_audio_url' => $localAudioUrl,
            'local_video_url' => $localVideoUrl
        ]);
        
    } elseif (isset($data['code']) && $data['code'] == 531) {
        // Handle API error 531 - generation failed
        $taskId = $data['data']['task_id'] ?? 'unknown';
        $errorMsg = $data['msg'] ?? 'Generation failed';
        
        // Sanitize error message - remove any references to API.Box or supplier names
        $errorMsg = preg_replace('/\b(API\.Box|api\.box|API\.box|not found on API\.Box|not found in API\.Box|Task not found in API\.Box)\b/i', '', $errorMsg);
        $errorMsg = trim($errorMsg);
        if (empty($errorMsg)) {
            $errorMsg = 'Generation failed';
        }
        
        // Extract comprehensive error metadata
        $enhanced_metadata = extractComprehensiveMetadata($data);
        // Sanitize metadata msg field if it exists
        if (isset($enhanced_metadata['msg'])) {
            $enhanced_metadata['msg'] = preg_replace('/\b(API\.Box|api\.box|API\.box|not found on API\.Box|not found in API\.Box|Task not found in API\.Box)\b/i', '', $enhanced_metadata['msg']);
            $enhanced_metadata['msg'] = trim($enhanced_metadata['msg']);
            if (empty($enhanced_metadata['msg'])) {
                $enhanced_metadata['msg'] = 'Generation failed';
            }
        }
        $metadata = json_encode($enhanced_metadata);
        
        // Update database with failed status
        updateMusicTrack($taskId, 'failed', null, null, null, $metadata);
        
        // Log the error
        file_put_contents($logFile, "API Error 531: $errorMsg for task $taskId\n", FILE_APPEND | LOCK_EX);
        
        // Return success response (we handled the error)
        echo json_encode([
            'success' => true,
            'message' => 'Error callback processed',
            'task_id' => $taskId,
            'status' => 'failed',
            'error' => $errorMsg
        ]);
        
    } elseif (isset($data['code']) && $data['code'] == 400) {
        // Handle API error 400 - content violation (e.g., artist name restrictions)
        $taskId = $data['data']['task_id'] ?? 'unknown';
        $errorMsg = $data['msg'] ?? 'Content violation detected';
        
        // Sanitize error message - remove any references to API.Box or supplier names
        $errorMsg = preg_replace('/\b(API\.Box|api\.box|API\.box|not found on API\.Box|not found in API\.Box|Task not found in API\.Box)\b/i', '', $errorMsg);
        $errorMsg = trim($errorMsg);
        if (empty($errorMsg)) {
            $errorMsg = 'Content violation detected';
        }
        
        // Extract comprehensive error metadata
        $enhanced_metadata = extractComprehensiveMetadata($data);
        // Sanitize metadata msg field if it exists
        if (isset($enhanced_metadata['msg'])) {
            $enhanced_metadata['msg'] = preg_replace('/\b(API\.Box|api\.box|API\.box|not found on API\.Box|not found in API\.Box|Task not found in API\.Box)\b/i', '', $enhanced_metadata['msg']);
            $enhanced_metadata['msg'] = trim($enhanced_metadata['msg']);
            if (empty($enhanced_metadata['msg'])) {
                $enhanced_metadata['msg'] = 'Content violation detected';
            }
        }
        $metadata = json_encode($enhanced_metadata);
        
        // Update database with failed status and store the specific error message
        updateMusicTrack($taskId, 'failed', null, null, null, $metadata);
        
        // Log the error
        file_put_contents($logFile, "API Error 400: $errorMsg for task $taskId\n", FILE_APPEND | LOCK_EX);
        
        // Return success response (we handled the error)
        echo json_encode([
            'success' => true,
            'message' => 'Content violation callback processed',
            'task_id' => $taskId,
            'status' => 'failed',
            'error' => $errorMsg,
            'error_type' => 'content_violation'
        ]);
        
    } elseif (isset($data['code']) && isset($data['data']['callbackType'])) {
        // Handle API format with callbackType (both success and error cases)
        $taskId = $data['data']['task_id'] ?? 'unknown';
        $callbackType = $data['data']['callbackType'];
        $audioData = $data['data']['data'] ?? [];
        
        file_put_contents($logFile, "Processing API format with callbackType: code={$data['code']}, callbackType=$callbackType, taskId=$taskId\n", FILE_APPEND | LOCK_EX);
        
        // Handle error cases (code 400, 531, etc.)
        if ($data['code'] != 200) {
            $errorMsg = $data['msg'] ?? 'API error occurred';
            
            // Sanitize error message - remove any references to API.Box or supplier names FIRST
            $errorMsg = preg_replace('/\b(API\.Box|api\.box|API\.box|not found on API\.Box|not found in API\.Box|Task not found in API\.Box)\b/i', '', $errorMsg);
            $errorMsg = trim($errorMsg);
            if (empty($errorMsg)) {
                $errorMsg = 'API error occurred';
            }
            
            // Determine error type for better user feedback
            $errorType = 'api_error';
            if ($data['code'] == 400) {
                $errorType = 'content_violation';
                // Common content violations
                if (stripos($errorMsg, 'artist name') !== false) {
                    $errorMsg = "Content violation: " . $errorMsg . " (Avoid mentioning existing artists, bands, or copyrighted material)";
                } elseif (stripos($errorMsg, 'copyright') !== false) {
                    $errorMsg = "Content violation: " . $errorMsg . " (Avoid copyrighted material, song titles, or lyrics)";
                } elseif (stripos($errorMsg, 'inappropriate') !== false) {
                    $errorMsg = "Content violation: " . $errorMsg . " (Avoid explicit or inappropriate content)";
                }
            } elseif ($data['code'] == 531) {
                $errorType = 'generation_failed';
                $errorMsg = "Generation failed: " . $errorMsg . " (Technical issue - try again or contact support)";
            }
            
            // Extract comprehensive error metadata
            $enhanced_metadata = extractComprehensiveMetadata($data);
            // Sanitize metadata msg field if it exists
            if (isset($enhanced_metadata['msg'])) {
                $enhanced_metadata['msg'] = preg_replace('/\b(API\.Box|api\.box|API\.box|not found on API\.Box|not found in API\.Box|Task not found in API\.Box)\b/i', '', $enhanced_metadata['msg']);
                $enhanced_metadata['msg'] = trim($enhanced_metadata['msg']);
                if (empty($enhanced_metadata['msg'])) {
                    $enhanced_metadata['msg'] = $errorMsg;
                }
            }
            $metadata = json_encode($enhanced_metadata);
            
            // Update database with failed status and enhanced error message
            updateMusicTrack($taskId, 'failed', null, null, null, $metadata);
            
            // Log the error with enhanced details
            file_put_contents($logFile, "API Error {$data['code']} ($errorType): $errorMsg for task $taskId\n", FILE_APPEND | LOCK_EX);
            
            // Return success response (we handled the error)
            echo json_encode([
                'success' => true,
                'message' => 'Error callback processed',
                'task_id' => $taskId,
                'status' => 'failed',
                'error' => $errorMsg,
                'error_type' => $errorType
            ]);
            return;
        }
        
        // Handle success case (code 200)
        if ($callbackType === 'complete' && !empty($audioData)) {
            // Sanitize all incoming data from DOM to prevent apostrophe issues
            $audioData = sanitizeDOMArray($audioData);
            $data = sanitizeDOMArray($data);
            
            // Extract comprehensive metadata
            $enhanced_metadata = extractComprehensiveMetadata($data);
            
            // Extract title using helper function (checks multiple locations)
            $title = extractTitleFromCallback($data);
            if ($title) {
                file_put_contents($logFile, "Found title for task $taskId: $title\n", FILE_APPEND | LOCK_EX);
            }
            
            // CRITICAL FIX: Also check all audioData items for title (even if they don't have audio_url yet)
            // This ensures we capture the title even if the first item doesn't have audio_url
            if (!$title && is_array($audioData) && !empty($audioData)) {
                foreach ($audioData as $audio) {
                    if (isset($audio['title']) && !empty($audio['title']) && trim($audio['title']) !== '') {
                        $title = sanitizeDOMText($audio['title']);
                        file_put_contents($logFile, "Found title in audioData for task $taskId: $title\n", FILE_APPEND | LOCK_EX);
                        break;
                    }
                }
            }
            
            // Extract duration using helper function
            $duration = extractDurationFromCallback($data);
            if ($duration) {
                file_put_contents($logFile, "Found duration for task $taskId: $duration\n", FILE_APPEND | LOCK_EX);
            }
            
            // Extract tags using helper function
            $tagsRaw = extractTagsFromCallback($data);
            if ($tagsRaw) {
                file_put_contents($logFile, "Found tags for task $taskId: " . (is_array($tagsRaw) ? implode(', ', $tagsRaw) : $tagsRaw) . "\n", FILE_APPEND | LOCK_EX);
            }
            // Format tags properly for storage in tags column (semicolon-separated string)
            $tags = formatTagsForStorage($tagsRaw);
            
            // Extract model_name using helper function
            $modelName = extractModelNameFromCallback($data);
            if ($modelName) {
                file_put_contents($logFile, "Found model_name for task $taskId: $modelName\n", FILE_APPEND | LOCK_EX);
            }
            
            // Extract lyrics from various possible locations in the API response
            // CRITICAL: Lyrics are in the 'prompt' field of each audio item in the data array
            $lyrics = '';
            
            // Priority 1: Check audioData items first (most reliable source for complete callbacks)
            if (is_array($audioData) && !empty($audioData)) {
                foreach ($audioData as $audio) {
                    if (isset($audio['prompt']) && !empty($audio['prompt'])) {
                        $lyrics = $audio['prompt'];
                        file_put_contents($logFile, "✅ Found lyrics in audioData prompt field for task $taskId\n", FILE_APPEND | LOCK_EX);
                        break; // Use first item's lyrics
                    }
                }
            }
            
            // Priority 2: Check data['data']['data'] array (alternative location)
            if (!$lyrics && isset($data['data']['data']) && is_array($data['data']['data'])) {
                foreach ($data['data']['data'] as $item) {
                    if (isset($item['prompt']) && !empty($item['prompt'])) {
                        $lyrics = $item['prompt'];
                        file_put_contents($logFile, "✅ Found lyrics in data.data.data prompt field for task $taskId\n", FILE_APPEND | LOCK_EX);
                        break;
                    }
                }
            }
            
            // Priority 3: Fallback to other possible locations (less common)
            if (!$lyrics) {
                if (isset($data['lyrics']) && !empty($data['lyrics'])) {
                    $lyrics = $data['lyrics'];
                    file_put_contents($logFile, "✅ Found lyrics in data.lyrics for task $taskId\n", FILE_APPEND | LOCK_EX);
                } elseif (isset($data['lyric']) && !empty($data['lyric'])) {
                    $lyrics = $data['lyric'];
                    file_put_contents($logFile, "✅ Found lyrics in data.lyric for task $taskId\n", FILE_APPEND | LOCK_EX);
                } elseif (isset($data['text']) && !empty($data['text'])) {
                    $lyrics = $data['text'];
                    file_put_contents($logFile, "✅ Found lyrics in data.text for task $taskId\n", FILE_APPEND | LOCK_EX);
                } elseif (isset($data['data']['lyrics']) && !empty($data['data']['lyrics'])) {
                    $lyrics = $data['data']['lyrics'];
                    file_put_contents($logFile, "✅ Found lyrics in data.data.lyrics for task $taskId\n", FILE_APPEND | LOCK_EX);
                } elseif (isset($data['data']['lyric']) && !empty($data['data']['lyric'])) {
                    $lyrics = $data['data']['lyric'];
                    file_put_contents($logFile, "✅ Found lyrics in data.data.lyric for task $taskId\n", FILE_APPEND | LOCK_EX);
                } elseif (isset($data['data']['text']) && !empty($data['data']['text'])) {
                    $lyrics = $data['data']['text'];
                    file_put_contents($logFile, "✅ Found lyrics in data.data.text for task $taskId\n", FILE_APPEND | LOCK_EX);
                } elseif (isset($data['data']['data']['lyrics']) && !empty($data['data']['data']['lyrics'])) {
                    $lyrics = $data['data']['data']['lyrics'];
                    file_put_contents($logFile, "✅ Found lyrics in data.data.data.lyrics for task $taskId\n", FILE_APPEND | LOCK_EX);
                } elseif (isset($data['data']['data']['lyric']) && !empty($data['data']['data']['lyric'])) {
                    $lyrics = $data['data']['data']['lyric'];
                    file_put_contents($logFile, "✅ Found lyrics in data.data.data.lyric for task $taskId\n", FILE_APPEND | LOCK_EX);
                } elseif (isset($data['data']['data']['text']) && !empty($data['data']['data']['text'])) {
                    $lyrics = $data['data']['data']['text'];
                    file_put_contents($logFile, "✅ Found lyrics in data.data.data.text for task $taskId\n", FILE_APPEND | LOCK_EX);
                } elseif (isset($data['data']['data']) && is_array($data['data']['data'])) {
                    // Handle nested array structure from API.Box
                    foreach ($data['data']['data'] as $item) {
                        if (isset($item['lyrics']) && !empty($item['lyrics'])) {
                            $lyrics = $item['lyrics'];
                            file_put_contents($logFile, "✅ Found lyrics in nested item.lyrics for task $taskId\n", FILE_APPEND | LOCK_EX);
                            break;
                        } elseif (isset($item['lyric']) && !empty($item['lyric'])) {
                            $lyrics = $item['lyric'];
                            file_put_contents($logFile, "✅ Found lyrics in nested item.lyric for task $taskId\n", FILE_APPEND | LOCK_EX);
                            break;
                        } elseif (isset($item['text']) && !empty($item['text'])) {
                            $lyrics = $item['text'];
                            file_put_contents($logFile, "✅ Found lyrics in nested item.text for task $taskId\n", FILE_APPEND | LOCK_EX);
                            break;
                        }
                    }
                }
            }
            
            // Clean up lyrics if found
            if ($lyrics) {
                // Remove common API formatting
                $lyrics = str_replace(['[Verse]', '[Chorus]', '[Bridge]', '[Outro]', '[Intro]'], '', $lyrics);
                $lyrics = preg_replace('/\[.*?\]/', '', $lyrics); // Remove any [bracketed] content
                $lyrics = trim($lyrics);
                
                // Log that we found and cleaned lyrics
                file_put_contents($logFile, "✅ Found and cleaned lyrics for task $taskId (length: " . strlen($lyrics) . " chars): " . substr($lyrics, 0, 100) . "...\n", FILE_APPEND | LOCK_EX);
            } else {
                // Log detailed debug info when lyrics are not found
                file_put_contents($logFile, "❌ No lyrics found for task $taskId\n", FILE_APPEND | LOCK_EX);
                file_put_contents($logFile, "   Debug: audioData is " . (is_array($audioData) ? "array with " . count($audioData) . " items" : "not an array") . "\n", FILE_APPEND | LOCK_EX);
                if (is_array($audioData) && !empty($audioData)) {
                    $firstItem = $audioData[0];
                    file_put_contents($logFile, "   Debug: First audioData item keys: " . implode(', ', array_keys($firstItem)) . "\n", FILE_APPEND | LOCK_EX);
                    if (isset($firstItem['prompt'])) {
                        file_put_contents($logFile, "   Debug: First item HAS prompt field (length: " . strlen($firstItem['prompt']) . ")\n", FILE_APPEND | LOCK_EX);
                    }
                }
            }
            
            // Extract image URL using helper function - MUST download and store locally, no external links
            $imageUrl = extractImageUrlFromCallback($data);
            $localImageUrl = null;
            if ($imageUrl) {
                $localImageUrl = downloadAndStoreImage($imageUrl, $taskId);
                if ($localImageUrl) {
                    file_put_contents($logFile, "✅ Downloaded and stored image locally: $localImageUrl for task $taskId\n", FILE_APPEND | LOCK_EX);
                } else {
                    file_put_contents($logFile, "❌ Failed to download image from: $imageUrl for task $taskId - NOT saving external URL\n", FILE_APPEND | LOCK_EX);
                    // DO NOT fallback to external URL - only save if we successfully downloaded it
                    $localImageUrl = null;
                }
            }
            
            // Get the first audio file with a valid URL for the main track
            $audioUrl = null;
            
            foreach ($audioData as $audio) {
                // Try different possible audio URL fields
                $audioUrl = $audio['audio_url'] ?? $audio['source_audio_url'] ?? $audio['stream_audio_url'] ?? null;
                
                // CRITICAL FIX: Extract title from audio item even if audioUrl is empty
                // This ensures we capture the title even if the audio file isn't ready yet
                if (!$title && isset($audio['title']) && !empty($audio['title']) && trim($audio['title']) !== '') {
                    $title = sanitizeDOMText($audio['title']);
                    file_put_contents($logFile, "✅ Found title in audioData item for task $taskId: $title\n", FILE_APPEND | LOCK_EX);
                }
                
                if (!empty($audioUrl)) {
                    // If duration wasn't found by helper, try from audio item
                    if (!$duration && isset($audio['duration']) && !empty($audio['duration'])) {
                        $duration = floatval($audio['duration']);
                    }
                    // If tags weren't found by helper, try from audio item
                    if (!$tags && isset($audio['tags']) && !empty($audio['tags'])) {
                        $tags = is_array($audio['tags']) ? $audio['tags'] : [$audio['tags']];
                        $tags = sanitizeDOMArray($tags);
                    }
                    // If model_name wasn't found by helper, try from audio item
                    if (!$modelName && isset($audio['model_name']) && !empty($audio['model_name'])) {
                        $modelName = sanitizeDOMText($audio['model_name']);
                    }
                    // If image_url wasn't found by helper, try from audio item - MUST download locally
                    if (!$imageUrl && isset($audio['image_url']) && !empty($audio['image_url'])) {
                        $imageUrl = $audio['image_url'];
                        $localImageUrl = downloadAndStoreImage($imageUrl, $taskId);
                        if ($localImageUrl) {
                            file_put_contents($logFile, "✅ Downloaded image from audio item to: $localImageUrl for task $taskId\n", FILE_APPEND | LOCK_EX);
                        } else {
                            file_put_contents($logFile, "❌ Failed to download image from audio item: $imageUrl for task $taskId - NOT saving external URL\n", FILE_APPEND | LOCK_EX);
                            // DO NOT fallback to external URL - only save if we successfully downloaded it
                            $localImageUrl = null;
                        }
                    }
                    
                    file_put_contents($logFile, "Found audio URL: $audioUrl for task $taskId\n", FILE_APPEND | LOCK_EX);
                    break;
                }
            }
            
            // Check if this is a stem separation result
            $isStemSeparation = false;
            $pdo = getDBConnection();
            if ($pdo) {
                $stmt = $pdo->prepare("SELECT music_type, metadata FROM music_tracks WHERE task_id = ?");
                $stmt->execute([$taskId]);
                $trackInfo = $stmt->fetch(PDO::FETCH_ASSOC);
                if ($trackInfo) {
                    $metadataCheck = json_decode($trackInfo['metadata'] ?? '{}', true);
                    $isStemSeparation = ($trackInfo['music_type'] === 'stem-separation') || 
                                       (isset($metadataCheck['separationMode']) && $metadataCheck['separationMode'] === 'split_stem');
                }
            }
            
            if ($audioUrl) {
                // Download and store main audio file locally
                $localAudioUrl = downloadAndStoreAudio($audioUrl, $taskId, 'main');
                file_put_contents($logFile, "Downloaded main audio to: " . ($localAudioUrl ?: 'failed') . " for task $taskId\n", FILE_APPEND | LOCK_EX);
                
                // For stem separation, process all stem files
                if ($isStemSeparation && is_array($audioData) && count($audioData) > 1) {
                    file_put_contents($logFile, "🔍 Processing full stem separation with " . count($audioData) . " stems for task $taskId\n", FILE_APPEND | LOCK_EX);
                    
                    $stemFiles = [];
                    $stemIndex = 0;
                    
                    foreach ($audioData as $stem) {
                        $stemAudioUrl = $stem['audio_url'] ?? $stem['source_audio_url'] ?? $stem['stream_audio_url'] ?? null;
                        $stemName = $stem['stem_name'] ?? $stem['name'] ?? $stem['type'] ?? "stem_$stemIndex";
                        
                        if (!empty($stemAudioUrl)) {
                            // Download and store each stem file
                            $localStemUrl = downloadAndStoreAudio($stemAudioUrl, $taskId, 'stem', $stemIndex);
                            if ($localStemUrl) {
                                $stemFiles[] = [
                                    'name' => $stemName,
                                    'url' => $localStemUrl,
                                    'original_url' => $stemAudioUrl,
                                    'index' => $stemIndex
                                ];
                                file_put_contents($logFile, "✅ Downloaded stem '$stemName' to: $localStemUrl\n", FILE_APPEND | LOCK_EX);
                            }
                            $stemIndex++;
                        }
                    }
                    
                    // Store stem files info in metadata
                    if (!empty($stemFiles)) {
                        $enhanced_metadata['stem_files'] = $stemFiles;
                        $enhanced_metadata['stem_count'] = count($stemFiles);
                        file_put_contents($logFile, "✅ Stored " . count($stemFiles) . " stem files in metadata\n", FILE_APPEND | LOCK_EX);
                    }
                }
                
                // CRITICAL FIX: Ensure title is not empty string before passing to updateMusicTrack
                // Convert empty strings to null so updateMusicTrack can handle them properly
                if ($title !== null && trim($title) === '') {
                    $title = null;
                    file_put_contents($logFile, "⚠️ Title was empty string, converting to null for task $taskId\n", FILE_APPEND | LOCK_EX);
                }
                
                // Update database with complete status, local audio URL, duration, title, metadata, image_url, and lyrics
                $metadata = json_encode($enhanced_metadata);
                file_put_contents($logFile, "🔍 About to call updateMusicTrack with: taskId=$taskId, status=complete, audioUrl=" . ($localAudioUrl ?: $audioUrl) . ", duration=$duration, title=" . ($title ?: 'none') . ", imageUrl=" . ($localImageUrl ?: 'none') . ", lyrics=" . ($lyrics ? 'yes' : 'no') . "\n", FILE_APPEND | LOCK_EX);
                
                // Test database connection before update
                $pdo = getDBConnection();
                if ($pdo) {
                    file_put_contents($logFile, "🔍 Database connection test successful\n", FILE_APPEND | LOCK_EX);
                } else {
                    file_put_contents($logFile, "❌ Database connection test failed\n", FILE_APPEND | LOCK_EX);
                }
                
                // CRITICAL: Always update lyrics if we found them, even if empty string (will be cleaned)
                // Pass lyrics as null if empty to avoid overwriting with empty string
                $lyricsToSave = (!empty($lyrics)) ? $lyrics : null;
                
                file_put_contents($logFile, "🔍 Calling updateMusicTrack with lyrics: " . ($lyricsToSave ? "YES (length: " . strlen($lyricsToSave) . ")" : "NULL") . "\n", FILE_APPEND | LOCK_EX);
                
                $updateResult = updateMusicTrack($taskId, 'complete', $localAudioUrl ?: $audioUrl, null, $lyricsToSave, $metadata, $duration, $title, $tags, $modelName, $localImageUrl);
                
                // CRITICAL FIX: If lyrics weren't found in callback but track is complete, 
                // try to extract from saved task_results JSON file as fallback
                if ($updateResult && !$lyricsToSave) {
                    file_put_contents($logFile, "⚠️ Lyrics not found in callback, attempting to extract from task_results JSON...\n", FILE_APPEND | LOCK_EX);
                    $fallbackLyrics = extractLyricsFromTaskResults($taskId);
                    if ($fallbackLyrics) {
                        // Update track with extracted lyrics
                        $pdo = getDBConnection();
                        if ($pdo) {
                            $stmt = $pdo->prepare("UPDATE music_tracks SET lyrics = ? WHERE task_id = ?");
                            if ($stmt->execute([$fallbackLyrics, $taskId])) {
                                file_put_contents($logFile, "✅ Successfully extracted and saved lyrics from task_results JSON for task $taskId\n", FILE_APPEND | LOCK_EX);
                                $lyricsToSave = $fallbackLyrics; // Update for logging
                            }
                        }
                    } else {
                        file_put_contents($logFile, "❌ Could not extract lyrics from task_results JSON for task $taskId\n", FILE_APPEND | LOCK_EX);
                    }
                }
                
                if ($updateResult) {
                    file_put_contents($logFile, "✅ Successfully updated track $taskId to complete with audio URL: " . ($localAudioUrl ?: $audioUrl) . ", duration: $duration" . ($lyricsToSave ? ", lyrics: YES" : ", lyrics: NO") . "\n", FILE_APPEND | LOCK_EX);
                } else {
                    file_put_contents($logFile, "❌ FAILED to update track $taskId to complete in database!\n", FILE_APPEND | LOCK_EX);
                    file_put_contents($logFile, "🔍 Error details: updateMusicTrack returned false\n", FILE_APPEND | LOCK_EX);
                    
                    // Try a direct database update as fallback
                    try {
                        $pdo = getDBConnection();
                        if ($pdo) {
                            $stmt = $pdo->prepare("UPDATE music_tracks SET status = 'complete', audio_url = ?, duration = ?, metadata = ?, updated_at = NOW() WHERE task_id = ?");
                            $directResult = $stmt->execute([$localAudioUrl ?: $audioUrl, $duration, $metadata, $taskId]);
                            
                            if ($directResult) {
                                file_put_contents($logFile, "✅ Direct database update succeeded for task $taskId\n", FILE_APPEND | LOCK_EX);
                            } else {
                                $errorInfo = $stmt->errorInfo();
                                file_put_contents($logFile, "❌ Direct database update also failed: " . json_encode($errorInfo) . "\n", FILE_APPEND | LOCK_EX);
                            }
                        }
                    } catch (Exception $e) {
                        file_put_contents($logFile, "❌ Exception during direct database update: " . $e->getMessage() . "\n", FILE_APPEND | LOCK_EX);
                    }
                }
                
                // Store all variations in the audio_variations table
                $pdo = getDBConnection();
                if ($pdo) {
                    // Get the track ID
                    $stmt = $pdo->prepare("SELECT id FROM music_tracks WHERE task_id = ?");
                    $stmt->execute([$taskId]);
                    $track = $stmt->fetch();
                    
                    if ($track) {
                        $trackId = $track['id'];
                        file_put_contents($logFile, "🔍 Found track ID {$trackId} for task {$taskId}, proceeding to store variations\n", FILE_APPEND | LOCK_EX);
                        
                        // Clear existing variations for this track
                        $stmt = $pdo->prepare("DELETE FROM audio_variations WHERE track_id = ?");
                        $stmt->execute([$trackId]);
                        
                        // CRITICAL FIX: Track actual stored variations count and use sequential index
                        $storedVariationsCount = 0;
                        $variationIndex = 0; // Sequential index for stored variations
                        
                        // Insert each variation
                        foreach ($audioData as $originalIndex => $variation) {
                            // CRITICAL FIX: Check multiple possible audio URL fields (same as main audio extraction)
                            // This ensures we capture variations even if audio_url is empty but source_audio_url or stream_audio_url exists
                            $variationAudioUrl = $variation['audio_url'] ?? $variation['source_audio_url'] ?? $variation['stream_audio_url'] ?? null;
                            
                            if (!empty($variationAudioUrl)) {
                                // Download and store variation audio file locally
                                $localVariationUrl = downloadAndStoreAudio($variationAudioUrl, $taskId, 'variation', $variationIndex);
                                
                                // Extract variation-specific metadata
                                // Generate a meaningful title for the variation if none provided
                                $variationTitle = $variation['title'] ?? null;
                                if (!$variationTitle) {
                                    // Create a descriptive title based on available metadata
                                    $titleParts = [];
                                    if (isset($variation['genre'])) $titleParts[] = $variation['genre'];
                                    if (isset($variation['style'])) $titleParts[] = $variation['style'];
                                    if (isset($variation['mood'])) $titleParts[] = $variation['mood'];
                                    if (isset($variation['energy'])) $titleParts[] = $variation['energy'];
                                    
                                    if (!empty($titleParts)) {
                                        $variationTitle = implode(' ', $titleParts) . ' Variation';
                                    } else {
                                        $variationTitle = "AI Variation " . ($index + 1);
                                    }
                                }
                                
                                $variationMetadata = [
                                    'genre' => $variation['genre'] ?? $variation['tags'][0] ?? null,
                                    'style' => $variation['style'] ?? null,
                                    'bpm' => $variation['bpm'] ?? $variation['tempo'] ?? null,
                                    'key' => $variation['key'] ?? null,
                                    'mood' => $variation['mood'] ?? null,
                                    'energy' => $variation['energy'] ?? null,
                                    'instruments' => $variation['instruments'] ?? null,
                                    'tags' => $variation['tags'] ?? null,
                                    'duration' => $variation['duration'] ?? null,
                                    'title' => $variationTitle
                                ];
                                
                                try {
                                    $stmt = $pdo->prepare("
                                        INSERT INTO audio_variations 
                                        (track_id, variation_index, audio_url, duration, title, tags, image_url, source_audio_url, stream_audio_url, metadata) 
                                        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                                    ");
                                    
                                    $insertResult = $stmt->execute([
                                        $trackId,
                                        $variationIndex,
                                        $localVariationUrl ?: $variationAudioUrl,
                                        $variation['duration'] ?? null,
                                        $variationTitle,
                                        is_array($variation['tags']) ? implode(', ', $variation['tags']) : $variation['tags'],
                                        $variation['image_url'] ?? null,
                                        $variation['source_audio_url'] ?? null,
                                        $variation['stream_audio_url'] ?? null,
                                        json_encode($variationMetadata)
                                    ]);
                                    
                                    if ($insertResult) {
                                        $storedVariationsCount++;
                                        $variationIndex++; // Increment for next stored variation
                                        
                                        file_put_contents($logFile, "✅ Stored variation $variationIndex (original index $originalIndex) for track $taskId with audio URL: " . ($localVariationUrl ?: $variationAudioUrl) . " and metadata: " . json_encode($variationMetadata) . "\n", FILE_APPEND | LOCK_EX);
                                    } else {
                                        $errorInfo = $stmt->errorInfo();
                                        file_put_contents($logFile, "❌ FAILED to insert variation $variationIndex (original index $originalIndex) for track $taskId: " . json_encode($errorInfo) . "\n", FILE_APPEND | LOCK_EX);
                                    }
                                } catch (Exception $e) {
                                    file_put_contents($logFile, "❌ EXCEPTION inserting variation $variationIndex (original index $originalIndex) for track $taskId: " . $e->getMessage() . "\n", FILE_APPEND | LOCK_EX);
                                }
                            } else {
                                // Log when a variation is skipped due to missing audio URL
                                file_put_contents($logFile, "⚠️ Skipping variation at original index $originalIndex for track $taskId - no valid audio URL found (checked audio_url, source_audio_url, stream_audio_url)\n", FILE_APPEND | LOCK_EX);
                            }
                        }
                        
                        // CRITICAL FIX: Update the main track with ACTUAL stored variations count (not total audioData count)
                        // This ensures variation_count matches what's actually in audio_variations table
                        $stmt = $pdo->prepare("UPDATE music_tracks SET variations_count = ?, selected_variation = 0 WHERE id = ?");
                        $stmt->execute([$storedVariationsCount, $trackId]);
                        
                        file_put_contents($logFile, "✅ Stored $storedVariationsCount variations (out of " . count($audioData) . " total audioData items) for track $taskId\n", FILE_APPEND | LOCK_EX);
                    } else {
                        // CRITICAL: Track not found - this is why variations aren't being stored!
                        file_put_contents($logFile, "❌ CRITICAL ERROR: Track not found for task_id {$taskId}! Variations will NOT be stored. This is why the variation button doesn't show!\n", FILE_APPEND | LOCK_EX);
                        file_put_contents($logFile, "🔍 Attempting to find track by checking all recent tracks...\n", FILE_APPEND | LOCK_EX);
                        
                        // Try to find the track - maybe task_id format is different
                        $stmt = $pdo->prepare("SELECT id, task_id, title, created_at FROM music_tracks WHERE task_id LIKE ? OR task_id = ? ORDER BY created_at DESC LIMIT 5");
                        $stmt->execute(["%{$taskId}%", $taskId]);
                        $similarTracks = $stmt->fetchAll(PDO::FETCH_ASSOC);
                        
                        if (!empty($similarTracks)) {
                            file_put_contents($logFile, "🔍 Found " . count($similarTracks) . " similar track(s):\n", FILE_APPEND | LOCK_EX);
                            foreach ($similarTracks as $similar) {
                                file_put_contents($logFile, "   - Track ID: {$similar['id']}, Task ID: {$similar['task_id']}, Title: {$similar['title']}\n", FILE_APPEND | LOCK_EX);
                            }
                        } else {
                            file_put_contents($logFile, "❌ No similar tracks found. Track may not exist in database yet.\n", FILE_APPEND | LOCK_EX);
                        }
                    }
                } else {
                    file_put_contents($logFile, "❌ CRITICAL ERROR: Database connection failed when trying to store variations for task {$taskId}!\n", FILE_APPEND | LOCK_EX);
                }
                
                // Return success response for complete callback
                echo json_encode([
                    'success' => true,
                    'message' => 'Complete callback processed successfully',
                    'task_id' => $taskId,
                    'status' => 'complete',
                    'local_audio_url' => $localAudioUrl ?: $audioUrl,
                    'duration' => $duration
                ]);
                return;
                
            } else {
                // No valid audio URL found
                $enhanced_metadata = extractComprehensiveMetadata($data);
                $metadata = json_encode($enhanced_metadata);
                updateMusicTrack($taskId, 'failed', null, null, null, $metadata);
                file_put_contents($logFile, "No valid audio URL found for task $taskId\n", FILE_APPEND | LOCK_EX);
            }
        } else {
            // Other callback types (text, first, etc.) - extract all available fields and update metadata
            $enhanced_metadata = extractComprehensiveMetadata($data);
            $metadata = json_encode($enhanced_metadata);
            
            // Extract title from various possible locations in the API response
            $title = extractTitleFromCallback($data);
            if ($title) {
                file_put_contents($logFile, "Found title for task $taskId (callbackType: $callbackType): $title\n", FILE_APPEND | LOCK_EX);
            }
            
            // Extract duration from various possible locations in the API response
            $duration = extractDurationFromCallback($data);
            if ($duration) {
                file_put_contents($logFile, "Found duration for task $taskId (callbackType: $callbackType): $duration\n", FILE_APPEND | LOCK_EX);
            }
            
            // Extract tags from various possible locations in the API response
            $tagsRaw = extractTagsFromCallback($data);
            if ($tagsRaw) {
                file_put_contents($logFile, "Found tags for task $taskId (callbackType: $callbackType): " . (is_array($tagsRaw) ? implode(', ', $tagsRaw) : $tagsRaw) . "\n", FILE_APPEND | LOCK_EX);
            }
            // Format tags properly for storage in tags column (semicolon-separated string)
            $tags = formatTagsForStorage($tagsRaw);
            
            // Extract model_name from various possible locations in the API response
            $modelName = extractModelNameFromCallback($data);
            if ($modelName) {
                file_put_contents($logFile, "Found model_name for task $taskId (callbackType: $callbackType): $modelName\n", FILE_APPEND | LOCK_EX);
            }
            
            // IMPORTANT: Extract lyrics from prompt field for "text" and "first" callbacks too
            // Lyrics are often available in early callbacks, not just "complete"
            $lyrics = '';
            if (isset($data['data']['data']) && is_array($data['data']['data'])) {
                foreach ($data['data']['data'] as $item) {
                    if (isset($item['prompt']) && !empty($item['prompt'])) {
                        $lyrics = $item['prompt'];
                        file_put_contents($logFile, "Found lyrics in prompt field for task $taskId (callbackType: $callbackType)\n", FILE_APPEND | LOCK_EX);
                        break;
                    }
                }
            }
            
            // Clean up lyrics if found
            if ($lyrics) {
                // Remove common API formatting
                $lyrics = str_replace(['[Verse]', '[Chorus]', '[Bridge]', '[Outro]', '[Intro]'], '', $lyrics);
                $lyrics = preg_replace('/\[.*?\]/', '', $lyrics); // Remove any [bracketed] content
                $lyrics = trim($lyrics);
                
                file_put_contents($logFile, "Extracted and cleaned lyrics for task $taskId (callbackType: $callbackType): " . substr($lyrics, 0, 100) . "...\n", FILE_APPEND | LOCK_EX);
            } else {
                file_put_contents($logFile, "No lyrics found in prompt field for task $taskId (callbackType: $callbackType)\n", FILE_APPEND | LOCK_EX);
            }
            
            // Update track with lyrics if found, even if status is still processing
            updateMusicTrack($taskId, 'processing', null, null, $lyrics, $metadata, $duration, $title, $tags, $modelName);
            file_put_contents($logFile, "Updated track $taskId to processing (callbackType: $callbackType)" . ($lyrics ? " with lyrics" : "") . "\n", FILE_APPEND | LOCK_EX);
        }
        
        // Store the task result for later retrieval (backup)
        $resultFile = "task_results/{$taskId}.json";
        
        // Ensure the directory exists
        if (!is_dir('task_results')) {
            mkdir('task_results', 0755, true);
        }
        
        // Save the result
        file_put_contents($resultFile, json_encode($data, JSON_PRETTY_PRINT), LOCK_EX);
        
        // Return success response
        echo json_encode([
            'success' => true,
            'message' => 'Callback processed successfully',
            'task_id' => $taskId,
            'status' => $callbackType === 'complete' ? 'complete' : 'processing'
        ]);
        
    } else {
        // Unknown callback format
        file_put_contents($logFile, "Unknown callback format\n", FILE_APPEND | LOCK_EX);
        
        echo json_encode([
            'success' => true,
            'message' => 'Callback received (unknown format)',
            'data' => $data
        ]);
    }
    
} catch (Exception $e) {
    // Log the error
    file_put_contents($logFile, "Error: " . $e->getMessage() . "\n", FILE_APPEND | LOCK_EX);
    
    if ($isDirectRequest) {
        http_response_code(500);
        echo json_encode([
            'success' => false,
            'error' => $e->getMessage()
        ]);
    }
}
} // End of if ($isDirectRequest)
?> 

CasperSecurity Mini