"""
Song Generator Engine - Professional Reggaeton Track Generator

Este módulo genera configuraciones completas de canciones de reggaeton profesional,
incluyendo estructura de secciones, selección de samples basada en perfiles de usuario,
y generación de patterns rítmicos y armónicos.

Autor: AbletonMCP_AI
"""
import logging
import random
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Any, Tuple
from pathlib import Path
import os
import datetime

logger = logging.getLogger("SongGenerator")

# Importar engines existentes
try:
    from .reference_matcher import get_recommended_samples, get_user_profile
    from .sample_selector import SampleInfo, DrumKit, InstrumentGroup, get_selector
    _ENGINES_AVAILABLE = True
except ImportError:
    logger.warning("No se pudieron importar engines. Usando modo fallback.")
    _ENGINES_AVAILABLE = False


# =============================================================================
# CONSTANTES Y CONFIGURACIONES
# =============================================================================

SUPPORTED_STYLES = ["dembow", "perreo", "romantico", "club", "moombahton"]
SUPPORTED_STRUCTURES = ["minimal", "standard", "extended"]
SUPPORTED_KEYS = ["Am", "Dm", "Gm", "Cm", "Em", "Bm", "Fm", "F#m", "C#m", "G#m"]

# Configuración de estructuras (nombre: [(section_name, bars)])
STRUCTURE_CONFIGS = {
    "minimal": [
        ("intro", 8),
        ("groove", 16),
        ("break", 8),
        ("outro", 8),
    ],
    "standard": [
        ("intro", 8),
        ("build", 8),
        ("drop", 16),
        ("break", 8),
        ("drop2", 16),
        ("outro", 8),
    ],
    "extended": [
        ("intro", 16),
        ("build", 8),
        ("drop", 16),
        ("break", 8),
        ("build2", 8),
        ("drop2", 16),
        ("peak", 8),
        ("outro", 16),
    ],
}

# Niveles de energía por sección
ENERGY_LEVELS = {
    "intro": 0.3,
    "groove": 0.6,
    "build": 0.7,
    "drop": 0.9,
    "break": 0.4,
    "drop2": 0.95,
    "build2": 0.75,
    "peak": 1.0,
    "outro": 0.2,
}

# Patterns de dembow clásico (16 pasos)
DEMBOW_PATTERNS = {
    "kick": [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0],
    "snare": [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
    "clap": [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
    "hat_closed": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    "hat_open": [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
    "bass": [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
}

# Variaciones por estilo
STYLE_VARIATIONS = {
    "dembow": {
        "kick_variation": "standard",
        "bass_syncopation": 0.3,
        "hat_density": 1.0,
        "perc_extra": False,
    },
    "perreo": {
        "kick_variation": "syncopated",
        "bass_syncopation": 0.5,
        "hat_density": 0.8,
        "perc_extra": True,
    },
    "romantico": {
        "kick_variation": "sparse",
        "bass_syncopation": 0.2,
        "hat_density": 0.6,
        "perc_extra": False,
    },
    "club": {
        "kick_variation": "four_on_floor",
        "bass_syncopation": 0.4,
        "hat_density": 1.0,
        "perc_extra": True,
    },
    "moombahton": {
        "kick_variation": "moombah",
        "bass_syncopation": 0.4,
        "hat_density": 0.9,
        "perc_extra": True,
    },
}

# Roles de instrumentos soportados
INSTRUMENT_ROLES = [
    "kick", "snare", "clap", "hat_closed", "hat_open",
    "bass", "synth_lead", "synth_pad", "synth_pluck", "fx"
]


# =============================================================================
# CLASES DE DATOS PRINCIPALES
# =============================================================================

@dataclass
class ClipConfig:
    """Configuración de un clip (MIDI o Audio)."""
    name: str
    start_time: float  # En beats
    duration: float    # En beats
    notes: List[Dict[str, Any]] = field(default_factory=list)
    sample_path: str = ""
    is_audio: bool = False
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "name": self.name,
            "start_time": self.start_time,
            "duration": self.duration,
            "notes": self.notes,
            "sample_path": self.sample_path,
            "is_audio": self.is_audio,
        }


@dataclass
class DeviceConfig:
    """Configuración de un device en la cadena."""
    name: str
    device_type: str  # "instrument", "audio_effect", "midi_effect"
    preset: str = ""
    parameters: Dict[str, float] = field(default_factory=dict)
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "name": self.name,
            "device_type": self.device_type,
            "preset": self.preset,
            "parameters": self.parameters,
        }


@dataclass
class TrackConfig:
    """Configuración completa de una pista."""
    name: str
    track_type: str  # "midi" o "audio"
    instrument_role: str
    clips: List[ClipConfig] = field(default_factory=list)
    device_chain: List[DeviceConfig] = field(default_factory=list)
    volume: float = 0.8
    pan: float = 0.0
    is_muted: bool = False
    is_soloed: bool = False
    
    # Samples seleccionados para esta pista
    selected_samples: List[Dict[str, Any]] = field(default_factory=list)
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "name": self.name,
            "track_type": self.track_type,
            "instrument_role": self.instrument_role,
            "clips": [c.to_dict() for c in self.clips],
            "device_chain": [d.to_dict() for d in self.device_chain],
            "volume": self.volume,
            "pan": self.pan,
            "is_muted": self.is_muted,
            "is_soloed": self.is_soloed,
            "selected_samples": self.selected_samples,
        }


@dataclass
class Pattern:
    """Pattern rítmico para un instrumento."""
    instrument: str
    steps: List[int]  # 1 = on, 0 = off
    velocity_variation: float = 0.2
    humanize: float = 0.1
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "instrument": self.instrument,
            "steps": self.steps,
            "velocity_variation": self.velocity_variation,
            "humanize": self.humanize,
        }


@dataclass
class Section:
    """Sección de una canción (Intro, Drop, Break, etc.)."""
    name: str
    bars: int
    start_bar: int
    energy_level: float
    patterns: Dict[str, Pattern] = field(default_factory=dict)
    tempo_multiplier: float = 1.0  # Para cambios de tempo
    
    # Notas de progresión armónica (si aplica)
    chord_progression: List[str] = field(default_factory=list)
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "name": self.name,
            "bars": self.bars,
            "start_bar": self.start_bar,
            "energy_level": self.energy_level,
            "patterns": {k: v.to_dict() for k, v in self.patterns.items()},
            "tempo_multiplier": self.tempo_multiplier,
            "chord_progression": self.chord_progression,
        }


@dataclass
class SongConfig:
    """Configuración completa de una canción generada."""
    bpm: float
    key: str
    style: str
    structure: str
    total_bars: int
    sections: List[Section] = field(default_factory=list)
    tracks: List[TrackConfig] = field(default_factory=list)
    
    # Metadatos
    generated_from_reference: str = ""
    generation_timestamp: str = ""
    variation_seed: int = 0
    
    # Samples usados
    drum_kit: Dict[str, Any] = field(default_factory=dict)
    bass_samples: List[Dict[str, Any]] = field(default_factory=list)
    synth_samples: List[Dict[str, Any]] = field(default_factory=list)
    fx_samples: List[Dict[str, Any]] = field(default_factory=list)
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "bpm": self.bpm,
            "key": self.key,
            "style": self.style,
            "structure": self.structure,
            "total_bars": self.total_bars,
            "sections": [s.to_dict() for s in self.sections],
            "tracks": [t.to_dict() for t in self.tracks],
            "generated_from_reference": self.generated_from_reference,
            "generation_timestamp": self.generation_timestamp,
            "variation_seed": self.variation_seed,
            "drum_kit": self.drum_kit,
            "bass_samples": self.bass_samples,
            "synth_samples": self.synth_samples,
            "fx_samples": self.fx_samples,
        }


# =============================================================================
# CLASE PRINCIPAL: REGGAETON GENERATOR
# =============================================================================

class ReggaetonGenerator:
    """
    Generador profesional de tracks de reggaeton.
    
    Genera configuraciones completas de canciones incluyendo:
    - Estructura de secciones (Intro, Drop, Break, etc.)
    - Selección inteligente de samples basada en perfiles de usuario
    - Patterns rítmicos adaptados al estilo
    - Configuración de pistas y dispositivos
    """
    
    def __init__(self):
        self._user_profile: Optional[Dict[str, Any]] = None
        self._selected_samples: Dict[str, List[Dict[str, Any]]] = {}
        self._variation_seed: int = random.randint(1, 10000)
        random.seed(self._variation_seed)
        
    def generate(self, 
                 bpm: float = 95.0, 
                 key: str = "Am", 
                 style: str = "dembow", 
                 structure: str = "standard") -> SongConfig:
        """
        Genera una configuración completa de canción.
        
        Args:
            bpm: Tempo en beats por minuto (80-110 recomendado)
            key: Tonalidad (Am, Dm, Gm, etc.)
            style: Estilo (dembow, perreo, romantico, club, moombahton)
            structure: Estructura (minimal, standard, extended)
            
        Returns:
            SongConfig con toda la información de la canción generada
        """
        logger.info("Generando canción: BPM=%.1f, Key=%s, Style=%s, Structure=%s",
                   bpm, key, style, structure)
        
        # Validar parámetros
        bpm = self._validate_bpm(bpm)
        key = self._validate_key(key)
        style = self._validate_style(style)
        structure = self._validate_structure(structure)
        
        # Seleccionar samples
        self._select_samples_for_song(style, key, bpm)
        
        # Crear estructura de secciones
        sections = self._create_sections(structure)
        
        # Calcular total de compases
        total_bars = sum(s.bars for s in sections)
        
        # Crear configuración de pistas
        tracks = self._create_tracks(style, sections, bpm, key)
        
        # Construir SongConfig
        config = SongConfig(
            bpm=bpm,
            key=key,
            style=style,
            structure=structure,
            total_bars=total_bars,
            sections=sections,
            tracks=tracks,
            variation_seed=self._variation_seed,
            generation_timestamp=datetime.datetime.now().isoformat(),
            drum_kit=self._get_drum_kit_info(),
            bass_samples=self._selected_samples.get("bass", []),
            synth_samples=self._selected_samples.get("synth", []),
            fx_samples=self._selected_samples.get("fx", []),
        )
        
        logger.info("Canción generada: %d compases, %d pistas", 
                   total_bars, len(tracks))
        
        return config
    
    def generate_from_reference(self, 
                               reference_path: str, 
                               bpm: float = 0, 
                               key: str = "") -> SongConfig:
        """
        Genera una canción basada en un archivo de referencia.
        
        Analiza el archivo de referencia, obtiene el perfil de usuario
        y genera una canción que suena similar.
        
        Args:
            reference_path: Ruta al archivo de audio de referencia
            bpm: Tempo deseado (0 = usar el detectado en referencia)
            key: Tonalidad deseada ("" = usar la detectada en referencia)
            
        Returns:
            SongConfig basado en la referencia
        """
        logger.info("Generando desde referencia: %s", reference_path)
        
        try:
            # Obtener perfil de usuario desde referencia
            profile = get_user_profile(reference_path=reference_path)
            self._user_profile = profile
            
            # Determinar BPM y Key
            if bpm <= 0:
                bpm = profile.get("preferred_bpm", 95.0)
            if not key:
                key = profile.get("preferred_key", "Am")
            
            # Detectar estilo preferido basado en características
            style = self._detect_style_from_profile(profile)
            
            # Generar con la configuración detectada
            config = self.generate(
                bpm=bpm,
                key=key,
                style=style,
                structure="standard"
            )
            
            config.generated_from_reference = reference_path
            
            logger.info("Canción generada desde referencia: BPM=%.1f, Key=%s", 
                       bpm, key)
            
            return config
            
        except Exception as e:
            logger.error("Error generando desde referencia: %s. Fallback a defaults.", e)
            return self.generate(bpm=bpm or 95.0, key=key or "Am")
    
    # -------------------------------------------------------------------------
    # MÉTODOS DE VALIDACIÓN
    # -------------------------------------------------------------------------
    
    def _validate_bpm(self, bpm: float) -> float:
        """Valida y normaliza el BPM."""
        if bpm < 60 or bpm > 150:
            logger.warning("BPM fuera de rango reggaeton (%.1f), usando 95", bpm)
            return 95.0
        return bpm
    
    def _validate_key(self, key: str) -> str:
        """Valida y normaliza la tonalidad."""
        key = key.strip().capitalize()
        if key not in SUPPORTED_KEYS:
            logger.warning("Key no soportada (%s), usando Am", key)
            return "Am"
        return key
    
    def _validate_style(self, style: str) -> str:
        """Valida y normaliza el estilo."""
        style = style.lower().strip()
        if style not in SUPPORTED_STYLES:
            logger.warning("Style no soportado (%s), usando dembow", style)
            return "dembow"
        return style
    
    def _validate_structure(self, structure: str) -> str:
        """Valida y normaliza la estructura."""
        structure = structure.lower().strip()
        if structure not in SUPPORTED_STRUCTURES:
            logger.warning("Structure no soportada (%s), usando standard", structure)
            return "standard"
        return structure
    
    # -------------------------------------------------------------------------
    # SELECCIÓN DE SAMPLES
    # -------------------------------------------------------------------------
    
    def _select_samples_for_song(self, style: str, key: str, bpm: float):
        """Selecciona todos los samples necesarios para la canción."""
        logger.info("Seleccionando samples para %s en %s @ %.1f BPM", style, key, bpm)
        
        self._selected_samples = {}
        
        if not _ENGINES_AVAILABLE:
            logger.warning("Engines no disponibles, usando samples por defecto")
            return
        
        try:
            # Seleccionar samples por rol usando el motor de recomendaciones
            roles_to_select = {
                "kick": 3,
                "snare": 3,
                "clap": 2,
                "hat_closed": 3,
                "hat_open": 2,
                "bass": 5,
                "synth": 5,
                "fx": 3,
            }
            
            for role, count in roles_to_select.items():
                samples = get_recommended_samples(role=role, count=count)
                self._selected_samples[role] = samples
                logger.debug("Seleccionados %d samples para %s", len(samples), role)
                
        except Exception as e:
            logger.error("Error seleccionando samples: %s", e)
    
    def _get_drum_kit_info(self) -> Dict[str, Any]:
        """Retorna información del drum kit seleccionado."""
        kit = {
            "kick": self._selected_samples.get("kick", [{}])[0] if self._selected_samples.get("kick") else {},
            "snare": self._selected_samples.get("snare", [{}])[0] if self._selected_samples.get("snare") else {},
            "clap": self._selected_samples.get("clap", [{}])[0] if self._selected_samples.get("clap") else {},
            "hat_closed": self._selected_samples.get("hat_closed", [{}])[0] if self._selected_samples.get("hat_closed") else {},
            "hat_open": self._selected_samples.get("hat_open", [{}])[0] if self._selected_samples.get("hat_open") else {},
        }
        return kit
    
    # -------------------------------------------------------------------------
    # CREACIÓN DE ESTRUCTURA
    # -------------------------------------------------------------------------
    
    def _create_sections(self, structure: str) -> List[Section]:
        """Crea la estructura de secciones de la canción."""
        sections_config = STRUCTURE_CONFIGS[structure]
        sections = []
        current_bar = 0
        
        for section_name, bars in sections_config:
            energy = ENERGY_LEVELS.get(section_name, 0.5)
            
            # Crear patterns para esta sección
            patterns = self._create_patterns_for_section(section_name, energy)
            
            section = Section(
                name=section_name,
                bars=bars,
                start_bar=current_bar,
                energy_level=energy,
                patterns=patterns,
            )
            
            sections.append(section)
            current_bar += bars
        
        return sections
    
    def _create_patterns_for_section(self, section_name: str, energy: float) -> Dict[str, Pattern]:
        """Crea los patterns rítmicos para una sección."""
        patterns = {}
        
        # Adaptar patterns según la energía de la sección
        if section_name in ["intro", "outro"]:
            # Intro y outro: patterns mínimos
            patterns["kick"] = self._adapt_pattern(DEMBOW_PATTERNS["kick"], density=0.5)
            patterns["snare"] = self._adapt_pattern(DEMBOW_PATTERNS["snare"], density=0.3)
            patterns["hat_closed"] = self._adapt_pattern(DEMBOW_PATTERNS["hat_closed"], density=0.6)
            
        elif section_name in ["build", "build2"]:
            # Build: aumentar intensidad
            patterns["kick"] = self._adapt_pattern(DEMBOW_PATTERNS["kick"], density=0.8)
            patterns["snare"] = self._adapt_pattern(DEMBOW_PATTERNS["snare"], density=0.6)
            patterns["hat_closed"] = self._adapt_pattern(DEMBOW_PATTERNS["hat_closed"], density=0.9)
            patterns["bass"] = self._adapt_pattern(DEMBOW_PATTERNS["bass"], density=0.7)
            
        elif section_name in ["drop", "drop2"]:
            # Drop: full dembow
            patterns["kick"] = Pattern("kick", DEMBOW_PATTERNS["kick"])
            patterns["snare"] = Pattern("snare", DEMBOW_PATTERNS["snare"])
            patterns["hat_closed"] = Pattern("hat_closed", DEMBOW_PATTERNS["hat_closed"])
            patterns["hat_open"] = Pattern("hat_open", DEMBOW_PATTERNS["hat_open"])
            patterns["bass"] = Pattern("bass", DEMBOW_PATTERNS["bass"])
            
        elif section_name == "break":
            # Break: drums mínimos, espacio para vocals
            patterns["kick"] = self._adapt_pattern(DEMBOW_PATTERNS["kick"], density=0.3)
            patterns["snare"] = Pattern("snare", [0] * 16)
            patterns["hat_closed"] = self._adapt_pattern(DEMBOW_PATTERNS["hat_closed"], density=0.4)
            
        elif section_name == "groove":
            # Groove: dembow estándar
            patterns["kick"] = Pattern("kick", DEMBOW_PATTERNS["kick"])
            patterns["snare"] = Pattern("snare", DEMBOW_PATTERNS["snare"])
            patterns["hat_closed"] = Pattern("hat_closed", DEMBOW_PATTERNS["hat_closed"])
            patterns["bass"] = Pattern("bass", DEMBOW_PATTERNS["bass"])
            
        elif section_name == "peak":
            # Peak: máxima intensidad
            patterns["kick"] = self._adapt_pattern(DEMBOW_PATTERNS["kick"], density=1.0)
            patterns["snare"] = self._adapt_pattern(DEMBOW_PATTERNS["snare"], density=1.0)
            patterns["clap"] = Pattern("clap", DEMBOW_PATTERNS["snare"])
            patterns["hat_closed"] = self._adapt_pattern(DEMBOW_PATTERNS["hat_closed"], density=1.0)
            patterns["hat_open"] = Pattern("hat_open", DEMBOW_PATTERNS["hat_open"])
            patterns["bass"] = self._adapt_pattern(DEMBOW_PATTERNS["bass"], density=1.0)
        
        return patterns
    
    def _adapt_pattern(self, base_pattern: List[int], density: float) -> Pattern:
        """Adapta un pattern base a una densidad específica."""
        if density >= 1.0:
            return Pattern("unknown", base_pattern[:])
        
        adapted = []
        for step in base_pattern:
            if step == 1 and random.random() > density:
                adapted.append(0)
            else:
                adapted.append(step)
        
        return Pattern("unknown", adapted)
    
    # -------------------------------------------------------------------------
    # CREACIÓN DE PISTAS
    # -------------------------------------------------------------------------
    
    def _create_tracks(self, style: str, sections: List[Section], bpm: float, key: str) -> List[TrackConfig]:
        """Crea la configuración de todas las pistas."""
        tracks = []
        
        # Pista 1: Kick
        kick_track = self._create_drum_track("Kick", "kick", sections, bpm)
        tracks.append(kick_track)
        
        # Pista 2: Snare
        snare_track = self._create_drum_track("Snare", "snare", sections, bpm)
        tracks.append(snare_track)
        
        # Pista 3: Clap (si aplica según estilo)
        if style in ["club", "perreo", "moombahton"]:
            clap_track = self._create_drum_track("Clap", "clap", sections, bpm)
            tracks.append(clap_track)
        
        # Pista 4: Hi-Hats
        hat_track = self._create_drum_track("Hi-Hats", "hat_closed", sections, bpm)
        tracks.append(hat_track)
        
        # Pista 5: Open Hat
        open_hat_track = self._create_drum_track("Open Hat", "hat_open", sections, bpm)
        tracks.append(open_hat_track)
        
        # Pista 6: Bass
        bass_track = self._create_bass_track(sections, bpm, key)
        tracks.append(bass_track)
        
        # Pista 7: Synth Lead
        synth_track = self._create_synth_track("Lead", sections, bpm, key)
        tracks.append(synth_track)
        
        # Pista 8: FX
        fx_track = self._create_fx_track(sections, bpm)
        tracks.append(fx_track)
        
        # Aplicar variaciones de estilo
        self._apply_style_variations(tracks, style)
        
        return tracks
    
    def _create_drum_track(self, name: str, role: str, sections: List[Section], bpm: float) -> TrackConfig:
        """Crea una pista de percusión."""
        clips = []
        current_time = 0.0
        
        for section in sections:
            # Crear clips para esta sección basado en el pattern
            if role in section.patterns:
                pattern = section.patterns[role]
                notes = self._pattern_to_notes(pattern, current_time, section.bars, bpm)
                
                clip = ClipConfig(
                    name=f"{name} - {section.name}",
                    start_time=current_time,
                    duration=section.bars * 4.0,  # 4 beats por compás
                    notes=notes,
                )
                clips.append(clip)
            
            current_time += section.bars * 4.0
        
        # Samples seleccionados
        samples = self._selected_samples.get(role, [])
        
        return TrackConfig(
            name=name,
            track_type="midi",
            instrument_role=role,
            clips=clips,
            selected_samples=samples,
            device_chain=[
                DeviceConfig("Drum Rack", "instrument", "default"),
            ],
        )
    
    def _pattern_to_notes(self, pattern: Pattern, start_time: float, bars: int, bpm: float) -> List[Dict[str, Any]]:
        """Convierte un pattern a notas MIDI."""
        notes = []
        beats_per_step = 4.0 / 16  # 16 steps en 4 beats (un compás)
        
        for bar in range(bars):
            for step_idx, step in enumerate(pattern.steps):
                if step == 1:
                    note_time = start_time + (bar * 4.0) + (step_idx * beats_per_step)
                    velocity = 100 + random.randint(-20, 20)  # Variación de velocity
                    
                    notes.append({
                        "pitch": 36 if pattern.instrument == "kick" else
                                38 if pattern.instrument == "snare" else
                                39 if pattern.instrument == "clap" else
                                42 if pattern.instrument == "hat_closed" else
                                46 if pattern.instrument == "hat_open" else
                                36,
                        "start_time": note_time,
                        "duration": 0.25,
                        "velocity": max(1, min(127, velocity)),
                    })
        
        return notes
    
    def _create_bass_track(self, sections: List[Section], bpm: float, key: str) -> TrackConfig:
        """Crea la pista de bajo."""
        clips = []
        current_time = 0.0
        
        # Notas raíz según la tonalidad
        root_notes = {
            "Am": 57, "Dm": 62, "Gm": 55, "Cm": 60,
            "Em": 64, "Bm": 71, "Fm": 65, "F#m": 66,
            "C#m": 61, "G#m": 68,
        }
        root_note = root_notes.get(key, 57)
        
        for section in sections:
            if "bass" in section.patterns:
                pattern = section.patterns["bass"]
                notes = []
                
                beats_per_step = 4.0 / 16
                for bar in range(section.bars):
                    for step_idx, step in enumerate(pattern.steps):
                        if step == 1:
                            note_time = current_time + (bar * 4.0) + (step_idx * beats_per_step)
                            
                            # Variar pitch según progresión
                            pitch = root_note
                            if section.energy_level > 0.7 and random.random() > 0.7:
                                pitch += 7  # Quinta
                            
                            notes.append({
                                "pitch": pitch,
                                "start_time": note_time,
                                "duration": 0.5,
                                "velocity": 110,
                            })
                
                clip = ClipConfig(
                    name=f"Bass - {section.name}",
                    start_time=current_time,
                    duration=section.bars * 4.0,
                    notes=notes,
                )
                clips.append(clip)
            
            current_time += section.bars * 4.0
        
        return TrackConfig(
            name="Bass",
            track_type="midi",
            instrument_role="bass",
            clips=clips,
            selected_samples=self._selected_samples.get("bass", []),
            device_chain=[
                DeviceConfig("Operator", "instrument", "bass_preset"),
                DeviceConfig("EQ Eight", "audio_effect", "bass_eq"),
            ],
        )
    
    def _create_synth_track(self, synth_type: str, sections: List[Section], bpm: float, key: str) -> TrackConfig:
        """Crea una pista de sintetizador."""
        clips = []
        current_time = 0.0
        
        # Notas de la escala menor
        scale_notes = self._get_scale_notes(key)
        
        for section in sections:
            # Solo tocar en secciones con suficiente energía
            if section.energy_level >= 0.6:
                notes = []
                
                # Crear progresión armónica simple
                chord_progression = [0, 3, 0, 5]  # i - iv - i - VI
                
                for bar in range(section.bars):
                    chord_idx = bar % len(chord_progression)
                    root_offset = chord_progression[chord_idx]
                    
                    # Tocar notas del acorde
                    for beat in range(4):
                        if random.random() > 0.3:  # No tocar en todos los beats
                            note_time = current_time + (bar * 4.0) + beat
                            pitch = scale_notes[(root_offset + random.choice([0, 2, 4])) % 7]
                            
                            notes.append({
                                "pitch": pitch,
                                "start_time": note_time,
                                "duration": 1.0,
                                "velocity": int(80 + section.energy_level * 40),
                            })
                
                clip = ClipConfig(
                    name=f"Synth {synth_type} - {section.name}",
                    start_time=current_time,
                    duration=section.bars * 4.0,
                    notes=notes,
                )
                clips.append(clip)
            
            current_time += section.bars * 4.0
        
        return TrackConfig(
            name=f"Synth {synth_type}",
            track_type="midi",
            instrument_role="synth_lead",
            clips=clips,
            selected_samples=self._selected_samples.get("synth", []),
            device_chain=[
                DeviceConfig("Wavetable", "instrument", "lead_preset"),
                DeviceConfig("Reverb", "audio_effect", "synth_reverb"),
                DeviceConfig("Delay", "audio_effect", "synth_delay"),
            ],
        )
    
    def _create_fx_track(self, sections: List[Section], bpm: float) -> TrackConfig:
        """Crea la pista de efectos."""
        clips = []
        current_time = 0.0
        
        for section in sections:
            # FX en transiciones importantes
            if section.name in ["build", "build2"]:
                # Riser antes del drop
                notes = []
                for i in range(int(section.bars * 4)):
                    notes.append({
                        "pitch": 60 + i,
                        "start_time": current_time + i,
                        "duration": 0.5,
                        "velocity": 80 + i * 2,
                    })
                
                clip = ClipConfig(
                    name=f"FX Riser - {section.name}",
                    start_time=current_time,
                    duration=section.bars * 4.0,
                    notes=notes,
                )
                clips.append(clip)
                
            elif section.name in ["drop", "drop2", "peak"]:
                # Impact/Hit al inicio
                notes = [{
                    "pitch": 36,
                    "start_time": current_time,
                    "duration": 2.0,
                    "velocity": 120,
                }]
                
                clip = ClipConfig(
                    name=f"FX Impact - {section.name}",
                    start_time=current_time,
                    duration=section.bars * 4.0,
                    notes=notes,
                )
                clips.append(clip)
            
            current_time += section.bars * 4.0
        
        return TrackConfig(
            name="FX",
            track_type="midi",
            instrument_role="fx",
            clips=clips,
            selected_samples=self._selected_samples.get("fx", []),
            device_chain=[
                DeviceConfig("Simpler", "instrument", "fx_sampler"),
            ],
        )
    
    def _get_scale_notes(self, key: str) -> List[int]:
        """Retorna las notas MIDI de la escala menor dada la tonalidad."""
        root_notes = {
            "Am": 57, "Dm": 62, "Gm": 55, "Cm": 60,
            "Em": 64, "Bm": 71, "Fm": 65, "F#m": 66,
            "C#m": 61, "G#m": 68,
        }
        root = root_notes.get(key, 57)
        
        # Escala menor natural: 0, 2, 3, 5, 7, 8, 10
        intervals = [0, 2, 3, 5, 7, 8, 10]
        return [root + interval for interval in intervals]
    
    def _apply_style_variations(self, tracks: List[TrackConfig], style: str):
        """Aplica variaciones específicas del estilo a las pistas."""
        variations = STYLE_VARIATIONS.get(style, STYLE_VARIATIONS["dembow"])
        
        # Ajustar volumes según estilo
        for track in tracks:
            if track.instrument_role == "kick":
                track.volume = 0.9 if variations["kick_variation"] != "sparse" else 0.7
            elif track.instrument_role == "bass":
                track.volume = 0.85 if variations["bass_syncopation"] > 0.3 else 0.75
            elif track.instrument_role == "hat_closed":
                track.volume = 0.7 * variations["hat_density"]
    
    def _detect_style_from_profile(self, profile: Dict[str, Any]) -> str:
        """Detecta el estilo preferido basado en el perfil de usuario."""
        bpm = profile.get("preferred_bpm", 95.0)
        roles = profile.get("preferred_roles", [])
        
        # Heurísticas simples basadas en BPM
        if bpm > 105:
            return "club"
        elif bpm < 88:
            return "romantico"
        elif bpm > 98:
            return "perreo"
        
        # Default
        return "dembow"


# =============================================================================
# SONG GENERATOR (Alias para compatibilidad)
# =============================================================================

class SongGenerator(ReggaetonGenerator):
    """
    Alias de ReggaetonGenerator para compatibilidad con imports existentes.
    """
    def generate_config(self, genre: str = "reggaeton", style: str = "",
                       bpm: float = 0, key: str = "Am",
                       structure: str = "standard") -> Dict[str, Any]:
        """
        Método de compatibilidad que emula la interfaz antigua.
        Convierte los parámetros y llama al nuevo método generate().
        """
        # Usar style como style si está presente, si no usar genre
        actual_style = style if style else genre
        
        # Determinar BPM
        actual_bpm = bpm if bpm > 0 else 95.0
        
        config = self.generate(
            bpm=actual_bpm,
            key=key,
            style=actual_style,
            structure=structure
        )
        
        return config.to_dict()


# =============================================================================
# FUNCIONES DE CONVENIENCIA
# =============================================================================

_generator: Optional[ReggaetonGenerator] = None


def get_song_generator() -> ReggaetonGenerator:
    """Retorna instancia global del generador."""
    global _generator
    if _generator is None:
        _generator = ReggaetonGenerator()
    return _generator


def generate_song(bpm: float = 95.0,
                  key: str = "Am",
                  style: str = "dembow",
                  structure: str = "standard") -> Dict[str, Any]:
    """
    Función de conveniencia para generar una canción.
    
    Returns:
        Diccionario con la configuración de la canción.
    """
    generator = get_song_generator()
    config = generator.generate(bpm, key, style, structure)
    return config.to_dict()


def generate_from_reference(reference_path: str,
                            bpm: float = 0,
                            key: str = "") -> Dict[str, Any]:
    """
    Función de conveniencia para generar desde una referencia.
    
    Returns:
        Diccionario con la configuración basada en la referencia.
    """
    generator = get_song_generator()
    config = generator.generate_from_reference(reference_path, bpm, key)
    return config.to_dict()


def get_supported_styles() -> List[str]:
    """Retorna la lista de estilos soportados."""
    return SUPPORTED_STYLES.copy()


def get_supported_structures() -> List[str]:
    """Retorna la lista de estructuras soportadas."""
    return SUPPORTED_STRUCTURES.copy()


# =============================================================================
# MAIN / TEST
# =============================================================================

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    
    print("=" * 70)
    print("SONG GENERATOR - Reggaeton Professional Track Generator")
    print("=" * 70)
    
    # Test 1: Generar canción standard
    print("\n1. Generando canción 'standard' en estilo 'dembow'...")
    generator = ReggaetonGenerator()
    config = generator.generate(bpm=95, key="Am", style="dembow", structure="standard")
    
    print(f"   BPM: {config.bpm}")
    print(f"   Key: {config.key}")
    print(f"   Style: {config.style}")
    print(f"   Structure: {config.structure}")
    print(f"   Total Bars: {config.total_bars}")
    print(f"   Sections: {[s.name for s in config.sections]}")
    print(f"   Tracks: {[t.name for t in config.tracks]}")
    
    # Test 2: Generar canción minimal
    print("\n2. Generando canción 'minimal' en estilo 'perreo'...")
    config2 = generator.generate(bpm=98, key="Gm", style="perreo", structure="minimal")
    print(f"   Total Bars: {config2.total_bars}")
    print(f"   Sections: {[s.name for s in config2.sections]}")
    
    # Test 3: Generar canción extended
    print("\n3. Generando canción 'extended' en estilo 'club'...")
    config3 = generator.generate(bpm=105, key="Dm", style="club", structure="extended")
    print(f"   Total Bars: {config3.total_bars}")
    print(f"   Sections: {[s.name for s in config3.sections]}")
    
    # Test 4: Mostrar samples seleccionados
    print("\n4. Samples seleccionados:")
    for role, samples in generator._selected_samples.items():
        if samples:
            print(f"   {role}: {len(samples)} samples")
            for s in samples[:2]:
                print(f"      - {s.get('name', 'unknown')}")
    
    print("\n" + "=" * 70)
    print("Test completado!")
    print("=" * 70)
