<?php
class ScoreScanner {
    private $scoresPath;
    
    public function __construct($path = '../legacy/Scores/') {
        $this->scoresPath = $path;
    }
    
    public function getAllScores() {
        $scores = [];
        $directories = scandir($this->scoresPath);
        
        foreach ($directories as $dir) {
            if ($dir === '.' || $dir === '..') continue;
            
            // Ignorer les fichiers cachés et non-numériques
            if (strpos($dir, '.') === 0) continue;
            if (!is_numeric($dir)) continue;
            
            $scorePath = $this->scoresPath . $dir;
            if (!is_dir($scorePath)) continue;
            
            $score = $this->getScoreInfo($dir);
            if ($score) {
                $scores[] = $score;
            }
        }
        
        // Trier par ID
        usort($scores, function($a, $b) {
            return intval($a['id']) - intval($b['id']);
        });
        
        return $scores;
    }
    
    public function getScoreInfo($id) {
        $scoreDir = $this->scoresPath . $id;
        $iniFile = $scoreDir . '/score.ini';
        
        if (!file_exists($iniFile)) {
            return null;
        }
        
        $ini = @parse_ini_file($iniFile, true);
        if ($ini === false) {
            return null;
        }
        
        return [
            'id' => $id,
            'name' => $ini['info']['name'] ?? 'Inconnu',
            'compositor' => $ini['info']['compositor'] ?? 'Inconnu',
            'ressource' => $ini['info']['ressource'] ?? null
        ];
    }
    
    public function getScoreDetail($id) {
        $scoreDir = $this->scoresPath . $id;
        
        if (!is_dir($scoreDir)) {
            return null;
        }
        
        $basicInfo = $this->getScoreInfo($id);
        if (!$basicInfo) {
            return null;
        }
        
        $instruments = [];
        
        // New structure: NUM/PIECE/INSTRUMENT/VERSION/
        // First get all piece directories
        $pieceDirs = scandir($scoreDir);
        
        foreach ($pieceDirs as $pieceDir) {
            if ($pieceDir === '.' || $pieceDir === '..' || $pieceDir === 'score.ini') continue;
            if (strpos($pieceDir, '.') === 0) continue;
            if (!is_dir($scoreDir . '/' . $pieceDir)) continue;
            
            // Then get instrument directories
            $instrumentDirs = scandir($scoreDir . '/' . $pieceDir);
            
            foreach ($instrumentDirs as $instrumentId) {
                if ($instrumentId === '.' || $instrumentId === '..') continue;
                if (strpos($instrumentId, '.') === 0) continue;
                if (!is_dir($scoreDir . '/' . $pieceDir . '/' . $instrumentId)) continue;
                
                $instrument = $this->getInstrumentInfo($id, $instrumentId, $pieceDir);
                if ($instrument) {
                    $instruments[] = $instrument;
                }
            }
        }
        
        // Trier instruments par nom
        usort($instruments, function($a, $b) {
            return strcmp($a['title'], $b['title']);
        });
        
        return array_merge($basicInfo, ['instruments' => $instruments]);
    }
    
    private function getInstrumentInfo($scoreId, $instrumentId, $pieceId = null) {
        // New structure: NUM/PIECE/INSTRUMENT/VERSION/
        if ($pieceId !== null) {
            $instrumentPath = $this->scoresPath . $scoreId . '/' . $pieceId . '/' . $instrumentId;
        } else {
            $instrumentPath = $this->scoresPath . $scoreId . '/' . $instrumentId;
        }
        
        if (!is_dir($instrumentPath)) {
            return null;
        }
        
        $title = $this->getInstrumentName($instrumentId);
        $parts = [];
        
        $entries = scandir($instrumentPath);
        foreach ($entries as $entry) {
            if ($entry === '.' || $entry === '..') continue;
            if (strpos($entry, '.') === 0) continue;
            
            $partPath = $instrumentPath . '/' . $entry;
            if (!is_dir($partPath)) continue;
            
            $part = $this->getPartInfo($scoreId, $instrumentId, $entry, $pieceId);
            if ($part) {
                $parts[] = $part;
            }
        }
        
        // Trier les parties par numéro
        usort($parts, function($a, $b) {
            return intval($a['id']) - intval($b['id']);
        });
        
        if (empty($parts)) {
            return null;
        }
        
        return [
            'id' => $instrumentId,
            'title' => $title,
            'piece' => $pieceId,
            'parts' => $parts
        ];
    }
    
    private function getPartInfo($scoreId, $instrumentId, $partId, $pieceId = null) {
        // New structure: NUM/PIECE/INSTRUMENT/VERSION/
        if ($pieceId !== null) {
            $partPath = $this->scoresPath . $scoreId . '/' . $pieceId . '/' . $instrumentId . '/' . $partId;
        } else {
            $partPath = $this->scoresPath . $scoreId . '/' . $instrumentId . '/' . $partId;
        }
        
        if (!is_dir($partPath)) {
            return null;
        }
        
        $files = [];
        $entries = scandir($partPath);
        
        foreach ($entries as $entry) {
            if ($entry === '.' || $entry === '..') continue;
            if (strpos($entry, '.') === 0) continue;
            
            $filePath = $partPath . '/' . $entry;
            if (!is_file($filePath)) continue;
            
            // Vérifier que c'est un PDF
            if (strtolower(pathinfo($entry, PATHINFO_EXTENSION)) !== 'pdf') continue;
            
            $relativePath = $pieceId !== null 
                ? "$scoreId/$pieceId/$instrumentId/$partId/$entry"
                : "$scoreId/$instrumentId/$partId/$entry";
            
            $parsed = $this->parseFilename(pathinfo($entry, PATHINFO_FILENAME));
            
            $files[] = [
                'name' => pathinfo($entry, PATHINFO_FILENAME),
                'filename' => $entry,
                'path' => $relativePath,
                'part' => $parsed['part'],
                'key' => $parsed['key'],
                'clef' => $parsed['clef'],
                'variant' => $parsed['variant']
            ];
        }
        
        if (empty($files)) {
            return null;
        }
        
        // Trier les fichiers par nom
        usort($files, function($a, $b) {
            return strcmp($a['name'], $b['name']);
        });
        
        return [
            'id' => $partId,
            'files' => $files
        ];
    }
    
    private function getInstrumentName($code) {
        $names = [
            'cla' => 'Clarinette',
            'flu' => 'Flûte',
            'trb' => 'Trombone',
            'pic' => 'Piccolo',
            'per' => 'Percussions',
            'htb' => 'Hautbois',
            'trp' => 'Trompette',
            'dir' => 'Direction',
            'sax' => 'Sax Alto',
            'sat' => 'Sax Ténor',
            'sab' => 'Sax Baryton',
            'cor' => 'Cor',
            'eup' => 'Euphonium',
            'bas' => 'Basson',
            'cba' => 'Contrebasse',
            'crn' => 'Cornet',
            'coa' => 'Cor Anglais',
            'clb' => 'Clarinette Basse',
            'har' => 'Harpe',
            'pia' => 'Piano',
            'tub' => 'Tuba',
            'sup' => 'Parties supplémentaires',
            'par' => 'Parties'
        ];
        
        return $names[$code] ?? $code;
    }
    
    private function parseFilename(string $filename): array {
        $result = [
            'part' => null,
            'key' => null,
            'clef' => null,
            'variant' => null
        ];
        
        // Normalize: replace - by _ and lowercase
        $name = strtolower(str_replace('-', '_', $filename));
        
        // Extract clef (clesol, clefa)
        if (preg_match('/(clesol|clefa)$/', $name, $m)) {
            $result['clef'] = $m[1];
            $name = preg_replace('/_(clesol|clefa)$/', '', $name);
        }
        
        // Extract variant (solo, etc.)
        $variants = ['solo', 'default'];
        foreach ($variants as $variant) {
            if (strpos($name, $variant) !== false) {
                $result['variant'] = $variant;
                $name = str_replace('_' . $variant, '', $name);
                break;
            }
        }
        
        // Extract key (tonalités: sib, mib, fa, do, etc.)
        $keys = ['sib', 'mib', 'reb', 'dob', 'solb', 'lab', 'mibemol', 'sibemol'];
        foreach ($keys as $key) {
            if (preg_match('/_' . $key . '/', $name)) {
                $result['key'] = $key;
                $name = preg_replace('/_' . $key . '/', '', $name);
                break;
            }
        }
        
        // If no compound keys found, try single keys
        if ($result['key'] === null) {
            $singleKeys = ['fa', 'do', 'ut', 'sol', 're', 'mi', 'si'];
            foreach ($singleKeys as $key) {
                if (preg_match('/_' . $key . '(?:_|$)/', $name)) {
                    $result['key'] = $key;
                    $name = preg_replace('/_' . $key . '/', '', $name);
                    break;
                }
            }
        }
        
        // Extract part number (1, 2, 1_et_2, etc.) - should be last
        // Handle formats: 1, 2, 1_et_2
        if (preg_match('/_(\d+(?:_\w+)*)$/', $name, $m)) {
            $result['part'] = $m[1];
            $name = preg_replace('/_\d+(?:_\w+)*$/', '', $name);
        }
        
        return $result;
    }
    
    // API Methods
    
    public function listScores(): array {
        return $this->getAllScores();
    }
    
    public function getScore(string $id): ?array {
        return $this->getScoreDetail($id);
    }
    
    public function getPieces(string $scoreId): array {
        $scoreDir = $this->scoresPath . $scoreId;
        $iniFile = $scoreDir . '/score.ini';
        
        if (!file_exists($iniFile)) {
            return [];
        }
        
        $ini = @parse_ini_file($iniFile, true);
        if ($ini === false) {
            return [];
        }
        
        $pieces = [];
        
        // Check if [pieces] section exists
        if (isset($ini['pieces'])) {
            $piecesSection = $ini['pieces'];
            $count = intval($piecesSection['count'] ?? 1);
            
            for ($i = 1; $i <= $count; $i++) {
                $pieces[] = [
                    'id' => $i,
                    'name' => $piecesSection[$i] ?? "Pièce $i"
                ];
            }
        }
        
        return $pieces;
    }

    public function getInstruments(string $scoreId, ?string $pieceId = null): array {
        $scoreDir = $this->scoresPath . $scoreId;
        
        if (!is_dir($scoreDir)) {
            return [];
        }
        
        $instruments = [];
        
        // New structure: NUM/PIECE/INSTRUMENT/VERSION/
        $pieceDirs = scandir($scoreDir);
        
        foreach ($pieceDirs as $pieceDir) {
            if ($pieceDir === '.' || $pieceDir === '..' || $pieceDir === 'score.ini') continue;
            if (strpos($pieceDir, '.') === 0) continue;
            if (!is_dir($scoreDir . '/' . $pieceDir)) continue;
            
            // Filter by piece if specified
            if ($pieceId !== null && $pieceDir !== $pieceId) continue;
            
            $instrumentDirs = scandir($scoreDir . '/' . $pieceDir);
            
            foreach ($instrumentDirs as $instrumentId) {
                if ($instrumentId === '.' || $instrumentId === '..') continue;
                if (strpos($instrumentId, '.') === 0) continue;
                if (!is_dir($scoreDir . '/' . $pieceDir . '/' . $instrumentId)) continue;
                
                $instrument = $this->getInstrumentInfo($scoreId, $instrumentId, $pieceDir);
                if ($instrument) {
                    $instruments[] = $instrument;
                }
            }
        }
        
        // Sort by title
        usort($instruments, function($a, $b) {
            return strcmp($a['title'], $b['title']);
        });
        
        return $instruments;
    }
    
    public function createScore(string $id, string $name, string $compositor, array $pieces = []): array {
        $scoreDir = $this->scoresPath . $id;
        
        if (is_dir($scoreDir)) {
            return ['success' => false, 'error' => 'Score already exists'];
        }
        
        if (!mkdir($scoreDir, 0755, true)) {
            return ['success' => false, 'error' => 'Failed to create directory'];
        }
        
        // Security: Sanitize inputs to prevent INI injection
        $name = $this->sanitizeIniValue($name);
        $compositor = $this->sanitizeIniValue($compositor);
        
        $pieceCount = count($pieces) > 0 ? count($pieces) : 1;
        $iniContent = "[info]\nname = $name\ncompositor = $compositor\n\n[pieces]\ncount = $pieceCount\n";
        
        // Add piece names if provided
        foreach ($pieces as $piece) {
            $num = $piece['number'];
            $pieceName = $this->sanitizeIniValue($piece['name']);
            $iniContent .= "{$num} = $pieceName\n";
        }
        
        if (file_put_contents($scoreDir . '/score.ini', $iniContent) === false) {
            return ['success' => false, 'error' => 'Failed to create score.ini'];
        }
        
        return [
            'success' => true,
            'score' => [
                'id' => $id,
                'name' => $name,
                'compositor' => $compositor
            ]
        ];
    }
    
    private function sanitizeIniValue(string $value): string {
        // Remove newlines and carriage returns to prevent INI injection
        return str_replace(["\n", "\r", "\x00"], '', $value);
    }
    
    public function updateScore(string $scoreId, ?string $name, ?string $compositor): array {
        $scoreDir = $this->scoresPath . $scoreId;
        $iniFile = $scoreDir . '/score.ini';
        
        if (!file_exists($iniFile)) {
            return ['success' => false, 'error' => 'Score not found'];
        }
        
        $ini = @parse_ini_file($iniFile, true);
        if ($ini === false) {
            return ['success' => false, 'error' => 'Failed to parse score.ini'];
        }
        
        // Update values
        if ($name !== null) {
            $ini['info']['name'] = $name;
        }
        if ($compositor !== null) {
            $ini['info']['compositor'] = $compositor;
        }
        
        // Rebuild ini content
        $content = "[info]\n";
        $content .= "name = " . ($ini['info']['name'] ?? '') . "\n";
        $content .= "compositor = " . ($ini['info']['compositor'] ?? '') . "\n\n";
        $content .= "[pieces]\n";
        
        if (isset($ini['pieces'])) {
            foreach ($ini['pieces'] as $key => $value) {
                $content .= "$key = $value\n";
            }
        }
        
        if (file_put_contents($iniFile, $content) === false) {
            return ['success' => false, 'error' => 'Failed to write score.ini'];
        }
        
        return ['success' => true];
    }
    
    public function deleteScore(string $scoreId): array {
        $scoreDir = $this->scoresPath . $scoreId;
        
        if (!is_dir($scoreDir)) {
            return ['success' => false, 'error' => 'Score not found'];
        }
        
        // Recursive delete
        $this->deleteDirectory($scoreDir);
        
        return ['success' => true];
    }
    
    private function deleteDirectory(string $dir): void {
        if (!is_dir($dir)) return;
        
        $items = scandir($dir);
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') continue;
            
            $path = $dir . '/' . $item;
            if (is_dir($path)) {
                $this->deleteDirectory($path);
            } else {
                unlink($path);
            }
        }
        
        rmdir($dir);
    }
    
    public function uploadPdf(string $scoreId, array $file, string $piece, string $instrument, string $version, string $key = '', string $clef = '', string $variant = '', string $part = '1'): array {
        $scoreDir = $this->scoresPath . $scoreId;
        
        if (!is_dir($scoreDir)) {
            return ['success' => false, 'error' => 'Score not found'];
        }
        
        // Security: Validate file upload
        if (!isset($file['tmp_name']) || !isset($file['name'])) {
            return ['success' => false, 'error' => 'No file uploaded'];
        }
        
        // Security: Check file upload errors
        if ($file['error'] !== UPLOAD_ERR_OK) {
            return ['success' => false, 'error' => 'Upload error: ' . $file['error']];
        }
        
        // Security: Validate MIME type
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mimeType = finfo_file($finfo, $file['tmp_name']);
        finfo_close($finfo);
        
        if ($mimeType !== 'application/pdf') {
            return ['success' => false, 'error' => 'Invalid file type. Only PDF files are allowed'];
        }
        
        // Security: Validate extension
        $originalExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        if ($originalExtension !== 'pdf') {
            return ['success' => false, 'error' => 'Invalid file extension. Only .pdf files are allowed'];
        }
        
        // Security: Check magic bytes (PDF starts with %PDF)
        $handle = fopen($file['tmp_name'], 'rb');
        if ($handle) {
            $header = fread($handle, 4);
            fclose($handle);
            if ($header !== '%PDF') {
                return ['success' => false, 'error' => 'Invalid PDF file header'];
            }
        }
        
        // Security: Check file size (max 20MB)
        $maxSize = 20 * 1024 * 1024; // 20MB
        if ($file['size'] > $maxSize) {
            return ['success' => false, 'error' => 'File too large. Maximum size is 20MB'];
        }
        
        // Create directory structure: scoreId/piece/instrument/version
        $targetDir = $scoreDir . '/' . $piece . '/' . $instrument . '/' . $version;
        
        if (!is_dir($targetDir)) {
            if (!mkdir($targetDir, 0755, true)) {
                return ['success' => false, 'error' => 'Failed to create directory'];
            }
        }
        
        // Map instrument code to name
        $instrumentNames = [
            'dir' => 'direction', 'pic' => 'piccolo', 'flu' => 'flute', 'cla' => 'clarinette',
            'clb' => 'clarinette_basse', 'sax' => 'saxophone_alto', 'sat' => 'saxophone_tenor',
            'sab' => 'saxophone_baryton', 'coa' => 'cor_anglais', 'htb' => 'hautbois',
            'bas' => 'basson', 'cor' => 'cor', 'trp' => 'trompette', 'crn' => 'cornet',
            'trb' => 'trombone', 'eup' => 'euphonium', 'tub' => 'tuba', 'cba' => 'contrebasse',
            'per' => 'percussion', 'pia' => 'piano', 'har' => 'harpe'
        ];
        
        $instName = $instrumentNames[$instrument] ?? $instrument;
        
        // Build filename: instrument_variant_key_clef_part.pdf
        $filenameParts = [$instName];
        if (!empty($variant)) {
            $filenameParts[] = $variant;
        }
        if (!empty($key)) {
            $filenameParts[] = $key;
        }
        if (!empty($clef)) {
            $filenameParts[] = $clef;
        }
        $filenameParts[] = $part;
        
        $filename = implode('_', $filenameParts) . '.pdf';
        
        $targetPath = $targetDir . '/' . $filename;
        
        // Use copy for CLI testing, move_uploaded_file for real uploads
        if (is_uploaded_file($file['tmp_name'])) {
            $result = move_uploaded_file($file['tmp_name'], $targetPath);
        } else {
            $result = copy($file['tmp_name'], $targetPath);
        }
        
        if ($result) {
            return ['success' => true, 'path' => "$scoreId/$piece/$instrument/$version/$filename"];
        }
        
        return ['success' => false, 'error' => 'Failed to move uploaded file'];
    }

    public function getScoreFiles(string $scoreId): array {
        $scoreDir = $this->scoresPath . $scoreId;
        
        if (!is_dir($scoreDir)) {
            return ['success' => false, 'error' => 'Score not found'];
        }
        
        $tree = $this->buildFileTree($scoreDir, $scoreId);
        
        return ['success' => true, 'files' => $tree];
    }

    private function buildFileTree(string $dir, string $scoreId, string $relativePath = ''): array {
        $items = [];
        $directories = scandir($dir);
        
        foreach ($directories as $item) {
            if ($item === '.' || $item === '..') continue;
            if ($item === 'score.ini') continue;
            
            $fullPath = $dir . '/' . $item;
            $itemRelativePath = $relativePath ? $relativePath . '/' . $item : $item;
            
            if (is_dir($fullPath)) {
                $children = $this->buildFileTree($fullPath, $scoreId, $itemRelativePath);
                $items[] = [
                    'name' => $item,
                    'path' => $itemRelativePath,
                    'type' => 'folder',
                    'children' => $children
                ];
            } else {
                $items[] = [
                    'name' => $item,
                    'path' => $itemRelativePath,
                    'type' => 'file'
                ];
            }
        }
        
        return $items;
    }

    public function deleteScoreFile(string $scoreId, string $filePath): array {
        $scoreDir = $this->scoresPath . $scoreId;
        $fullPath = $scoreDir . '/' . $filePath;
        
        if (!file_exists($fullPath)) {
            return ['success' => false, 'error' => 'File not found'];
        }
        
        if (is_dir($fullPath)) {
            return ['success' => false, 'error' => 'Cannot delete directory'];
        }
        
        if (unlink($fullPath)) {
            $this->cleanupEmptyDirectories($scoreDir, dirname($filePath));
            return ['success' => true];
        }
        
        return ['success' => false, 'error' => 'Failed to delete file'];
    }

    private function cleanupEmptyDirectories(string $scoreDir, string $dirPath): void {
        while ($dirPath && $dirPath !== '.') {
            $fullPath = $scoreDir . '/' . $dirPath;
            if (!is_dir($fullPath)) break;
            
            $files = scandir($fullPath);
            $files = array_diff($files, ['.', '..']);
            
            if (empty($files)) {
                rmdir($fullPath);
                $dirPath = dirname($dirPath);
            } else {
                break;
            }
        }
    }
}
