"""
library_indexer.py - Comprehensive Library Indexing Engine

Indexes all 618 samples in the library with complete feature extraction,
categorization, quality scoring, and SQLite database storage for fast queries.

Author: AbletonMCP_AI Senior Architecture (v3.0)
"""

import os
import sqlite3
import json
import struct
import wave
import math
from pathlib import Path
from typing import Dict, List, Tuple, Optional, Any
from dataclasses import dataclass, asdict
from datetime import datetime
import threading
import hashlib


@dataclass
class SampleFeatures:
    """Complete feature set for a sample."""
    file_path: str
    file_name: str
    category: str
    subcategory: str
    pack: str  # 'SL2025', 'user', 'factory', etc.
    
    # Audio features
    duration: float = 0.0
    sample_rate: int = 44100
    channels: int = 2
    bit_depth: int = 16
    
    # Analysis features
    bpm: float = 0.0
    key: str = "Unknown"
    rms: float = 0.0
    peak: float = 0.0
    spectral_centroid: float = 0.0
    
    # Quality metrics
    file_size: int = 0
    quality_score: float = 0.0
    
    # Metadata
    analyzed_at: str = ""
    hash: str = ""


class LibraryIndexer:
    """
    Comprehensive library indexer for all 618 samples.
    
    Handles:
    - 329 SL2025 premium pack samples
    - 107 MIDI files
    - All WAV files in reggaeton/ subfolders
    - Quality scoring based on file size, bitrate, folder priority
    - Error handling for corrupt files
    - SQLite database for fast queries
    """
    
    def __init__(self, library_path: str):
        """
        Initialize the LibraryIndexer.
        
        Args:
            library_path: Root path to the library (e.g., "C:\\...\\libreria")
        """
        self.library_path = Path(library_path)
        self.db_path = self.library_path / "library_index.db"
        self.indexed_samples: Dict[str, SampleFeatures] = {}
        self._lock = threading.Lock()
        
        # Category mappings
        self.category_folders = {
            "drums": ["kick", "snare", "clap", "hat", "hihat", "perc", "shaker", "tom"],
            "bass": ["bass", "808", "sub"],
            "synths": ["synth", "pad", "pluck", "lead", "keys", "piano", "guitar"],
            "fx": ["fx", "riser", "downlifter", "impact", "sweep", "noise", "crash"],
            "vocals": ["vocal", "vox", "chant", "hook", "phrase", "adlib"],
            "midi": ["midi", "mid", "pattern"]
        }
        
        # Pack priorities for quality scoring
        self.pack_priorities = {
            "SL2025": 1.0,      # Premium pack - highest quality
            "SL2024": 0.9,      # Previous premium
            "factory": 0.7,     # Factory samples
            "user": 0.6,        # User samples
            "default": 0.5      # Unknown origin
        }
        
        # Initialize database
        self._init_database()
    
    def _init_database(self) -> None:
        """Initialize SQLite database with required tables."""
        try:
            conn = sqlite3.connect(str(self.db_path))
            cursor = conn.cursor()
            
            # Main samples table
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS samples (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    file_path TEXT UNIQUE NOT NULL,
                    file_name TEXT NOT NULL,
                    category TEXT NOT NULL,
                    subcategory TEXT,
                    pack TEXT DEFAULT 'user',
                    duration REAL DEFAULT 0.0,
                    sample_rate INTEGER DEFAULT 44100,
                    channels INTEGER DEFAULT 2,
                    bit_depth INTEGER DEFAULT 16,
                    bpm REAL DEFAULT 0.0,
                    key TEXT DEFAULT 'Unknown',
                    rms REAL DEFAULT 0.0,
                    peak REAL DEFAULT 0.0,
                    spectral_centroid REAL DEFAULT 0.0,
                    file_size INTEGER DEFAULT 0,
                    quality_score REAL DEFAULT 0.0,
                    analyzed_at TEXT,
                    hash TEXT,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                )
            ''')
            
            # Index for fast queries
            cursor.execute('''
                CREATE INDEX IF NOT EXISTS idx_category ON samples(category)
            ''')
            cursor.execute('''
                CREATE INDEX IF NOT EXISTS idx_pack ON samples(pack)
            ''')
            cursor.execute('''
                CREATE INDEX IF NOT EXISTS idx_bpm ON samples(bpm)
            ''')
            cursor.execute('''
                CREATE INDEX IF NOT EXISTS idx_key ON samples(key)
            ''')
            cursor.execute('''
                CREATE INDEX IF NOT EXISTS idx_quality ON samples(quality_score DESC)
            ''')
            
            # Statistics table
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS index_stats (
                    id INTEGER PRIMARY KEY,
                    total_samples INTEGER DEFAULT 0,
                    total_categories INTEGER DEFAULT 0,
                    last_indexed TIMESTAMP,
                    corrupt_files INTEGER DEFAULT 0
                )
            ''')
            
            conn.commit()
            conn.close()
            
        except Exception as e:
            print(f"[LibraryIndexer] Database initialization error: {e}")
            raise
    
    def _detect_pack(self, file_path: Path) -> str:
        """Detect which pack a sample belongs to."""
        path_str = str(file_path).lower()
        
        if "sl2025" in path_str or "2025" in path_str:
            return "SL2025"
        elif "sl2024" in path_str or "2024" in path_str:
            return "SL2024"
        elif "factory" in path_str or "ableton" in path_str:
            return "factory"
        else:
            return "user"
    
    def _detect_category(self, file_path: Path) -> Tuple[str, str]:
        """
        Detect category and subcategory from file path.
        
        Returns:
            Tuple of (category, subcategory)
        """
        path_str = str(file_path).lower()
        file_name = file_path.stem.lower()
        
        # Check file extension first
        if file_path.suffix.lower() in ['.mid', '.midi']:
            return ("midi", "pattern")
        
        # Check parent folder names
        parent_folders = [p.name.lower() for p in file_path.parents]
        
        for category, keywords in self.category_folders.items():
            # Check if any keyword appears in path
            for keyword in keywords:
                if keyword in path_str or keyword in parent_folders:
                    subcategory = self._detect_subcategory(file_name, category)
                    return (category, subcategory)
        
        # Fallback: analyze filename
        if "kick" in file_name:
            return ("drums", "kick")
        elif "snare" in file_name or "clap" in file_name:
            return ("drums", "snare")
        elif "hat" in file_name:
            return ("drums", "hat")
        elif "bass" in file_name or "808" in file_name:
            return ("bass", "bass")
        elif "synth" in file_name or "pad" in file_name:
            return ("synths", "pad")
        elif "fx" in file_name or "riser" in file_name:
            return ("fx", "riser")
        
        return ("unknown", "unknown")
    
    def _detect_subcategory(self, file_name: str, category: str) -> str:
        """Detect subcategory based on filename and category."""
        file_lower = file_name.lower()
        
        if category == "drums":
            if "kick" in file_lower:
                return "kick"
            elif "snare" in file_lower:
                return "snare"
            elif "clap" in file_lower:
                return "clap"
            elif "hat" in file_lower or "hihat" in file_lower:
                return "hat"
            elif "perc" in file_lower or "shaker" in file_lower:
                return "percussion"
            elif "tom" in file_lower:
                return "tom"
            return "drum_other"
            
        elif category == "bass":
            if "808" in file_lower:
                return "808"
            elif "sub" in file_lower:
                return "sub"
            return "bass"
            
        elif category == "synths":
            if "pad" in file_lower:
                return "pad"
            elif "pluck" in file_lower:
                return "pluck"
            elif "lead" in file_lower:
                return "lead"
            elif "keys" in file_lower or "piano" in file_lower:
                return "keys"
            elif "guitar" in file_lower:
                return "guitar"
            return "synth"
            
        elif category == "fx":
            if "riser" in file_lower:
                return "riser"
            elif "down" in file_lower or "lifter" in file_lower:
                return "downlifter"
            elif "impact" in file_lower or "hit" in file_lower:
                return "impact"
            elif "sweep" in file_lower:
                return "sweep"
            elif "noise" in file_lower:
                return "noise"
            elif "crash" in file_lower:
                return "crash"
            return "fx_other"
            
        elif category == "vocals":
            if "hook" in file_lower:
                return "hook"
            elif "phrase" in file_lower:
                return "phrase"
            elif "adlib" in file_lower:
                return "adlib"
            elif "chant" in file_lower:
                return "chant"
            return "vocal_other"
        
        return "unknown"
    
    def _calculate_quality_score(self, features: SampleFeatures) -> float:
        """
        Calculate quality score (0.0-1.0) based on multiple factors.
        
        Factors:
        - Pack priority (SL2025 = highest)
        - Bit depth (24-bit > 16-bit)
        - Sample rate (48kHz > 44.1kHz)
        - File size (larger generally means more content/detail)
        - Duration (loops need appropriate length)
        """
        score = 0.0
        
        # Pack priority (40% weight)
        pack_score = self.pack_priorities.get(features.pack, 0.5)
        score += pack_score * 0.4
        
        # Bit depth (20% weight)
        if features.bit_depth >= 24:
            score += 0.2
        elif features.bit_depth >= 16:
            score += 0.15
        else:
            score += 0.05
        
        # Sample rate (15% weight)
        if features.sample_rate >= 48000:
            score += 0.15
        elif features.sample_rate >= 44100:
            score += 0.12
        else:
            score += 0.05
        
        # File size appropriateness (15% weight)
        # Optimal size depends on category
        optimal_sizes = {
            "drums": (50000, 500000),      # 50KB - 500KB
            "bass": (100000, 2000000),     # 100KB - 2MB
            "synths": (200000, 5000000),   # 200KB - 5MB
            "fx": (50000, 2000000),        # 50KB - 2MB
            "vocals": (100000, 5000000),   # 100KB - 5MB
            "midi": (500, 50000)           # 500B - 50KB
        }
        
        min_size, max_size = optimal_sizes.get(features.category, (100000, 3000000))
        if min_size <= features.file_size <= max_size:
            score += 0.15
        elif features.file_size > max_size:
            score += 0.10  # Slightly penalize oversized
        else:
            score += 0.05  # Penalize undersized
        
        # Duration appropriateness (10% weight)
        if features.category == "midi":
            score += 0.1  # MIDI files don't need duration
        elif 1.0 <= features.duration <= 16.0:
            score += 0.1  # Good loop range
        elif features.duration > 0:
            score += 0.05  # Very short or very long
        
        return min(1.0, max(0.0, score))
    
    def _analyze_wav_file(self, file_path: Path) -> Optional[Dict]:
        """
        Analyze a WAV file and extract features.
        
        Returns None if file is corrupt or unreadable.
        """
        try:
            with wave.open(str(file_path), 'rb') as wav:
                # Basic metadata
                channels = wav.getnchannels()
                sample_width = wav.getsampwidth()
                sample_rate = wav.getframerate()
                n_frames = wav.getnframes()
                
                duration = n_frames / sample_rate
                bit_depth = sample_width * 8
                
                # Read audio data for analysis
                frames = wav.readframes(n_frames)
                
                # Calculate RMS and peak
                if sample_width == 2:  # 16-bit
                    fmt = f"<{n_frames * channels}h"
                elif sample_width == 3:  # 24-bit packed
                    # Skip detailed analysis for 24-bit (complex unpacking)
                    return {
                        "duration": duration,
                        "sample_rate": sample_rate,
                        "channels": channels,
                        "bit_depth": 24,
                        "rms": 0.0,
                        "peak": 0.0,
                        "spectral_centroid": 0.0
                    }
                elif sample_width == 4:  # 32-bit
                    fmt = f"<{n_frames * channels}i"
                else:
                    return None  # Unsupported format
                
                try:
                    samples = struct.unpack(fmt, frames)
                except struct.error:
                    return None  # Corrupt file
                
                # Calculate mono mix if stereo
                if channels == 2:
                    mono_samples = []
                    for i in range(0, len(samples), 2):
                        mono_samples.append((samples[i] + samples[i+1]) / 2)
                    samples = mono_samples
                
                # RMS calculation
                sum_squares = sum(s * s for s in samples)
                rms = math.sqrt(sum_squares / len(samples)) if samples else 0
                
                # Peak calculation
                peak = max(abs(s) for s in samples) if samples else 0
                
                # Normalize RMS and peak to 0.0-1.0 range (16-bit max = 32768)
                max_val = 2 ** (bit_depth - 1)
                rms_normalized = rms / max_val
                peak_normalized = peak / max_val
                
                # Simple spectral centroid approximation using sample variance
                # (real centroid requires FFT, but this gives rough brightness indicator)
                if len(samples) > 1:
                    mean = sum(samples) / len(samples)
                    variance = sum((s - mean) ** 2 for s in samples) / len(samples)
                    spectral_centroid = math.sqrt(variance) / max_val * 10000  # Scale to Hz-like
                else:
                    spectral_centroid = 0.0
                
                return {
                    "duration": duration,
                    "sample_rate": sample_rate,
                    "channels": channels,
                    "bit_depth": bit_depth,
                    "rms": rms_normalized,
                    "peak": peak_normalized,
                    "spectral_centroid": spectral_centroid
                }
                
        except wave.Error as e:
            print(f"[LibraryIndexer] WAV error in {file_path}: {e}")
            return None
        except Exception as e:
            print(f"[LibraryIndexer] Error analyzing {file_path}: {e}")
            return None
    
    def _estimate_bpm(self, file_path: Path, duration: float) -> float:
        """
        Estimate BPM from filename or return default.
        
        This is a simple heuristic - real BPM detection requires onset detection.
        """
        file_name = file_path.stem.lower()
        
        # Common BPMs to check for in filename
        bpm_markers = ["95", "100", "105", "110", "120", "128", "140", "150", "174"]
        for bpm_str in bpm_markers:
            if bpm_str in file_name:
                try:
                    return float(bpm_str)
                except:
                    pass
        
        # If filename contains loop markers
        if "loop" in file_name:
            # Estimate based on common reggaeton BPM
            return 95.0
        
        return 0.0  # Unknown
    
    def _estimate_key(self, file_path: Path) -> str:
        """
        Estimate key from filename.
        
        This is a simple heuristic - real key detection requires pitch analysis.
        """
        file_name = file_path.stem.upper()
        
        # Common keys to check for
        keys = ["A", "AM", "A#", "A#M", "BB", "BBM", "B", "BM",
                "C", "CM", "C#", "C#M", "DB", "DBM", "D", "DM",
                "D#", "D#M", "EB", "EBM", "E", "EM", "F", "FM",
                "F#", "F#M", "GB", "GBM", "G", "GM", "G#", "G#M",
                "AB", "ABM", "AMINOR", "CMINOR", "DMINOR", "EMINOR",
                "FMINOR", "GMINOR"]
        
        for key in keys:
            if key in file_name or f"_{key}_" in file_name or f"-{key}-" in file_name:
                # Normalize key format
                key = key.replace("MINOR", "m")
                if len(key) == 1:
                    key = f"{key}m"  # Assume minor for single letters
                return key
        
        return "Unknown"
    
    def _calculate_file_hash(self, file_path: Path) -> str:
        """Calculate MD5 hash of file for change detection."""
        try:
            hash_md5 = hashlib.md5()
            with open(file_path, "rb") as f:
                for chunk in iter(lambda: f.read(4096), b""):
                    hash_md5.update(chunk)
            return hash_md5.hexdigest()
        except:
            return ""
    
    def extract_features(self, file_path: str) -> Optional[Dict]:
        """
        Extract comprehensive features from a sample file.
        
        Args:
            file_path: Absolute path to the sample file
            
        Returns:
            Dictionary with extracted features, or None if file is corrupt
        """
        path = Path(file_path)
        
        if not path.exists():
            print(f"[LibraryIndexer] File not found: {file_path}")
            return None
        
        # Get basic file info
        file_stat = path.stat()
        file_size = file_stat.st_size
        
        # Detect category and pack
        category, subcategory = self._detect_category(path)
        pack = self._detect_pack(path)
        
        # Initialize features
        features = {
            "file_path": str(path),
            "file_name": path.name,
            "category": category,
            "subcategory": subcategory,
            "pack": pack,
            "file_size": file_size,
            "analyzed_at": datetime.now().isoformat()
        }
        
        # Analyze based on file type
        suffix = path.suffix.lower()
        
        if suffix == '.wav':
            wav_info = self._analyze_wav_file(path)
            if wav_info is None:
                return None  # Corrupt file
            
            features.update(wav_info)
            
            # Estimate BPM and Key from filename
            features["bpm"] = self._estimate_bpm(path, features["duration"])
            features["key"] = self._estimate_key(path)
            
        elif suffix in ['.mid', '.midi']:
            # MIDI files - minimal analysis
            features["duration"] = 0.0  # Would need MIDI parsing
            features["sample_rate"] = 0
            features["channels"] = 0
            features["bit_depth"] = 0
            features["bpm"] = self._estimate_bpm(path, 0)
            features["key"] = self._estimate_key(path)
            features["rms"] = 0.0
            features["peak"] = 0.0
            features["spectral_centroid"] = 0.0
            
        elif suffix in ['.mp3', '.aif', '.aiff', '.flac']:
            # For compressed formats, we can't easily extract features
            # without external libraries - just record basic info
            features["duration"] = 0.0  # Would need parsing
            features["sample_rate"] = 44100  # Assume standard
            features["channels"] = 2
            features["bit_depth"] = 16
            features["bpm"] = self._estimate_bpm(path, 0)
            features["key"] = self._estimate_key(path)
            features["rms"] = 0.0
            features["peak"] = 0.0
            features["spectral_centroid"] = 0.0
        else:
            return None  # Unsupported format
        
        # Calculate file hash
        features["hash"] = self._calculate_file_hash(path)
        
        # Create SampleFeatures object and calculate quality
        sample = SampleFeatures(**features)
        sample.quality_score = self._calculate_quality_score(sample)
        features["quality_score"] = sample.quality_score
        
        return features
    
    def index_all_samples(self) -> Dict:
        """
        Scan and index all samples in the library.
        
        Handles:
        - 329 SL2025 samples (premium pack)
        - 107 MIDI files
        - All WAV files in reggaeton/ subfolders
        
        Returns:
            Dictionary with indexing statistics
        """
        stats = {
            "total_files": 0,
            "indexed": 0,
            "corrupt": 0,
            "skipped": 0,
            "by_category": {},
            "by_pack": {},
            "errors": []
        }
        
        print(f"[LibraryIndexer] Starting full library index...")
        print(f"[LibraryIndexer] Library path: {self.library_path}")
        
        # Supported extensions
        extensions = {'.wav', '.mp3', '.aif', '.aiff', '.flac', '.mid', '.midi'}
        
        # Scan all files recursively
        all_files = []
        try:
            for ext in extensions:
                all_files.extend(self.library_path.rglob(f"*{ext}"))
        except Exception as e:
            stats["errors"].append(f"Scan error: {e}")
            return stats
        
        stats["total_files"] = len(all_files)
        print(f"[LibraryIndexer] Found {len(all_files)} files to index")
        
        # Process each file
        corrupt_count = 0
        for i, file_path in enumerate(all_files, 1):
            if i % 50 == 0:
                print(f"[LibraryIndexer] Processed {i}/{len(all_files)} files...")
            
            try:
                features = self.extract_features(str(file_path))
                
                if features is None:
                    stats["corrupt"] += 1
                    corrupt_count += 1
                    continue
                
                # Store in memory
                with self._lock:
                    self.indexed_samples[str(file_path)] = SampleFeatures(**features)
                
                # Update stats
                category = features["category"]
                pack = features["pack"]
                
                stats["by_category"][category] = stats["by_category"].get(category, 0) + 1
                stats["by_pack"][pack] = stats["by_pack"].get(pack, 0) + 1
                stats["indexed"] += 1
                
            except Exception as e:
                stats["errors"].append(f"{file_path}: {e}")
                stats["corrupt"] += 1
                corrupt_count += 1
        
        print(f"[LibraryIndexer] Indexing complete!")
        print(f"  - Total: {stats['total_files']}")
        print(f"  - Indexed: {stats['indexed']}")
        print(f"  - Corrupt: {stats['corrupt']}")
        
        return stats
    
    def categorize_samples(self) -> Dict[str, List[str]]:
        """
        Organize samples by category.
        
        Returns:
            Dictionary mapping category -> list of file paths
        """
        categories = {
            "drums": [],
            "bass": [],
            "synths": [],
            "fx": [],
            "vocals": [],
            "midi": [],
            "unknown": []
        }
        
        for file_path, features in self.indexed_samples.items():
            category = features.category
            if category in categories:
                categories[category].append(file_path)
            else:
                categories["unknown"].append(file_path)
        
        # Sort each category by quality score
        for category in categories:
            categories[category].sort(
                key=lambda p: self.indexed_samples.get(p, SampleFeatures(file_path=p, file_name="", category="")).quality_score,
                reverse=True
            )
        
        return categories
    
    def save_to_database(self) -> Dict:
        """
        Save indexed samples to SQLite database.
        
        Returns:
            Dictionary with save statistics
        """
        stats = {
            "saved": 0,
            "updated": 0,
            "errors": []
        }
        
        try:
            conn = sqlite3.connect(str(self.db_path))
            cursor = conn.cursor()
            
            for file_path, features in self.indexed_samples.items():
                try:
                    cursor.execute('''
                        INSERT OR REPLACE INTO samples 
                        (file_path, file_name, category, subcategory, pack,
                         duration, sample_rate, channels, bit_depth, bpm, key,
                         rms, peak, spectral_centroid, file_size, quality_score,
                         analyzed_at, hash)
                        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    ''', (
                        features.file_path,
                        features.file_name,
                        features.category,
                        features.subcategory,
                        features.pack,
                        features.duration,
                        features.sample_rate,
                        features.channels,
                        features.bit_depth,
                        features.bpm,
                        features.key,
                        features.rms,
                        features.peak,
                        features.spectral_centroid,
                        features.file_size,
                        features.quality_score,
                        features.analyzed_at,
                        features.hash
                    ))
                    
                    if cursor.rowcount == 1:
                        if cursor.lastrowid:
                            stats["saved"] += 1
                        else:
                            stats["updated"] += 1
                    
                except Exception as e:
                    stats["errors"].append(f"{file_path}: {e}")
            
            # Update statistics
            cursor.execute('''
                INSERT OR REPLACE INTO index_stats 
                (id, total_samples, total_categories, last_indexed, corrupt_files)
                VALUES (1, ?, ?, ?, ?)
            ''', (
                len(self.indexed_samples),
                len(set(f.category for f in self.indexed_samples.values())),
                datetime.now().isoformat(),
                len(stats["errors"])
            ))
            
            conn.commit()
            conn.close()
            
            print(f"[LibraryIndexer] Database saved!")
            print(f"  - Saved: {stats['saved']}")
            print(f"  - Updated: {stats['updated']}")
            print(f"  - Errors: {len(stats['errors'])}")
            
        except Exception as e:
            stats["errors"].append(f"Database error: {e}")
            print(f"[LibraryIndexer] Database save failed: {e}")
        
        return stats
    
    def get_sample_by_category(self, category: str, limit: int = 100) -> List[str]:
        """
        Get samples by category from database.
        
        Args:
            category: Category name (drums, bass, synths, fx, vocals, midi)
            limit: Maximum number of samples to return
            
        Returns:
            List of file paths
        """
        try:
            conn = sqlite3.connect(str(self.db_path))
            cursor = conn.cursor()
            
            cursor.execute('''
                SELECT file_path FROM samples 
                WHERE category = ? 
                ORDER BY quality_score DESC, pack DESC
                LIMIT ?
            ''', (category, limit))
            
            results = [row[0] for row in cursor.fetchall()]
            conn.close()
            
            return results
            
        except Exception as e:
            print(f"[LibraryIndexer] Query error: {e}")
            return []
    
    def get_samples_by_query(self, 
                            category: Optional[str] = None,
                            pack: Optional[str] = None,
                            bpm_min: float = 0,
                            bpm_max: float = 999,
                            key: Optional[str] = None,
                            quality_min: float = 0.0,
                            limit: int = 100) -> List[Dict]:
        """
        Advanced query with multiple filters.
        
        Returns:
            List of sample dictionaries
        """
        try:
            conn = sqlite3.connect(str(self.db_path))
            conn.row_factory = sqlite3.Row
            cursor = conn.cursor()
            
            query = "SELECT * FROM samples WHERE 1=1"
            params = []
            
            if category:
                query += " AND category = ?"
                params.append(category)
            
            if pack:
                query += " AND pack = ?"
                params.append(pack)
            
            if bpm_min > 0:
                query += " AND bpm >= ?"
                params.append(bpm_min)
            
            if bpm_max < 999:
                query += " AND bpm <= ?"
                params.append(bpm_max)
            
            if key:
                query += " AND key = ?"
                params.append(key)
            
            if quality_min > 0:
                query += " AND quality_score >= ?"
                params.append(quality_min)
            
            query += " ORDER BY quality_score DESC LIMIT ?"
            params.append(limit)
            
            cursor.execute(query, params)
            
            results = [dict(row) for row in cursor.fetchall()]
            conn.close()
            
            return results
            
        except Exception as e:
            print(f"[LibraryIndexer] Query error: {e}")
            return []
    
    def get_index_stats(self) -> Dict:
        """Get current indexing statistics from database."""
        try:
            conn = sqlite3.connect(str(self.db_path))
            cursor = conn.cursor()
            
            cursor.execute('SELECT * FROM index_stats WHERE id = 1')
            row = cursor.fetchone()
            
            if row:
                return {
                    "total_samples": row[1],
                    "total_categories": row[2],
                    "last_indexed": row[3],
                    "corrupt_files": row[4]
                }
            
            conn.close()
            
        except Exception as e:
            print(f"[LibraryIndexer] Stats error: {e}")
        
        return {}
    
    def quick_index(self) -> Dict:
        """
        Quick indexing mode - categorizes without deep analysis.
        
        Faster than index_all_samples but with less feature extraction.
        """
        stats = {
            "total_files": 0,
            "indexed": 0,
            "by_category": {}
        }
        
        extensions = {'.wav', '.mp3', '.aif', '.aiff', '.flac', '.mid', '.midi'}
        
        for ext in extensions:
            for file_path in self.library_path.rglob(f"*{ext}"):
                stats["total_files"] += 1
                
                category, subcategory = self._detect_category(file_path)
                pack = self._detect_pack(file_path)
                
                features = SampleFeatures(
                    file_path=str(file_path),
                    file_name=file_path.name,
                    category=category,
                    subcategory=subcategory,
                    pack=pack,
                    analyzed_at=datetime.now().isoformat()
                )
                
                self.indexed_samples[str(file_path)] = features
                
                stats["by_category"][category] = stats["by_category"].get(category, 0) + 1
                stats["indexed"] += 1
        
        return stats


# Convenience functions for direct usage
def index_library(library_path: str) -> Dict:
    """
    One-shot function to index an entire library.
    
    Usage:
        stats = index_library("C:\\...\\libreria")
    """
    indexer = LibraryIndexer(library_path)
    stats = indexer.index_all_samples()
    indexer.save_to_database()
    return stats


def get_library_stats(library_path: str) -> Dict:
    """Get statistics about an indexed library."""
    indexer = LibraryIndexer(library_path)
    return indexer.get_index_stats()


def search_samples(library_path: str, category: str, limit: int = 100) -> List[str]:
    """Search for samples by category."""
    indexer = LibraryIndexer(library_path)
    return indexer.get_sample_by_category(category, limit)
