"""
arrangement_intelligence.py - Lógica de arrangement para DJ profesional.

Este módulo implementa:
- T086: Estructura reggaeton 95 BPM
- T088: Mute throws (silencio antes del drop)
- T089: Energy curve checker

Proporciona lógica de arrangement de nivel DJ para reggaeton,
incluyendo estructuras de canción, curvas de energía y mute throws.
"""

import logging
from collections import defaultdict
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Set, Tuple

logger = logging.getLogger("ArrangementIntelligence")


REGGAETON_STRUCTURE_95BPM = {
    'intro':   {'start': 0,   'length': 32,  'energy': 0.3, 'layers': ['kick', 'hat', 'bass']},
    'build_a': {'start': 32,  'length': 32,  'energy': 0.6, 'layers': ['kick', 'hat', 'clap', 'bass', 'perc_main']},
    'drop_a':  {'start': 64,  'length': 64,  'energy': 1.0, 'layers': ['kick', 'hat', 'clap', 'bass', 'perc_main', 'perc_alt', 'synth']},
    'break':   {'start': 128, 'length': 32,  'energy': 0.2, 'layers': ['bass', 'synth', 'atmos']},
    'build_b': {'start': 160, 'length': 32,  'energy': 0.7, 'layers': ['kick', 'hat', 'clap', 'bass', 'perc_main', 'synth']},
    'drop_b':  {'start': 192, 'length': 64,  'energy': 1.0, 'layers': ['kick', 'hat', 'clap', 'bass', 'perc_main', 'perc_alt', 'synth', 'top_loop']},
    'outro':   {'start': 256, 'length': 32,  'energy': 0.2, 'layers': ['kick', 'hat', 'bass']},
}

MUTE_THROW_WINDOWS = [
    {'before_section': 'drop_a', 'start_beat': 61, 'end_beat': 64, 'layers_to_mute': ['kick', 'hat', 'clap']},
    {'before_section': 'drop_b', 'start_beat': 189, 'end_beat': 192, 'layers_to_mute': ['kick', 'hat', 'clap']},
]

ROLE_TO_TRACK_INDEX_MAP = {
    'kick': 0,
    'clap': 1,
    'hat': 2,
    'bass': 3,
    'perc_main': 4,
    'perc_alt': 5,
    'synth': 6,
    'top_loop': 7,
    'atmos': 8,
    'hat_open': 9,
    'snare': 10,
}

HARMONIC_TRACK_INDEX = 15
TOP_LOOP_TRACK_INDEX = 12
PERC_ALT_TRACK_INDEX = 11


@dataclass
class SectionInfo:
    name: str
    start: float
    end: float
    energy: float
    layers: List[str]
    
    @property
    def length(self) -> float:
        return self.end - self.start
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            'name': self.name,
            'start': self.start,
            'end': self.end,
            'length': self.length,
            'energy': self.energy,
            'layers': self.layers
        }


@dataclass
class EnergyCurveResult:
    score: float
    sections_analyzed: int
    sections_with_correct_energy: int
    deviations: List[Dict[str, Any]]
    recommendations: List[str]
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            'score': round(self.score, 3),
            'sections_analyzed': self.sections_analyzed,
            'sections_with_correct_energy': self.sections_with_correct_energy,
            'deviations': self.deviations,
            'recommendations': self.recommendations
        }


class ArrangementIntelligence:
    """
    Motor de inteligencia de arrangement para producción DJ profesional.
    
    Características:
    - Análisis de estructura reggaeton
    - Mute throws antes de drops
    - Verificación de curva de energía
    - Detección de gaps y secciones faltantes
    """
    
    TARGET_ENERGY_CURVE = {
        'intro': (0.2, 0.4),
        'build': (0.5, 0.8),
        'drop': (0.9, 1.0),
        'break': (0.1, 0.3),
        'outro': (0.1, 0.3)
    }
    
    MIN_LAYERS_BY_SECTION = {
        'intro': 2,
        'build': 4,
        'drop': 6,
        'break': 2,
        'outro': 2
    }
    
    def __init__(self, structure: Optional[Dict[str, Dict[str, Any]]] = None):
        self.structure = structure or REGGAETON_STRUCTURE_95BPM
        self._section_cache: Dict[str, SectionInfo] = {}
        self._build_section_cache()
    
    def _build_section_cache(self) -> None:
        for name, info in self.structure.items():
            self._section_cache[name] = SectionInfo(
                name=name,
                start=float(info['start']),
                end=float(info['start'] + info['length']),
                energy=float(info['energy']),
                layers=list(info['layers'])
            )
    
    def get_section_at_beat(self, beat: float) -> Optional[SectionInfo]:
        for section in self._section_cache.values():
            if section.start <= beat < section.end:
                return section
        return None
    
    def get_sections_by_energy(self, min_energy: float = 0.0, max_energy: float = 1.0) -> List[SectionInfo]:
        return [
            section for section in self._section_cache.values()
            if min_energy <= section.energy <= max_energy
        ]
    
    def get_mute_throw_positions(self) -> List[Dict[str, Any]]:
        """
        T088: Retorna posiciones donde deben aplicarse mute throws.
        
        Los mute throws silencian kick, hat y clap 3 beats antes del drop
        para crear el "pull-back" que hace que el drop golpee más fuerte.
        """
        positions = []
        for mute_info in MUTE_THROW_WINDOWS:
            before_section = mute_info['before_section']
            section = self._section_cache.get(before_section)
            
            if section:
                positions.append({
                    'before_section': before_section,
                    'mute_start': mute_info['start_beat'],
                    'mute_end': mute_info['end_beat'],
                    'drop_start': section.start,
                    'layers_to_mute': mute_info['layers_to_mute'],
                    'duration_beats': mute_info['end_beat'] - mute_info['start_beat'],
                    'reason': f"Pull-back before {before_section} for impact"
                })
        
        return positions
    
    def check_energy_curve(self, track_clips: Dict[str, List[Dict[str, Any]]]) -> EnergyCurveResult:
        """
        T089: Verifica qué tan bien la curva de energía sigue la estructura esperada.
        
        Args:
            track_clips: Dict mapeando nombre de track a lista de clips.
                        Cada clip debe tener 'start' y 'length'.
        
        Returns:
            EnergyCurveResult con score 0-1 y recomendaciones.
        """
        total_beats = self._get_total_beats(track_clips)
        if total_beats == 0:
            return EnergyCurveResult(
                score=0.0,
                sections_analyzed=0,
                sections_with_correct_energy=0,
                deviations=[{'error': 'No clips found'}],
                recommendations=['Add clips to analyze energy curve']
            )
        
        layer_activity_by_section: Dict[str, Set[str]] = defaultdict(set)
        deviations = []
        sections_correct = 0
        sections_analyzed = 0
        
        for section_name, section in self._section_cache.items():
            sections_analyzed += 1
            active_layers = set()
            
            for track_name, clips in track_clips.items():
                for clip in clips:
                    clip_start = float(clip.get('start', 0))
                    clip_length = float(clip.get('length', 4))
                    clip_end = clip_start + clip_length
                    
                    if clip_start < section.end and clip_end > section.start:
                        active_layers.add(track_name.lower())
            
            layer_activity_by_section[section_name] = active_layers
            
            expected_min, expected_max = self.TARGET_ENERGY_CURVE.get(
                section_name.replace('_a', '').replace('_b', ''),
                (0.3, 0.7)
            )
            
            min_layers = self.MIN_LAYERS_BY_SECTION.get(
                section_name.replace('_a', '').replace('_b', ''),
                2
            )
            
            actual_layer_count = len(active_layers)
            
            if actual_layer_count >= min_layers:
                sections_correct += 1
            else:
                deviations.append({
                    'section': section_name,
                    'expected_layers': min_layers,
                    'actual_layers': actual_layer_count,
                    'missing_layers': min_layers - actual_layer_count,
                    'active_layers': list(active_layers),
                    'expected_energy_range': (expected_min, expected_max),
                    'issue': f"Section has {actual_layer_count} layers, expected at least {min_layers}"
                })
        
        score = sections_correct / sections_analyzed if sections_analyzed > 0 else 0.0
        
        recommendations = self._generate_energy_recommendations(deviations, layer_activity_by_section)
        
        return EnergyCurveResult(
            score=score,
            sections_analyzed=sections_analyzed,
            sections_with_correct_energy=sections_correct,
            deviations=deviations,
            recommendations=recommendations
        )
    
    def _get_total_beats(self, track_clips: Dict[str, List[Dict[str, Any]]]) -> float:
        max_beat = 0.0
        for track_name, clips in track_clips.items():
            for clip in clips:
                clip_start = float(clip.get('start', 0))
                clip_length = float(clip.get('length', 4))
                max_beat = max(max_beat, clip_start + clip_length)
        return max_beat
    
    def _generate_energy_recommendations(
        self,
        deviations: List[Dict[str, Any]],
        layer_activity: Dict[str, Set[str]]
    ) -> List[str]:
        recommendations = []
        
        for deviation in deviations:
            section = deviation['section']
            missing = deviation['missing_layers']
            
            if missing > 0:
                recommendations.append(
                    f"Add {missing} more layer(s) to '{section}' section for proper energy"
                )
        
        for mute_pos in self.get_mute_throw_positions():
            before_section = mute_pos['before_section']
            if before_section.replace('_', '') in ['dropa', 'dropb']:
                recommendations.append(
                    f"Apply mute throw at beat {mute_pos['mute_start']}-{mute_pos['mute_end']} "
                    f"before {before_section} for impact"
                )
        
        return recommendations
    
    def get_gaps_in_section(
        self,
        track_clips: Dict[str, List[Dict[str, Any]]],
        gap_threshold_beats: float = 32.0
    ) -> List[Dict[str, Any]]:
        """
        Detecta gaps (huecos de silencio) mayores al threshold en cada track.
        """
        gaps = []
        
        for track_name, clips in track_clips.items():
            if not clips:
                gaps.append({
                    'track': track_name,
                    'start': 0,
                    'end': 288,
                    'duration': 288,
                    'type': 'empty_track'
                })
                continue
            
            sorted_clips = sorted(clips, key=lambda c: float(c.get('start', 0)))
            
            prev_end = 0.0
            for clip in sorted_clips:
                clip_start = float(clip.get('start', 0))
                gap_duration = clip_start - prev_end
                
                if gap_duration >= gap_threshold_beats:
                    gaps.append({
                        'track': track_name,
                        'start': prev_end,
                        'end': clip_start,
                        'duration': gap_duration,
                        'type': 'intra_track_gap'
                    })
                
                clip_length = float(clip.get('length', 4))
                prev_end = max(prev_end, clip_start + clip_length)
            
            total_beats = 288.0
            if prev_end < total_beats - gap_threshold_beats:
                gaps.append({
                    'track': track_name,
                    'start': prev_end,
                    'end': total_beats,
                    'duration': total_beats - prev_end,
                    'type': 'trailing_gap'
                })
        
        return gaps
    
    def get_missing_harmonic_coverage(self, track_clips: Dict[str, List[Dict[str, Any]]]) -> Dict[str, Any]:
        """
        T091: Analiza si el track harmónico tiene clips en arrangement.
        """
        harmonic_track = None
        for track_name in track_clips:
            if 'harm' in track_name.lower() or 'keys' in track_name.lower() or 'chord' in track_name.lower():
                harmonic_track = track_name
                break
        
        if harmonic_track is None:
            return {
                'has_harmonic_track': False,
                'clip_count': 0,
                'needs_population': True,
                'recommendation': 'Create and populate harmonic track (index 15)'
            }
        
        clips = track_clips.get(harmonic_track, [])
        clip_count = len(clips)
        
        return {
            'has_harmonic_track': True,
            'track_name': harmonic_track,
            'clip_count': clip_count,
            'needs_population': clip_count == 0,
            'recommendation': 'Populate harmonic track with chord progression' if clip_count == 0 else 'OK'
        }
    
    def get_top_loop_gaps(self, track_clips: Dict[str, List[Dict[str, Any]]], threshold: float = 32.0) -> Dict[str, Any]:
        """
        T092: Detecta gaps en el track top_loop.
        """
        top_loop_track = None
        for track_name in track_clips:
            if 'top' in track_name.lower() or 'top_loop' in track_name.lower():
                top_loop_track = track_name
                break
        
        if top_loop_track is None:
            return {
                'has_top_loop_track': False,
                'gaps': [],
                'recommendation': 'Create top_loop track (index 12)'
            }
        
        clips = track_clips.get(top_loop_track, [])
        gaps = []
        
        if clips:
            sorted_clips = sorted(clips, key=lambda c: float(c.get('start', 0)))
            prev_end = 0.0
            
            for clip in sorted_clips:
                clip_start = float(clip.get('start', 0))
                gap_duration = clip_start - prev_end
                
                if gap_duration >= threshold:
                    gaps.append({
                        'start': prev_end,
                        'end': clip_start,
                        'duration': gap_duration
                    })
                
                clip_length = float(clip.get('length', 4))
                prev_end = max(prev_end, clip_start + clip_length)
        
        most_used_sample = None
        if clips:
            sample_counts = defaultdict(int)
            for clip in clips:
                sample = clip.get('sample', clip.get('file_path', 'unknown'))
                sample_counts[sample] += 1
            if sample_counts:
                most_used_sample = max(sample_counts.items(), key=lambda x: x[1])[0]
        
        return {
            'has_top_loop_track': True,
            'track_name': top_loop_track,
            'gaps': gaps,
            'gap_count': len(gaps),
            'most_used_sample': most_used_sample,
            'recommendation': f"Fill gaps with sample: {most_used_sample}" if gaps and most_used_sample else "OK"
        }
    
    def get_perc_alt_gaps(self, track_clips: Dict[str, List[Dict[str, Any]]], threshold: float = 32.0) -> Dict[str, Any]:
        """
        T093: Detecta gaps en el track perc_alt.
        """
        perc_alt_track = None
        for track_name in track_clips:
            if 'perc_alt' in track_name.lower() or 'perc alt' in track_name.lower():
                perc_alt_track = track_name
                break
        
        if perc_alt_track is None:
            return {
                'has_perc_alt_track': False,
                'gaps': [],
                'recommendation': 'Create perc_alt track (index 11)'
            }
        
        clips = track_clips.get(perc_alt_track, [])
        gaps = []
        
        if clips:
            sorted_clips = sorted(clips, key=lambda c: float(c.get('start', 0)))
            prev_end = 0.0
            
            for clip in sorted_clips:
                clip_start = float(clip.get('start', 0))
                gap_duration = clip_start - prev_end
                
                if gap_duration >= threshold:
                    gaps.append({
                        'start': prev_end,
                        'end': clip_start,
                        'duration': gap_duration
                    })
                
                clip_length = float(clip.get('length', 4))
                prev_end = max(prev_end, clip_start + clip_length)
        
        return {
            'has_perc_alt_track': True,
            'track_name': perc_alt_track,
            'gaps': gaps,
            'gap_count': len(gaps),
            'recommendation': "Fill gaps with alternating perc 1 and perc 2" if gaps else "OK"
        }


_arrangement_intelligence_instance: Optional[ArrangementIntelligence] = None


def get_arrangement_intelligence() -> ArrangementIntelligence:
    global _arrangement_intelligence_instance
    if _arrangement_intelligence_instance is None:
        _arrangement_intelligence_instance = ArrangementIntelligence()
    return _arrangement_intelligence_instance


def apply_mute_throws(track_clips: Dict[str, List[Dict[str, Any]]]) -> Dict[str, Any]:
    """
    T088: Aplica mute throws al mapa de clips.
    
    Retorna información sobre los mute throws aplicados.
    """
    ai = get_arrangement_intelligence()
    mute_positions = ai.get_mute_throw_positions()
    
    applied_mutes = []
    
    for mute_info in mute_positions:
        mute_start = mute_info['mute_start']
        mute_end = mute_info['mute_end']
        layers_to_mute = mute_info['layers_to_mute']
        
        for layer in layers_to_mute:
            if layer in track_clips:
                clips = track_clips[layer]
                clips_to_modify = []
                
                for clip in clips:
                    clip_start = float(clip.get('start', 0))
                    if mute_start <= clip_start < mute_end:
                        clips_to_modify.append(clip)
                
                if clips_to_modify:
                    applied_mutes.append({
                        'layer': layer,
                        'mute_start': mute_start,
                        'mute_end': mute_end,
                        'clips_affected': len(clips_to_modify),
                        'action': 'mute_remove'
                    })
    
    return {
        'mute_throws_applied': len(applied_mutes),
        'details': applied_mutes,
        'positions': mute_positions
    }


def place_crash_at_drop(drop_position_beats: float, fx_track_index: int = 10) -> Dict[str, Any]:
    """
    T147: Place crash cymbal at drop position.
    
    Args:
        drop_position_beats: Position in beats where the drop occurs
        fx_track_index: Track index for FX (default 10)
    
    Returns:
        Dict with crash placement recommendation
    """
    crash_offset = -0.5
    crash_position = drop_position_beats + crash_offset
    crash_length = 2.0
    
    return {
        "status": "success",
        "fx_type": "crash",
        "track_index": fx_track_index,
        "position_beats": crash_position,
        "length_beats": crash_length,
        "timing": "half_beat_before_drop",
        "sample_recommendation": "crash_16th_hit_short_reverb.wav",
        "automation": {
            "envelope": "fast_attack_medium_decay",
            "volume_start": 0.9,
            "volume_end": 0.1,
            "fade_time_beats": 1.5
        },
        "message": "Crash placement configured for drop impact"
    }


def place_snare_roll(build_start_beats: float, build_end_beats: float, fx_track_index: int = 10, density: str = "medium") -> Dict[str, Any]:
    """
    T148: Place snare roll during build section.
    
    Args:
        build_start_beats: Start position in beats
        build_end_beats: End position in beats (drop position)
        fx_track_index: Track index for FX (default 10)
        density: Density level ('sparse', 'medium', 'heavy')
    
    Returns:
        Dict with snare roll placement recommendation
    """
    duration = build_end_beats - build_start_beats
    
    density_patterns = {
        "sparse": {
            "subdivisions": 4,
            "hit_pattern": [1, 0, 0, 0],
            "velocity_curve": "linear"
        },
        "medium": {
            "subdivisions": 8,
            "hit_pattern": [1, 0, 1, 0, 1, 0, 1, 0],
            "velocity_curve": "exponential"
        },
        "heavy": {
            "subdivisions": 16,
            "hit_pattern": [1, 1, 1, 1, 1, 1, 1, 1],
            "velocity_curve": "exponential_aggressive"
        }
    }
    
    pattern_config = density_patterns.get(density, density_patterns["medium"])
    subdivision_length = duration / pattern_config["subdivisions"]
    
    notes = []
    for i in range(pattern_config["subdivisions"]):
        if pattern_config["hit_pattern"][i % len(pattern_config["hit_pattern"])]:
            t = i * subdivision_length
            velocity_start = 60
            velocity_end = 127
            if pattern_config["velocity_curve"] == "linear":
                velocity = velocity_start + (velocity_end - velocity_start) * (t / duration)
            elif pattern_config["velocity_curve"] == "exponential":
                velocity = velocity_start + (velocity_end - velocity_start) * ((t / duration) ** 1.5)
            else:
                velocity = velocity_start + (velocity_end - velocity_start) * ((t / duration) ** 2)
            
            notes.append({
                "pitch": 38,
                "start_time": build_start_beats + t,
                "duration": 0.25,
                "velocity": int(min(127, max(1, velocity)))
            })
    
    return {
        "status": "success",
        "fx_type": "snare_roll",
        "track_index": fx_track_index,
        "start_beats": build_start_beats,
        "end_beats": build_end_beats,
        "duration_beats": duration,
        "density": density,
        "subdivisions": pattern_config["subdivisions"],
        "notes": notes,
        "velocity_curve": pattern_config["velocity_curve"],
        "message": "Snare roll placement configured for build"
    }


def place_riser(start_beats: float, end_beats: float, fx_track_index: int = 10, riser_type: str = "noise") -> Dict[str, Any]:
    """
    T149: Place riser effect during build section.
    
    Args:
        start_beats: Start position in beats
        end_beats: End position in beats (drop position)
        fx_track_index: Track index for FX (default 10)
        riser_type: Type of riser ('noise', 'synth', 'pitch')
    
    Returns:
        Dict with riser placement recommendation
    """
    duration = end_beats - start_beats
    
    riser_configs = {
        "noise": {
            "automation_type": "filter_sweep",
            "filter_start": 80,
            "filter_end": 12000,
            "volume_curve": "exponential"
        },
        "synth": {
            "automation_type": "pitch_rise",
            "semitones_start": 0,
            "semitones_end": 12,
            "volume_curve": "exponential"
        },
        "pitch": {
            "automation_type": "pitch_rise",
            "semitones_start": 0,
            "semitones_end": 24,
            "volume_curve": "aggressive"
        }
    }
    
    config = riser_configs.get(riser_type, riser_configs["noise"])
    
    num_automation_points = 16
    automation_points = []
    for i in range(num_automation_points + 1):
        t = i / num_automation_points
        bar = start_beats + t * duration
        
        if riser_type == "noise":
            value = config["filter_start"] + (config["filter_end"] - config["filter_start"]) * (t ** 1.5)
        else:
            value = config["semitones_start"] + (config["semitones_end"] - config["semitones_start"]) * (t ** 1.5)
        
        automation_points.append({
            "bar": bar,
            "time": t * duration,
            "value": value,
            "parameter": "filter_freq" if riser_type == "noise" else "pitch"
        })
    
    return {
        "status": "success",
        "fx_type": "riser",
        "riser_type": riser_type,
        "track_index": fx_track_index,
        "start_beats": start_beats,
        "end_beats": end_beats,
        "duration_beats": duration,
        "automation_type": config["automation_type"],
        "automation_points": automation_points,
        "volume_curve": config["volume_curve"],
        "message": "Riser placement configured with {0} automation points".format(len(automation_points))
    }


def place_downlifter(start_beats: float, end_beats: float, fx_track_index: int = 10, downlifter_type: str = "noise") -> Dict[str, Any]:
    """
    T150: Place downlifter effect after drop.
    
    Args:
        start_beats: Start position in beats (at drop)
        end_beats: End position in beats
        fx_track_index: Track index for FX (default 10)
        downlifter_type: Type of downlifter ('noise', 'reverse_crash', 'pitch')
    
    Returns:
        Dict with downlifter placement recommendation
    """
    duration = end_beats - start_beats
    
    downlifter_configs = {
        "noise": {
            "automation_type": "filter_fall",
            "filter_start": 12000,
            "filter_end": 80,
            "volume_curve": "decaying"
        },
        "reverse_crash": {
            "automation_type": "reverse_swell",
            "volume_start": 0.0,
            "volume_end": 0.9,
            "volume_curve": "reverse_envelope"
        },
        "pitch": {
            "automation_type": "pitch_fall",
            "semitones_start": 12,
            "semitones_end": -12,
            "volume_curve": "decaying"
        }
    }
    
    config = downlifter_configs.get(downlifter_type, downlifter_configs["noise"])
    
    num_automation_points = 12
    automation_points = []
    for i in range(num_automation_points + 1):
        t = i / num_automation_points
        bar = start_beats + t * duration
        
        if downlifter_type == "noise":
            value = config["filter_start"] - (config["filter_start"] - config["filter_end"]) * t
        elif downlifter_type == "reverse_crash":
            value = config["volume_start"] + (config["volume_end"] - config["volume_start"]) * (t ** 0.5)
        else:
            value = config["semitones_start"] - (config["semitones_start"] - config["semitones_end"]) * t
        
        automation_points.append({
            "bar": bar,
            "time": t * duration,
            "value": value,
            "parameter": "filter_freq" if downlifter_type == "noise" else ("volume" if downlifter_type == "reverse_crash" else "pitch")
        })
    
    return {
        "status": "success",
        "fx_type": "downlifter",
        "downlifter_type": downlifter_type,
        "track_index": fx_track_index,
        "start_beats": start_beats,
        "end_beats": end_beats,
        "duration_beats": duration,
        "automation_type": config["automation_type"],
        "automation_points": automation_points,
        "volume_curve": config["volume_curve"],
        "message": "Downlifter placement configured with {0} automation points".format(len(automation_points))
    }


def audit_arrangement_structure(track_clips: Dict[str, List[Dict[str, Any]]]) -> Dict[str, Any]:
    """
    T090: Audita la estructura del arrangement y retorna reporte.
    """
    ai = get_arrangement_intelligence()
    
    energy_result = ai.check_energy_curve(track_clips)
    gaps = ai.get_gaps_in_section(track_clips)
    harmonic_coverage = ai.get_missing_harmonic_coverage(track_clips)
    top_loop_gaps = ai.get_top_loop_gaps(track_clips)
    perc_alt_gaps = ai.get_perc_alt_gaps(track_clips)
    mute_throws = ai.get_mute_throw_positions()
    
    total_clips = sum(len(clips) for clips in track_clips.values())
    total_tracks = len([t for t, clips in track_clips.items() if clips])
    
    return {
        'energy_curve_score': energy_result.score,
        'energy_curve_details': energy_result.to_dict(),
        'total_clips': total_clips,
        'active_tracks': total_tracks,
        'gaps_detected': len(gaps),
        'gaps': gaps[:10],
        'harmonic_coverage': harmonic_coverage,
        'top_loop_status': top_loop_gaps,
        'perc_alt_status': perc_alt_gaps,
        'mute_throw_positions': mute_throws,
        'recommendations': energy_result.recommendations,
        'structure': {name: section.to_dict() for name, section in ai._section_cache.items()}
    }