"""
Section Generator - Agente 17: Generador de Secciones Basado en Composición

Este módulo proporciona generación inteligente de secciones musicales
intro, build, breakdown, chorus y outro con configuraciones predefinidas.

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

logger = logging.getLogger("SectionGenerator")


@dataclass
class SectionConfig:
    """Configuración completa de una sección musical."""
    section_type: str
    duration_bars: int
    energy_level: float
    tracks: List[Dict[str, Any]] = field(default_factory=list)
    automations: List[Dict[str, Any]] = field(default_factory=list)
    fx: List[Dict[str, Any]] = field(default_factory=list)
    progression: Optional[str] = None
    key: str = "Am"
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "section_type": self.section_type,
            "duration_bars": self.duration_bars,
            "energy_level": self.energy_level,
            "tracks": self.tracks,
            "automations": self.automations,
            "fx": self.fx,
            "progression": self.progression,
            "key": self.key,
        }


class SectionGenerator:
    """
    Generador de secciones musicales para composición basada en secciones.
    
    Crea configuraciones JSON para diferentes tipos de secciones:
    - Intro: Tensión creciente, elementos sparse
    - Build: Buildup gradual hacia el drop
    - Breakdown: Versión reducida, enfocada en melodía
    - Chorus: Máxima energía con todos los elementos
    - Outro: Recapitulación y cierre
    """
    
    def __init__(self, key: str = "Am", bpm: float = 95.0):
        self.key = key
        self.bpm = bpm
        self._section_counter = 0
        
    def generate_intro(
        self,
        build_method: str = "gradual",
        duration: int = 8,
        start_with_drums: bool = False,
        include_fx_riser: bool = True
    ) -> SectionConfig:
        """
        Genera configuración para sección Intro.
        
        Args:
            build_method: "gradual", "sudden", "ambient", "rhythmic"
            duration: Duración en compases (default 8)
            start_with_drums: Si comienza con percusión o no
            include_fx_riser: Si incluye riser al final
            
        Returns:
            SectionConfig configurada para intro
        """
        self._section_counter += 1
        tracks = []
        automations = []
        fx = []
        
        # Base: elementos mínimos
        if build_method == "ambient":
            # Comienza solo con pads/texturas
            tracks.append({
                "type": "chords",
                "variation": "sparse",
                "start_bar": 0,
                "density": 0.3,
            })
            if duration >= 8:
                # Añadir drums a la mitad
                tracks.append({
                    "type": "drums",
                    "variation": "minimal",
                    "start_bar": duration // 2,
                    "density": 0.4,
                })
                
        elif build_method == "rhythmic":
            # Comienza con percusión mínima
            tracks.append({
                "type": "drums",
                "variation": "minimal",
                "start_bar": 0,
                "elements": ["kick", "hat"],
            })
            if duration >= 6:
                tracks.append({
                    "type": "bass",
                    "variation": "sparse",
                    "start_bar": 4,
                })
                
        else:  # gradual o sudden
            if start_with_drums:
                tracks.append({
                    "type": "drums",
                    "variation": "minimal",
                    "start_bar": 0,
                    "density": 0.5,
                })
                tracks.append({
                    "type": "bass",
                    "variation": "sparse",
                    "start_bar": duration // 2,
                })
            else:
                # Comienza con bajo
                tracks.append({
                    "type": "bass",
                    "variation": "sparse",
                    "start_bar": 0,
                })
                tracks.append({
                    "type": "drums",
                    "variation": "minimal",
                    "start_bar": duration // 2,
                })
        
        # Añadir chords al final para buildup
        if duration >= 4:
            tracks.append({
                "type": "chords",
                "variation": "sparse",
                "start_bar": max(0, duration - 4),
                "density": 0.4,
            })
        
        # FX riser al final
        if include_fx_riser and duration >= 4:
            fx.append({
                "type": "riser",
                "start_bar": max(0, duration - 4),
                "duration": 4,
                "intensity": 0.7 if build_method == "gradual" else 0.9,
            })
        
        # Automación: filter sweep up
        automations.append({
            "parameter": "filter_freq",
            "start_bar": 0,
            "end_bar": duration,
            "start_value": 200,
            "end_value": 8000,
        })
        
        # Automación: volume fade in
        automations.append({
            "parameter": "master_volume",
            "start_bar": 0,
            "end_bar": min(4, duration),
            "start_value": 0.0,
            "end_value": 0.85,
        })
        
        logger.info("Intro generada: %s, %d compases", build_method, duration)
        
        return SectionConfig(
            section_type="intro",
            duration_bars=duration,
            energy_level=0.2,
            tracks=tracks,
            automations=automations,
            fx=fx,
            key=self.key,
        )
    
    def generate_build(
        self,
        riser_type: str = "standard",
        drum_fill_intensity: float = 0.8,
        duration: int = 8,
        filter_sweep: bool = True
    ) -> SectionConfig:
        """
        Genera configuración para sección Build (buildup).
        
        Args:
            riser_type: "standard", "noise", "synth", "vocal", "minimal"
            drum_fill_intensity: 0.0-1.0 intensidad de fills
            duration: Duración en compases
            filter_sweep: Si incluye sweep de filtro
            
        Returns:
            SectionConfig configurada para build
        """
        self._section_counter += 1
        tracks = []
        automations = []
        fx = []
        
        # Todos los elementos presentes pero en tensión creciente
        tracks.append({
            "type": "drums",
            "variation": "full",
            "start_bar": 0,
            "fill_intensity": drum_fill_intensity,
        })
        
        tracks.append({
            "type": "bass",
            "variation": "standard",
            "start_bar": 0,
            "automation": "rising_energy",
        })
        
        tracks.append({
            "type": "chords",
            "variation": "standard",
            "start_bar": 0,
        })
        
        # Riser según tipo
        if riser_type == "noise":
            fx.append({
                "type": "noise_riser",
                "start_bar": 0,
                "duration": duration,
                "intensity": 0.9,
            })
        elif riser_type == "synth":
            fx.append({
                "type": "synth_riser",
                "start_bar": 0,
                "duration": duration,
                "pitch_range": [36, 84],
            })
        elif riser_type == "vocal":
            fx.append({
                "type": "vocal_riser",
                "start_bar": duration // 2,
                "duration": duration // 2,
            })
        elif riser_type != "minimal":
            # Standard riser
            fx.append({
                "type": "riser",
                "start_bar": 0,
                "duration": duration,
                "intensity": 0.8,
            })
        
        # Drum fills en últimos compases
        if drum_fill_intensity > 0.5:
            fill_bars = list(range(max(0, duration - 2), duration))
            for bar in fill_bars:
                tracks.append({
                    "type": "perc_fill",
                    "start_bar": bar,
                    "intensity": drum_fill_intensity,
                })
        
        # Automaciones de tensión
        if filter_sweep:
            automations.append({
                "parameter": "filter_freq",
                "start_bar": 0,
                "end_bar": duration,
                "start_value": 800,
                "end_value": 18000,
                "curve": "exponential",
            })
        
        # Reverb aumentando
        automations.append({
            "parameter": "reverb_wet",
            "start_bar": 0,
            "end_bar": duration,
            "start_value": 0.2,
            "end_value": 0.6,
        })
        
        # Impact al final
        fx.append({
            "type": "impact",
            "start_bar": duration,
            "impact_type": "hit",
            "intensity": 1.0,
        })
        
        logger.info("Build generado: %s, %d compases", riser_type, duration)
        
        return SectionConfig(
            section_type="build",
            duration_bars=duration,
            energy_level=0.7,
            tracks=tracks,
            automations=automations,
            fx=fx,
            key=self.key,
        )
    
    def generate_breakdown(
        self,
        melodic_focus: bool = True,
        drum_reduction: float = 0.7,
        duration: int = 8,
        include_buildup: bool = True
    ) -> SectionConfig:
        """
        Genera configuración para sección Breakdown.
        
        Args:
            melodic_focus: Si enfocar en elementos melódicos
            drum_reduction: 0.0-1.0 cuánto reducir drums (0.7 = 70% reducción)
            duration: Duración en compases
            include_buildup: Si incluye buildup hacia el siguiente drop
            
        Returns:
            SectionConfig configurada para breakdown
        """
        self._section_counter += 1
        tracks = []
        automations = []
        fx = []
        
        # Drums reducidos
        if drum_reduction < 1.0:
            drum_variation = "minimal" if drum_reduction > 0.5 else "sparse"
            tracks.append({
                "type": "drums",
                "variation": drum_variation,
                "start_bar": 0,
                "elements": ["kick", "hat"] if drum_reduction > 0.5 else ["hat"],
            })
        
        # Bass reducido o melódico
        if melodic_focus:
            tracks.append({
                "type": "bass",
                "variation": "melodic",
                "start_bar": 0,
                "density": 0.5,
            })
        else:
            tracks.append({
                "type": "bass",
                "variation": "sparse",
                "start_bar": 0,
            })
        
        # Chords/Acordes siempre presentes en breakdown
        tracks.append({
            "type": "chords",
            "variation": "full",
            "start_bar": 0,
        })
        
        # Melody si melodic_focus
        if melodic_focus:
            tracks.append({
                "type": "melody",
                "variation": "lead",
                "start_bar": 0,
                "density": "medium",
            })
        
        # Downlifter al inicio (transición desde drop)
        fx.append({
            "type": "downlifter",
            "start_bar": 0,
            "duration": min(4, duration // 2),
            "intensity": 0.6,
        })
        
        # Buildup hacia el final si se solicita
        if include_buildup and duration >= 6:
            buildup_start = duration - 4
            fx.append({
                "type": "riser",
                "start_bar": buildup_start,
                "duration": 4,
                "intensity": 0.7,
            })
            
            # Añadir drums gradualmente
            tracks.append({
                "type": "drums",
                "variation": "standard",
                "start_bar": buildup_start,
                "density": 0.5 + (0.5 * (1 - drum_reduction)),
            })
            
            automations.append({
                "parameter": "filter_freq",
                "start_bar": buildup_start,
                "end_bar": duration,
                "start_value": 1000,
                "end_value": 12000,
            })
        
        # Automaciones
        automations.append({
            "parameter": "master_volume",
            "start_bar": 0,
            "end_bar": min(2, duration),
            "start_value": 0.9,
            "end_value": 0.75,  # Más bajo que el drop
        })
        
        logger.info("Breakdown generado: melodic=%s, %d compases", melodic_focus, duration)
        
        return SectionConfig(
            section_type="breakdown",
            duration_bars=duration,
            energy_level=0.3,
            tracks=tracks,
            automations=automations,
            fx=fx,
            key=self.key,
        )
    
    def generate_chorus(
        self,
        max_energy: bool = True,
        all_elements: bool = True,
        duration: int = 16,
        variation_type: str = "standard"
    ) -> SectionConfig:
        """
        Genera configuración para sección Chorus/Drop (máxima energía).
        
        Args:
            max_energy: Si usar máxima energía
            all_elements: Si incluir todos los elementos
            duration: Duración en compases
            variation_type: "standard", "minimal", "double", "bouncy"
            
        Returns:
            SectionConfig configurada para chorus
        """
        self._section_counter += 1
        tracks = []
        automations = []
        fx = []
        
        energy_mult = 1.0 if max_energy else 0.8
        
        # Drums: full energy
        drum_variation = variation_type if variation_type in ["standard", "minimal", "bouncy"] else "standard"
        tracks.append({
            "type": "drums",
            "variation": drum_variation,
            "start_bar": 0,
            "energy": energy_mult,
        })
        
        # Bass: staccato o melodic según variación
        bass_style = "staccato" if variation_type == "bouncy" else "melodic"
        tracks.append({
            "type": "bass",
            "variation": bass_style,
            "start_bar": 0,
        })
        
        if all_elements:
            # Chords
            tracks.append({
                "type": "chords",
                "variation": "full",
                "start_bar": 0,
            })
            
            # Melody lead
            tracks.append({
                "type": "melody",
                "variation": "lead",
                "start_bar": 0,
                "density": "dense" if max_energy else "medium",
            })
            
            # Percussion extra
            tracks.append({
                "type": "perc",
                "variation": "full",
                "start_bar": 0,
            })
            
            # FX elements
            tracks.append({
                "type": "fx",
                "variation": "ambient",
                "start_bar": 0,
            })
        
        # Impact al inicio
        fx.append({
            "type": "impact",
            "start_bar": 0,
            "impact_type": "sub_drop" if max_energy else "hit",
            "intensity": energy_mult,
        })
        
        # Fills cada 4-8 compases
        fill_interval = 4 if variation_type == "bouncy" else 8
        for bar in range(fill_interval, duration, fill_interval):
            tracks.append({
                "type": "perc_fill",
                "start_bar": bar,
                "intensity": 0.7 * energy_mult,
            })
        
        # Variaciones específicas
        if variation_type == "double":
            # Doble tempo feel
            tracks.append({
                "type": "drums",
                "variation": "double_time",
                "start_bar": duration // 2,
            })
        elif variation_type == "minimal":
            # Menos elementos pero más intensos
            tracks = [t for t in tracks if t["type"] in ["drums", "bass"]]
        
        # Automaciones
        automations.append({
            "parameter": "master_volume",
            "start_bar": 0,
            "end_bar": 1,
            "start_value": 0.7,
            "end_value": 0.95,
        })
        
        # Filter abierto
        automations.append({
            "parameter": "filter_freq",
            "start_bar": 0,
            "end_bar": 2,
            "start_value": 20000,
            "end_value": 20000,
        })
        
        logger.info("Chorus generado: max_energy=%s, %s, %d compases", max_energy, variation_type, duration)
        
        return SectionConfig(
            section_type="chorus",
            duration_bars=duration,
            energy_level=1.0 if max_energy else 0.85,
            tracks=tracks,
            automations=automations,
            fx=fx,
            key=self.key,
        )
    
    def generate_outro(
        self,
        recap_type: str = "full",
        ending_style: str = "fade",
        duration: int = 8,
        include_melody: bool = True
    ) -> SectionConfig:
        """
        Genera configuración para sección Outro.
        
        Args:
            recap_type: "full", "melody_only", "drums_only", "chords_only"
            ending_style: "fade", "cut", "breakdown", "loop"
            duration: Duración en compases
            include_melody: Si incluir recap de melodía
            
        Returns:
            SectionConfig configurada para outro
        """
        self._section_counter += 1
        tracks = []
        automations = []
        fx = []
        
        # Elementos según recap_type
        if recap_type in ["full", "drums_only"]:
            tracks.append({
                "type": "drums",
                "variation": "standard" if recap_type == "full" else "minimal",
                "start_bar": 0,
            })
        
        if recap_type in ["full", "melody_only"] and include_melody:
            tracks.append({
                "type": "melody",
                "variation": "lead",
                "start_bar": 0,
                "density": "sparse",
            })
        
        if recap_type in ["full", "chords_only"]:
            tracks.append({
                "type": "chords",
                "variation": "full",
                "start_bar": 0,
            })
        
        if recap_type == "full":
            tracks.append({
                "type": "bass",
                "variation": "melodic",
                "start_bar": 0,
            })
        
        # FX según ending_style
        if ending_style == "breakdown":
            # Downlifter dramático
            fx.append({
                "type": "downlifter",
                "start_bar": 0,
                "duration": duration // 2,
                "intensity": 0.8,
            })
            # Luego silencio parcial
            tracks.append({
                "type": "silence",
                "start_bar": duration // 2,
                "duration": duration // 2,
            })
        elif ending_style == "loop":
            # Solo disminuir elementos gradualmente
            for i, track in enumerate(tracks):
                track["fade_out_bar"] = duration - (i * 2)
        
        # Automación de fade out
        if ending_style in ["fade", "full"]:
            fade_start = max(0, duration - 4)
            automations.append({
                "parameter": "master_volume",
                "start_bar": fade_start,
                "end_bar": duration,
                "start_value": 0.9,
                "end_value": 0.0,
                "curve": "logarithmic",
            })
            
            # Filter cerrándose
            automations.append({
                "parameter": "filter_freq",
                "start_bar": fade_start,
                "end_bar": duration,
                "start_value": 15000,
                "end_value": 200,
            })
        
        # Impact final
        if ending_style == "cut":
            fx.append({
                "type": "impact",
                "start_bar": duration - 1,
                "impact_type": "hit",
                "intensity": 1.0,
            })
            # Corte abrupto
            automations.append({
                "parameter": "master_volume",
                "start_bar": duration - 0.5,
                "end_bar": duration,
                "start_value": 0.9,
                "end_value": 0.0,
            })
        
        logger.info("Outro generado: %s, %s, %d compases", recap_type, ending_style, duration)
        
        return SectionConfig(
            section_type="outro",
            duration_bars=duration,
            energy_level=0.15,
            tracks=tracks,
            automations=automations,
            fx=fx,
            key=self.key,
        )
    
    def generate_verse(
        self,
        variation: str = "standard",
        duration: int = 16,
        include_melody: bool = False
    ) -> SectionConfig:
        """
        Genera configuración para sección Verse.
        
        Args:
            variation: "standard", "minimal", "rhythmic", "melodic"
            duration: Duración en compases
            include_melody: Si incluir melodía vocal
            
        Returns:
            SectionConfig configurada para verse
        """
        self._section_counter += 1
        tracks = []
        automations = []
        fx = []
        
        # Drums siempre presentes
        drum_variation = "standard" if variation != "minimal" else "minimal"
        tracks.append({
            "type": "drums",
            "variation": drum_variation,
            "start_bar": 0,
        })
        
        # Bass
        if variation != "minimal":
            tracks.append({
                "type": "bass",
                "variation": "standard" if variation != "melodic" else "melodic",
                "start_bar": 0,
            })
        
        # Chords
        if variation in ["standard", "melodic"]:
            tracks.append({
                "type": "chords",
                "variation": "sparse" if variation == "standard" else "full",
                "start_bar": 0,
            })
        
        # Melody
        if include_melody or variation == "melodic":
            tracks.append({
                "type": "melody",
                "variation": "sparse",
                "start_bar": 0,
                "density": "sparse",
            })
        
        # Percusiones adicionales para variación rítmica
        if variation == "rhythmic":
            tracks.append({
                "type": "perc",
                "variation": "full",
                "start_bar": 0,
            })
        
        logger.info("Verse generado: %s, %d compases", variation, duration)
        
        return SectionConfig(
            section_type="verse",
            duration_bars=duration,
            energy_level=0.5,
            tracks=tracks,
            automations=automations,
            fx=fx,
            key=self.key,
        )
    
    def create_full_song_structure(
        self,
        structure_type: str = "standard",
        total_duration: int = 64,
        custom_sections: Optional[List[Dict]] = None
    ) -> List[SectionConfig]:
        """
        Crea una estructura completa de canción.
        
        Args:
            structure_type: "standard", "extended", "minimal", "custom"
            total_duration: Duración total aproximada en compases
            custom_sections: Lista de secciones personalizadas si structure_type="custom"
            
        Returns:
            Lista de SectionConfig
        """
        sections = []
        
        if structure_type == "standard":
            # Intro-Verse-Chorus-Verse-Chorus-Breakdown-Chorus-Outro
            sections = [
                self.generate_intro(build_method="gradual", duration=8),
                self.generate_verse(variation="standard", duration=16),
                self.generate_chorus(max_energy=True, all_elements=True, duration=16),
                self.generate_verse(variation="standard", duration=16),
                self.generate_chorus(max_energy=True, all_elements=True, duration=16),
                self.generate_breakdown(melodic_focus=True, drum_reduction=0.8, duration=8),
                self.generate_chorus(max_energy=True, all_elements=True, duration=16),
                self.generate_outro(recap_type="melody_only", ending_style="fade", duration=8),
            ]
        elif structure_type == "extended":
            # Con build antes del primer drop
            sections = [
                self.generate_intro(build_method="gradual", duration=8),
                self.generate_verse(variation="minimal", duration=8),
                self.generate_build(riser_type="standard", drum_fill_intensity=0.8, duration=8),
                self.generate_chorus(max_energy=True, all_elements=True, duration=16),
                self.generate_breakdown(melodic_focus=True, drum_reduction=0.7, duration=8),
                self.generate_build(riser_type="synth", drum_fill_intensity=0.9, duration=8),
                self.generate_chorus(max_energy=True, all_elements=True, duration=16, variation_type="double"),
                self.generate_outro(recap_type="full", ending_style="fade", duration=8),
            ]
        elif structure_type == "minimal":
            # Corto y simple
            sections = [
                self.generate_intro(build_method="rhythmic", duration=4),
                self.generate_chorus(max_energy=False, all_elements=True, duration=16),
                self.generate_breakdown(melodic_focus=False, drum_reduction=0.5, duration=4),
                self.generate_chorus(max_energy=True, all_elements=False, duration=8),
                self.generate_outro(recap_type="drums_only", ending_style="cut", duration=4),
            ]
        elif structure_type == "custom" and custom_sections:
            for sec in custom_sections:
                section_type = sec.get("type", "intro")
                duration = sec.get("duration", 8)
                
                if section_type == "intro":
                    sections.append(self.generate_intro(
                        build_method=sec.get("build_method", "gradual"),
                        duration=duration
                    ))
                elif section_type == "build":
                    sections.append(self.generate_build(
                        riser_type=sec.get("riser_type", "standard"),
                        drum_fill_intensity=sec.get("drum_fill_intensity", 0.8),
                        duration=duration
                    ))
                elif section_type == "breakdown":
                    sections.append(self.generate_breakdown(
                        melodic_focus=sec.get("melodic_focus", True),
                        drum_reduction=sec.get("drum_reduction", 0.7),
                        duration=duration
                    ))
                elif section_type == "chorus":
                    sections.append(self.generate_chorus(
                        max_energy=sec.get("max_energy", True),
                        all_elements=sec.get("all_elements", True),
                        duration=duration
                    ))
                elif section_type == "outro":
                    sections.append(self.generate_outro(
                        recap_type=sec.get("recap_type", "melody_only"),
                        ending_style=sec.get("ending_style", "fade"),
                        duration=duration
                    ))
                elif section_type == "verse":
                    sections.append(self.generate_verse(
                        variation=sec.get("variation", "standard"),
                        duration=duration
                    ))
        
        logger.info("Estructura completa generada: %d secciones, tipo=%s", len(sections), structure_type)
        return sections


# Singleton para uso global
_section_generator_instance = None


def get_section_generator(key: str = "Am", bpm: float = 95.0) -> SectionGenerator:
    """Obtiene instancia singleton del SectionGenerator."""
    global _section_generator_instance
    if _section_generator_instance is None:
        _section_generator_instance = SectionGenerator(key=key, bpm=bpm)
    return _section_generator_instance


def reset_section_generator():
    """Resetea el singleton para crear nuevo generador."""
    global _section_generator_instance
    _section_generator_instance = None


__all__ = [
    "SectionGenerator",
    "SectionConfig",
    "get_section_generator",
    "reset_section_generator",
]
