"""
audio_soundscape.py - Soundscape y FX automáticos
T051-T062: Ambiente, FX Bus y Tonal Conflict Detection
"""
import logging
from typing import List, Dict, Any, Optional, Tuple
from pathlib import Path

logger = logging.getLogger("AudioSoundscape")

class SoundscapeEngine:
    """T051-T054: Engine de ambientes y texturas"""

    def __init__(self):
        self.atmos_templates = {
            'intro': ['*Atmos*Intro*.wav', '*Texture*Intro*.wav', '*Pad*Intro*.wav'],
            'break': ['*Atmos*Break*.wav', '*Texture*Break*.wav', '*Pad*Break*.wav'],
            'outro': ['*Atmos*Outro*.wav', '*Texture*Outro*.wav', '*Pad*Outro*.wav'],
        }

    def detect_ambience_gaps(self, timeline: List[Dict], min_gap_beats: float = 8.0) -> List[Dict]:
        """T051: Detecta espacios vacíos sin audio."""
        gaps = []
        for i in range(len(timeline) - 1):
            current_end = timeline[i].get('end', 0)
            next_start = timeline[i + 1].get('start', current_end)
            gap = next_start - current_end
            if gap >= min_gap_beats:
                gaps.append({
                    'start': current_end,
                    'end': next_start,
                    'duration': gap,
                    'section': timeline[i].get('kind', 'unknown')
                })
        return gaps

    def fill_with_atmos(self, gaps: List[Dict], genre: str, key: str) -> List[Dict]:
        """T052-T053: Carga atmos loops en gaps detectados."""
        atmos_events = []
        for gap in gaps:
            section = gap.get('section', 'intro')
            templates = self.atmos_templates.get(section, self.atmos_templates['break'])
            atmos_events.append({
                'position': gap['start'],
                'duration': min(gap['duration'], 16.0),  # Max 16 beats
                'templates': templates,
                'genre': genre,
                'key': key,
                'type': 'atmos_fill'
            })
        return atmos_events


class FXEngine:
    """T055-T058: Engine de FX automáticos"""

    def __init__(self):
        self.fx_patterns = {
            'riser': {'template': '*Riser*.wav', 'pre_beats': 8},
            'downlifter': {'template': '*Downlifter*.wav', 'post_beats': 2},
            'impact': {'template': '*Impact*.wav', 'at_position': True},
            'crash': {'template': '*Crash*.wav', 'at_position': True},
            'snare_roll': {'template': '*Snare Roll*.wav', 'pre_beats': 4},
        }

    def auto_riser_before_drop(self, section_start: float, n_beats: int = 8) -> Optional[Dict]:
        """T055: Genera riser N beats antes de drop."""
        return {
            'type': 'riser',
            'position': max(0, section_start - n_beats),
            'duration': n_beats,
            'template': self.fx_patterns['riser']['template']
        }

    def auto_downlifter_transition(self, from_section: str, to_section: str,
                                    section_end: float) -> Optional[Dict]:
        """T056: Auto-downlifter en transiciones."""
        if to_section in ['drop', 'break'] and from_section in ['build', 'drop']:
            return {
                'type': 'downlifter',
                'position': section_end - 2,
                'duration': 2,
                'template': self.fx_patterns['downlifter']['template']
            }
        return None

    def auto_impact_on_downbeat(self, section_start: float, section_kind: str) -> Optional[Dict]:
        """T057: Impact/crash en downbeats de drop."""
        if section_kind in ['drop', 'build']:
            return {
                'type': 'impact',
                'position': section_start,
                'template': self.fx_patterns['impact']['template']
            }
        return None

    def auto_snare_roll(self, section_start: float, duration_beats: int = 4) -> Optional[Dict]:
        """T058: Snare roll automático antes de drops."""
        return {
            'type': 'snare_roll',
            'position': max(0, section_start - duration_beats),
            'duration': duration_beats,
            'template': self.fx_patterns['snare_roll']['template']
        }


class TonalAnalyzer:
    """T059-T062: Análisis de conflictos tonales"""

    NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']

    def detect_key_conflict(self, samples: List[Dict], target_key: str) -> List[Dict]:
        """T059: Detecta si samples tienen key conflict con target_key."""
        conflicts = []
        for sample in samples:
            sample_key = sample.get('key', '')
            if sample_key and sample_key != target_key:
                # Check compatibility using circle of fifths
                distance = self._key_distance(target_key, sample_key)
                if distance > 2:  # More than 2 steps on circle
                    conflicts.append({
                        'sample': sample.get('path', 'unknown'),
                        'sample_key': sample_key,
                        'target_key': target_key,
                        'distance': distance,
                        'severity': 'high' if distance > 4 else 'medium'
                    })
        return conflicts

    def _key_distance(self, key1: str, key2: str) -> int:
        """Calcula distancia en círculo de quintas."""
        # Normalize keys
        is_minor1 = 'm' in key1.lower()
        is_minor2 = 'm' in key2.lower()

        if is_minor1 != is_minor2:
            return 6  # Different modes = max distance

        root1 = key1.replace('m', '').replace('M', '')
        root2 = key2.replace('m', '').replace('M', '')

        try:
            idx1 = self.NOTE_NAMES.index(root1)
            idx2 = self.NOTE_NAMES.index(root2)
        except ValueError:
            return 6  # Unknown note

        # Distance on circle of fifths
        circle_of_fifths = [0, 7, 2, 9, 4, 11, 6, 1, 8, 3, 10, 5]  # Perfect fifths order
        pos1 = circle_of_fifths.index(idx1) if idx1 in circle_of_fifths else 0
        pos2 = circle_of_fifths.index(idx2) if idx2 in circle_of_fifths else 0

        return min(abs(pos1 - pos2), 12 - abs(pos1 - pos2))

    def suggest_transpose(self, sample_path: str, from_key: str, to_key: str) -> int:
        """T060-T061: Sugiere semitonos para transponer sample a key objetivo."""
        try:
            root_from = from_key.replace('m', '').replace('M', '')
            root_to = to_key.replace('m', '').replace('M', '')

            idx_from = self.NOTE_NAMES.index(root_from)
            idx_to = self.NOTE_NAMES.index(root_to)

            semitones = idx_to - idx_from
            # Normalize to -6 to +6 range
            if semitones > 6:
                semitones -= 12
            elif semitones < -6:
                semitones += 12

            return semitones
        except ValueError:
            return 0  # Can't calculate

    def generate_dissonance_alert(self, conflicts: List[Dict]) -> str:
        """T062: Genera alertas de disonancia."""
        if not conflicts:
            return "No tonal conflicts detected."

        high_conflicts = [c for c in conflicts if c['severity'] == 'high']
        if high_conflicts:
            return f"WARNING: {len(high_conflicts)} high-severity key conflicts detected!"
        return f"INFO: {len(conflicts)} minor key variations (acceptable)."
