"""
MIDI Orchestrator Engine - Agente 18

Orchestrates all 107 MIDI files from the library into professional arrangements.
Handles SL2025 MIDI Pack, Construction Kits, and Reggaeton 3 collections.

Usage:
    orchestrator = MIDIOrchestrator("C:/.../libreria/MIDI Files")
    midi_data = orchestrator.load_all_midis()
    categories = orchestrator.categorize_midis()
    
    # Create arpeggio track
    orchestrator.create_arpeggio_track(
        track_index=5,
        midi_files=categories['arpeggios'][:4],
        bar_positions=[0, 8, 16, 24]
    )
"""

from __future__ import annotations

import os
import re
import json
import logging
from pathlib import Path
from typing import Dict, List, Tuple, Optional, Any, Set
from dataclasses import dataclass, field, asdict
from enum import Enum

logger = logging.getLogger(__name__)


class MIDIType(Enum):
    """Classification of MIDI file types."""
    CHORDS = "chords"
    ARPEGGIOS = "arpeggios"
    BASSLINE = "bassline"
    LEAD = "lead"
    PAD = "pad"
    PLUCK = "pluck"
    MELODY = "melody"
    PERCUSSION = "percussion"
    UNKNOWN = "unknown"


class MIDISource(Enum):
    """Source collection for MIDI files."""
    SL2025_MIDI_PACK = "sl2025_midi_pack"  # 30 chord progressions
    SL2025_CONSTRUCTION_KITS = "sl2025_construction_kits"  # Bass, Lead, Pad, Arp, Pluck
    REGGAETON_3 = "reggaeton_3"  # 4 chord progressions + 5 arpeggios
    UNKNOWN = "unknown"


@dataclass
class MIDIFileInfo:
    """Metadata for a MIDI file."""
    path: str
    filename: str
    source: MIDISource
    midi_type: MIDIType
    category: str
    key: Optional[str] = None
    bpm: Optional[int] = None
    bars: int = 4
    track_name: Optional[str] = None
    description: Optional[str] = None
    tags: List[str] = field(default_factory=list)
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary with enum values as strings."""
        result = asdict(self)
        result['source'] = self.source.value
        result['midi_type'] = self.midi_type.value
        return result


@dataclass
class ArrangementPlan:
    """Plan for MIDI distribution across song sections."""
    section: str
    start_bar: int
    duration_bars: int
    midi_files: List[str]
    track_assignments: Dict[str, int]  # category -> track_index
    intensity: float = 0.5  # 0.0 to 1.0


class MIDIOrchestrator:
    """
    Orchestrates 107 MIDI files into professional arrangements.
    
    Handles:
    - SL2025 MIDI Pack: 30 chord progressions
    - SL2025 Construction Kits: Bass, Lead, Pad, Arp, Pluck MIDIs
    - Reggaeton 3: 4 chord progressions + 5 arpeggios
    
    Provides intelligent categorization, placement planning, and track creation.
    """
    
    # Expected MIDI counts by source
    EXPECTED_COUNTS = {
        MIDISource.SL2025_MIDI_PACK: 30,
        MIDISource.SL2025_CONSTRUCTION_KITS: 68,  # Estimated
        MIDISource.REGGAETON_3: 9,  # 4 chords + 5 arpeggios
    }
    
    # File patterns for categorization
    CATEGORY_PATTERNS = {
        MIDIType.CHORDS: [
            r'chord', r'progression', r'harmony', r'chords',
            r'110-', r'123-juan', r'125-club', r'126-afro',  # Reggaeton 3 patterns
        ],
        MIDIType.ARPEGGIOS: [
            r'arp', r'arpeggio', r'sequences?',
            r'128-tech',  # Reggaeton 3 tech arpeggio
        ],
        MIDIType.BASSLINE: [
            r'bass', r'bajo', r'low', r'sub',
        ],
        MIDIType.LEAD: [
            r'lead', r'melody', r'top', r'main',
        ],
        MIDIType.PAD: [
            r'pad', r'ambient', r'atmosphere', r'chord',
        ],
        MIDIType.PLUCK: [
            r'pluck', r'percussive', r'pick',
        ],
        MIDIType.PERCUSSION: [
            r'perc', r'percussion', r'drum', r'rhythm',
        ],
    }
    
    # Key detection patterns
    KEY_PATTERNS = {
        'Am': [r'\bam\b', r'a[_-]?minor', r'a[_-]?m\b'],
        'A': [r'\ba\b(?!m)', r'a[_-]?major'],
        'Bm': [r'\bbm\b', r'b[_-]?minor', r'b[_-]?m\b'],
        'B': [r'\bb\b(?!m)', r'b[_-]?major'],
        'Cm': [r'\bcm\b', r'c[_-]?minor', r'c[_-]?m\b'],
        'C': [r'\bc\b(?!m)', r'c[_-]?major'],
        'Dm': [r'\bdm\b', r'd[_-]?minor', r'd[_-]?m\b'],
        'D': [r'\bd\b(?!m)', r'd[_-]?major'],
        'Em': [r'\bem\b', r'e[_-]?minor', r'e[_-]?m\b'],
        'E': [r'\be\b(?!m)', r'e[_-]?major'],
        'Fm': [r'\bfm\b', r'f[_-]?minor', r'f[_-]?m\b'],
        'F': [r'\bf\b(?!m)', r'f[_-]?major'],
        'Gm': [r'\bgm\b', r'g[_-]?minor', r'g[_-]?m\b'],
        'G': [r'\bg\b(?!m)', r'g[_-]?major'],
    }
    
    def __init__(self, library_path: str):
        """
        Initialize MIDI Orchestrator.
        
        Args:
            library_path: Path to MIDI files directory
        """
        self.library_path = Path(library_path)
        self.midi_files: List[MIDIFileInfo] = []
        self.categorized: Dict[MIDIType, List[MIDIFileInfo]] = {
            mt: [] for mt in MIDIType
        }
        self.by_source: Dict[MIDISource, List[MIDIFileInfo]] = {
            ms: [] for ms in MIDISource
        }
        self.arrangement_plans: List[ArrangementPlan] = []
        
        logger.info(f"MIDIOrchestrator initialized with path: {library_path}")
    
    def load_all_midis(self) -> Dict[str, List[Dict]]:
        """
        Load all MIDI files with metadata from the library.
        
        Scans for .mid and .midi files, extracts metadata based on:
        - Filename patterns (category, key, BPM)
        - Folder structure (source collection)
        - File properties
        
        Returns:
            Dictionary mapping source names to lists of MIDI file metadata
            {
                "sl2025_midi_pack": [{...}, ...],
                "sl2025_construction_kits": [{...}, ...],
                "reggaeton_3": [{...}, ...]
            }
        """
        self.midi_files = []
        self.by_source = {ms: [] for ms in MIDISource}
        
        if not self.library_path.exists():
            logger.error(f"Library path does not exist: {self.library_path}")
            return {ms.value: [] for ms in MIDISource}
        
        # Scan all .mid and .midi files
        midi_extensions = {'.mid', '.midi'}
        all_files = []
        
        for ext in midi_extensions:
            all_files.extend(self.library_path.rglob(f'*{ext}'))
        
        logger.info(f"Found {len(all_files)} MIDI files")
        
        for file_path in all_files:
            try:
                info = self._analyze_midi_file(file_path)
                self.midi_files.append(info)
                self.by_source[info.source].append(info)
            except Exception as e:
                logger.warning(f"Error analyzing {file_path}: {e}")
        
        # Log summary
        result = {}
        for source, files in self.by_source.items():
            expected = self.EXPECTED_COUNTS.get(source, 0)
            count = len(files)
            logger.info(f"{source.value}: {count} files (expected: {expected})")
            result[source.value] = [f.to_dict() for f in files]
        
        logger.info(f"Total MIDI files loaded: {len(self.midi_files)}")
        return result
    
    def _analyze_midi_file(self, file_path: Path) -> MIDIFileInfo:
        """Analyze a single MIDI file and extract metadata."""
        filename = file_path.stem
        full_path = str(file_path)
        
        # Determine source from path
        source = self._detect_source(file_path)
        
        # Detect MIDI type from filename
        midi_type = self._detect_midi_type(filename)
        
        # Detect key
        key = self._detect_key(filename)
        
        # Detect BPM
        bpm = self._detect_bpm(filename)
        
        # Detect bars (default 4, look for patterns like "8bars", "16bar")
        bars = self._detect_bars(filename)
        
        # Generate track name suggestion
        track_name = self._generate_track_name(filename, midi_type)
        
        # Generate description
        description = self._generate_description(filename, midi_type, source)
        
        # Extract tags
        tags = self._extract_tags(filename, midi_type, source)
        
        # Determine category
        category = self._determine_category(midi_type, source)
        
        return MIDIFileInfo(
            path=full_path,
            filename=filename,
            source=source,
            midi_type=midi_type,
            category=category,
            key=key,
            bpm=bpm,
            bars=bars,
            track_name=track_name,
            description=description,
            tags=tags
        )
    
    def _detect_source(self, file_path: Path) -> MIDISource:
        """Detect the source collection from file path."""
        path_str = str(file_path).lower()
        
        if 'sl2025' in path_str or 'sl 2025' in path_str:
            if 'construction' in path_str or 'kit' in path_str:
                return MIDISource.SL2025_CONSTRUCTION_KITS
            else:
                return MIDISource.SL2025_MIDI_PACK
        elif 'reggaeton 3' in path_str or 'reggaeton3' in path_str:
            return MIDISource.REGGAETON_3
        
        # Try to infer from filename patterns
        filename = file_path.stem.lower()
        if any(x in filename for x in ['110-', '123-', '125-', '126-', '128-tech']):
            return MIDISource.REGGAETON_3
        
        return MIDISource.UNKNOWN
    
    def _detect_midi_type(self, filename: str) -> MIDIType:
        """Detect MIDI type from filename patterns."""
        filename_lower = filename.lower()
        
        for midi_type, patterns in self.CATEGORY_PATTERNS.items():
            for pattern in patterns:
                if re.search(pattern, filename_lower):
                    return midi_type
        
        return MIDIType.UNKNOWN
    
    def _detect_key(self, filename: str) -> Optional[str]:
        """Detect musical key from filename."""
        filename_lower = filename.lower()
        
        for key, patterns in self.KEY_PATTERNS.items():
            for pattern in patterns:
                if re.search(pattern, filename_lower):
                    return key
        
        return None
    
    def _detect_bpm(self, filename: str) -> Optional[int]:
        """Detect BPM from filename patterns like '95bpm', '110 BPM'."""
        patterns = [
            r'(\d+)\s*bpm',
            r'(\d+)_bpm',
            r'bpm[_-]?(\d+)',
            r'(\d{2,3})\s*-\s*[a-z]',  # e.g., "95-perreo"
            r'\b(\d{2,3})bpm\b',
        ]
        
        filename_lower = filename.lower()
        for pattern in patterns:
            match = re.search(pattern, filename_lower)
            if match:
                bpm = int(match.group(1))
                if 60 <= bpm <= 200:  # Reasonable BPM range
                    return bpm
        
        return None
    
    def _detect_bars(self, filename: str) -> int:
        """Detect bar count from filename."""
        patterns = [
            r'(\d+)\s*bars?',
            r'(\d+)\s*bar',
            r'(\d+)bars',
            r'(\d+)bar',
        ]
        
        filename_lower = filename.lower()
        for pattern in patterns:
            match = re.search(pattern, filename_lower)
            if match:
                bars = int(match.group(1))
                if 1 <= bars <= 64:
                    return bars
        
        return 4  # Default to 4 bars
    
    def _generate_track_name(self, filename: str, midi_type: MIDIType) -> str:
        """Generate a suggested track name."""
        # Clean up filename
        name = filename.replace('_', ' ').replace('-', ' ')
        
        # Add type prefix
        if midi_type != MIDIType.UNKNOWN:
            prefix = midi_type.value.title()
            return f"{prefix}: {name[:30]}"
        
        return name[:40]
    
    def _generate_description(
        self, 
        filename: str, 
        midi_type: MIDIType, 
        source: MIDISource
    ) -> str:
        """Generate a description for the MIDI file."""
        parts = []
        
        if midi_type != MIDIType.UNKNOWN:
            parts.append(f"{midi_type.value.title()} pattern")
        
        if source != MIDISource.UNKNOWN:
            source_name = source.value.replace('_', ' ').title()
            parts.append(f"from {source_name}")
        
        key = self._detect_key(filename)
        if key:
            parts.append(f"Key: {key}")
        
        bpm = self._detect_bpm(filename)
        if bpm:
            parts.append(f"BPM: {bpm}")
        
        return " | ".join(parts) if parts else filename
    
    def _extract_tags(
        self, 
        filename: str, 
        midi_type: MIDIType, 
        source: MIDISource
    ) -> List[str]:
        """Extract tags from filename and metadata."""
        tags = []
        filename_lower = filename.lower()
        
        # Add type as tag
        if midi_type != MIDIType.UNKNOWN:
            tags.append(midi_type.value)
        
        # Add source as tag
        if source != MIDISource.UNKNOWN:
            tags.append(source.value)
        
        # Common style tags
        style_tags = [
            'reggaeton', 'dembow', 'perreo', 'moombahton', 'trap',
            'house', 'tech', 'club', 'afro', 'juan', 'romantic',
            'classic', 'modern', 'melodic', 'rhythmic', 'dark', 'bright'
        ]
        for tag in style_tags:
            if tag in filename_lower:
                tags.append(tag)
        
        return tags
    
    def _determine_category(self, midi_type: MIDIType, source: MIDISource) -> str:
        """Determine the general category for a MIDI file."""
        if midi_type == MIDIType.CHORDS:
            return "harmonic"
        elif midi_type in [MIDIType.ARPEGGIOS, MIDIType.BASSLINE]:
            return "rhythmic"
        elif midi_type in [MIDIType.LEAD, MIDIType.MELODY]:
            return "melodic"
        elif midi_type in [MIDIType.PAD]:
            return "textural"
        elif midi_type in [MIDIType.PERCUSSION]:
            return "percussion"
        else:
            return "misc"
    
    def categorize_midis(self) -> Dict[str, List[str]]:
        """
        Organize MIDI files into categories.
        
        Categories:
        - chords: Chord progressions and harmonic patterns
        - arpeggios: Arpeggio patterns and sequences
        - basslines: Bass MIDI patterns
        - leads: Lead melody patterns
        - pads: Pad and atmospheric patterns
        - plucks: Pluck patterns
        - percussion: Drum and percussion patterns
        - unknown: Uncategorized files
        
        Returns:
            Dictionary mapping category names to lists of file paths
            {
                "chords": ["path/to/file1.mid", ...],
                "arpeggios": [...],
                ...
            }
        """
        if not self.midi_files:
            self.load_all_midis()
        
        # Reset categorization
        self.categorized = {mt: [] for mt in MIDIType}
        
        # Categorize each file
        for midi_info in self.midi_files:
            self.categorized[midi_info.midi_type].append(midi_info)
        
        # Build result dictionary
        result = {}
        for midi_type, files in self.categorized.items():
            result[midi_type.value] = [f.path for f in files]
        
        # Log summary
        logger.info("MIDI Categorization Summary:")
        for category, paths in result.items():
            logger.info(f"  {category}: {len(paths)} files")
        
        return result
    
    def create_arpeggio_track(
        self,
        track_index: int,
        midi_files: List[str],
        bar_positions: List[float],
        loop: bool = True
    ) -> Dict:
        """
        Create an arpeggio track with MIDI clips at specified positions.
        
        Args:
            track_index: Index of the track to place clips on
            midi_files: List of MIDI file paths to place
            bar_positions: List of bar positions for each clip
            loop: Whether clips should loop (default True)
        
        Returns:
            Dictionary with creation status and clip info:
            {
                "status": "success",
                "track_index": 5,
                "clips_created": 4,
                "clip_positions": [0, 8, 16, 24],
                "total_bars": 32,
                "midi_files": [...]
            }
        """
        if len(midi_files) != len(bar_positions):
            raise ValueError(
                f"midi_files ({len(midi_files)}) and bar_positions "
                f"({len(bar_positions)}) must have same length"
            )
        
        clips_info = []
        for i, (midi_path, position) in enumerate(zip(midi_files, bar_positions)):
            clip_info = {
                "index": i,
                "midi_file": midi_path,
                "start_bar": position,
                "loop": loop
            }
            clips_info.append(clip_info)
        
        total_bars = max(bar_positions) + 4 if bar_positions else 0
        
        result = {
            "status": "success",
            "track_index": track_index,
            "clips_created": len(clips_info),
            "clip_positions": bar_positions,
            "total_bars": total_bars,
            "midi_files": midi_files,
            "clips": clips_info,
            "track_type": "arpeggio"
        }
        
        logger.info(
            f"Created arpeggio track {track_index} with "
            f"{len(clips_info)} clips at positions {bar_positions}"
        )
        
        return result
    
    def create_chord_progression_track(
        self,
        track_index: int,
        progression_files: List[str],
        sections: List[str],
        section_bars: Optional[Dict[str, int]] = None
    ) -> Dict:
        """
        Create a chord progression track with MIDI clips per song section.
        
        Maps different chord progressions to different song sections
        (intro, verse, chorus, bridge, outro).
        
        Args:
            track_index: Index of the track to place clips on
            progression_files: List of MIDI file paths for chord progressions
            sections: List of section names (e.g., ["intro", "verse", "chorus"])
            section_bars: Optional dict mapping section names to bar counts
                (default: intro=8, verse=16, chorus=8, bridge=8, outro=8)
        
        Returns:
            Dictionary with creation status and section mapping:
            {
                "status": "success",
                "track_index": 3,
                "sections_mapped": {
                    "intro": {"file": "...", "start_bar": 0, "bars": 8},
                    "verse": {"file": "...", "start_bar": 8, "bars": 16},
                    ...
                },
                "total_bars": 56
            }
        """
        # Default section bar counts
        default_section_bars = {
            "intro": 8,
            "verse": 16,
            "chorus": 8,
            "bridge": 8,
            "outro": 8,
            "build": 8,
            "drop": 8,
        }
        
        if section_bars:
            default_section_bars.update(section_bars)
        
        # Map progressions to sections
        sections_mapped = {}
        current_bar = 0.0
        
        for i, section in enumerate(sections):
            # Cycle through progression files if fewer than sections
            file_index = i % len(progression_files)
            midi_file = progression_files[file_index]
            bars = default_section_bars.get(section, 8)
            
            sections_mapped[section] = {
                "file": midi_file,
                "start_bar": current_bar,
                "bars": bars,
                "loop": True
            }
            
            current_bar += bars
        
        result = {
            "status": "success",
            "track_index": track_index,
            "sections_mapped": sections_mapped,
            "total_bars": current_bar,
            "progression_count": len(progression_files),
            "section_count": len(sections),
            "track_type": "chords"
        }
        
        logger.info(
            f"Created chord progression track {track_index} with "
            f"{len(sections)} sections over {current_bar} bars"
        )
        
        return result
    
    def distribute_midis_across_song(
        self,
        total_bars: int,
        structure: Optional[List[str]] = None,
        track_indices: Optional[Dict[str, int]] = None
    ) -> Dict[str, List[Tuple[float, float, str]]]:
        """
        Plan MIDI placement across the entire song structure.
        
        Creates a comprehensive arrangement plan distributing different
        MIDI types across the song timeline.
        
        Args:
            total_bars: Total length of the song in bars
            structure: List of section names (default: standard verse-chorus)
            track_indices: Optional mapping of category to track index
        
        Returns:
            Dictionary mapping categories to lists of (start_bar, duration, file) tuples:
            {
                "chords": [(0, 8, "intro_chords.mid"), (8, 16, "verse_chords.mid"), ...],
                "arpeggios": [(16, 8, "arp1.mid"), (40, 8, "arp2.mid"), ...],
                "basslines": [(0, 56, "bass_loop.mid")],
                ...
            }
        """
        if not self.midi_files:
            self.load_all_midis()
        
        if not self.categorized[MIDIType.CHORDS]:
            self.categorize_midis()
        
        # Default structure
        if structure is None:
            structure = ["intro", "verse", "chorus", "verse", "chorus", "bridge", "chorus", "outro"]
        
        # Default track indices
        if track_indices is None:
            track_indices = {
                "chords": 0,
                "arpeggios": 1,
                "basslines": 2,
                "leads": 3,
                "pads": 4,
                "plucks": 5,
            }
        
        # Section bar lengths
        section_lengths = {
            "intro": 8,
            "verse": 16,
            "chorus": 16,
            "bridge": 8,
            "outro": 8,
            "build": 4,
            "drop": 16,
            "break": 8,
        }
        
        # Calculate section positions
        section_positions = {}
        current_bar = 0.0
        for section in structure:
            length = section_lengths.get(section, 8)
            section_positions[section] = (current_bar, length)
            current_bar += length
        
        # Get available MIDIs by category
        available = {
            "chords": self.categorized[MIDIType.CHORDS] + self.categorized[MIDIType.PAD],
            "arpeggios": self.categorized[MIDIType.ARPEGGIOS],
            "basslines": self.categorized[MIDIType.BASSLINE],
            "leads": self.categorized[MIDIType.LEAD] + self.categorized[MIDIType.MELODY],
            "pads": self.categorized[MIDIType.PAD],
            "plucks": self.categorized[MIDIType.PLUCK],
        }
        
        # Build distribution plan
        distribution = {cat: [] for cat in available.keys()}
        
        # Strategy: Place chords/pads throughout, vary others by section
        
        # 1. Chords: Different progression per major section
        chord_idx = 0
        for section in structure:
            if section in ["verse", "chorus", "intro"]:
                start, length = section_positions[section]
                if available["chords"]:
                    midi_info = available["chords"][chord_idx % len(available["chords"])]
                    distribution["chords"].append((start, length, midi_info.path))
                    chord_idx += 1
        
        # 2. Arpeggios: Add in choruses and builds for energy
        arp_idx = 0
        for section in structure:
            if section in ["chorus", "build"]:
                start, length = section_positions[section]
                if available["arpeggios"]:
                    midi_info = available["arpeggios"][arp_idx % len(available["arpeggios"])]
                    distribution["arpeggios"].append((start, length, midi_info.path))
                    arp_idx += 1
        
        # 3. Basslines: Continuous throughout with variations
        if available["basslines"]:
            bass_per_section = max(1, len(available["basslines"]) // len(structure))
            bass_idx = 0
            for section in structure:
                start, length = section_positions[section]
                midi_info = available["basslines"][bass_idx % len(available["basslines"])]
                distribution["basslines"].append((start, length, midi_info.path))
                bass_idx += 1
        
        # 4. Leads: Chorus and bridge sections
        lead_idx = 0
        for section in structure:
            if section in ["chorus", "bridge"]:
                start, length = section_positions[section]
                if available["leads"]:
                    midi_info = available["leads"][lead_idx % len(available["leads"])]
                    distribution["leads"].append((start, length, midi_info.path))
                    lead_idx += 1
        
        # 5. Pads: Ambient background throughout
        if available["pads"]:
            # Use one pad for whole song or vary
            pad_idx = 0
            for section in structure:
                start, length = section_positions[section]
                midi_info = available["pads"][pad_idx % len(available["pads"])]
                distribution["pads"].append((start, length, midi_info.path))
                pad_idx += min(1, len(available["pads"]) // 2)
        
        # 6. Plucks: Verse sections for rhythmic interest
        pluck_idx = 0
        for section in structure:
            if section == "verse":
                start, length = section_positions[section]
                if available["plucks"]:
                    midi_info = available["plucks"][pluck_idx % len(available["plucks"])]
                    distribution["plucks"].append((start, length, midi_info.path))
                    pluck_idx += 1
        
        logger.info(f"Distributed MIDIs across {total_bars} bars")
        for cat, items in distribution.items():
            if items:
                total_cat_bars = sum(d[1] for d in items)
                logger.info(f"  {cat}: {len(items)} clips, {total_cat_bars} bars")
        
        return distribution
    
    def get_midi_by_source(self, source: MIDISource) -> List[Dict]:
        """Get all MIDI files from a specific source collection."""
        files = self.by_source.get(source, [])
        return [f.to_dict() for f in files]
    
    def get_midi_by_type(self, midi_type: MIDIType) -> List[Dict]:
        """Get all MIDI files of a specific type."""
        if not self.categorized.get(midi_type):
            self.categorize_midis()
        files = self.categorized.get(midi_type, [])
        return [f.to_dict() for f in files]
    
    def suggest_arrangement(
        self,
        bpm: int = 95,
        key: str = "Am",
        style: str = "classic"
    ) -> Dict:
        """
        Suggest a complete MIDI arrangement based on parameters.
        
        Args:
            bpm: Target BPM
            key: Target key
            style: Style preference (classic, modern, perreo, moombahton)
        
        Returns:
            Complete arrangement suggestion with MIDI selections
        """
        if not self.midi_files:
            self.load_all_midis()
        
        # Filter by key if specified
        key_filtered = [
            f for f in self.midi_files 
            if f.key is None or f.key == key or f.key == key.replace('m', '')
        ]
        
        # Filter by BPM compatibility (within 10%)
        bpm_filtered = [
            f for f in key_filtered
            if f.bpm is None or abs(f.bpm - bpm) / bpm <= 0.1
        ]
        
        # Group by type
        by_type = {mt: [] for mt in MIDIType}
        for f in bpm_filtered:
            by_type[f.midi_type].append(f)
        
        # Select best matches for each role
        suggestion = {
            "bpm": bpm,
            "key": key,
            "style": style,
            "chords": [f.to_dict() for f in by_type[MIDIType.CHORDS][:4]],
            "arpeggios": [f.to_dict() for f in by_type[MIDIType.ARPEGGIOS][:3]],
            "basslines": [f.to_dict() for f in by_type[MIDIType.BASSLINE][:4]],
            "leads": [f.to_dict() for f in by_type[MIDIType.LEAD][:3]],
            "pads": [f.to_dict() for f in by_type[MIDIType.PAD][:3]],
        }
        
        return suggestion
    
    def export_catalog(self, output_path: str) -> Dict:
        """
        Export complete MIDI catalog to JSON file.
        
        Args:
            output_path: Path for the output JSON file
        
        Returns:
            Export status and summary
        """
        if not self.midi_files:
            self.load_all_midis()
        
        catalog = {
            "total_files": len(self.midi_files),
            "by_source": {
                source.value: [f.to_dict() for f in files]
                for source, files in self.by_source.items()
            },
            "by_type": {
                midi_type.value: [f.to_dict() for f in files]
                for midi_type, files in self.categorized.items()
            },
            "all_files": [f.to_dict() for f in self.midi_files]
        }
        
        try:
            with open(output_path, 'w', encoding='utf-8') as f:
                json.dump(catalog, f, indent=2, ensure_ascii=False)
            
            logger.info(f"Exported catalog to {output_path}")
            return {
                "status": "success",
                "output_path": output_path,
                "total_files": len(self.midi_files),
                "sources": {s.value: len(f) for s, f in self.by_source.items()},
                "types": {mt.value: len(f) for mt, f in self.categorized.items()}
            }
        except Exception as e:
            logger.error(f"Failed to export catalog: {e}")
            return {"status": "error", "message": str(e)}
    
    def create_complete_arrangement(
        self,
        track_config: Dict[str, int],
        total_bars: int = 64,
        structure: Optional[List[str]] = None
    ) -> Dict:
        """
        Create a complete arrangement with all MIDI types assigned to tracks.
        
        Args:
            track_config: Dict mapping category to track index
                e.g., {"chords": 0, "arpeggios": 1, "basslines": 2, ...}
            total_bars: Total song length
            structure: Song section structure
        
        Returns:
            Complete arrangement configuration
        """
        # Get distribution plan
        distribution = self.distribute_midis_across_song(
            total_bars=total_bars,
            structure=structure,
            track_indices=track_config
        )
        
        # Build track assignments
        tracks = {}
        for category, track_idx in track_config.items():
            clips = distribution.get(category, [])
            if clips:
                tracks[category] = {
                    "track_index": track_idx,
                    "clip_count": len(clips),
                    "clips": [
                        {
                            "start_bar": clip[0],
                            "duration_bars": clip[1],
                            "midi_file": clip[2]
                        }
                        for clip in clips
                    ]
                }
        
        return {
            "status": "success",
            "total_bars": total_bars,
            "structure": structure or ["intro", "verse", "chorus", "verse", "chorus", "outro"],
            "tracks": tracks,
            "track_count": len(tracks),
            "total_clips": sum(t["clip_count"] for t in tracks.values())
        }


# Convenience functions for direct usage
def create_orchestrator(library_path: str) -> MIDIOrchestrator:
    """Create and initialize a MIDIOrchestrator instance."""
    return MIDIOrchestrator(library_path)


def get_midi_catalog(library_path: str) -> Dict[str, List[Dict]]:
    """Quick function to get full MIDI catalog."""
    orch = MIDIOrchestrator(library_path)
    return orch.load_all_midis()


def categorize_library(library_path: str) -> Dict[str, List[str]]:
    """Quick function to categorize all MIDI files."""
    orch = MIDIOrchestrator(library_path)
    return orch.categorize_midis()
