"""
ARC 1: Advanced Transition Engine (T001-T020)

This module provides advanced DJ-style transition tools for AbletonMCP-AI,
enabling seamless mixing between tracks, sections, and scenes.

Key Features:
- Crossfades with various curve shapes (T001, T018)
- EQ kills and frequency-based transitions (T002, T003, T004)
- FX-based transitions (echo-out, reverb wash, noise risers) (T005, T012, T015)
- Tempo and pitch manipulation (T006, T017)
- Creative transitions (vinyl stop, stutter, drop) (T009, T011, T014)
- Automated mixing tools (gap detection, fader macros) (T007, T010)
- Acapella isolation and overlay (T013)
- Impact/crash injection (T016)
- Sub-bass ducking and sidechain (T019)
- Loop-to-fade capture (T008)

Usage:
    from transition_engine import TransitionEngine, CrossfadeShape
    
    engine = TransitionEngine(ableton_connection)
    engine.apply_crossfade(track_a=0, track_b=1, duration_bars=4, shape=CrossfadeShape.EXPONENTIAL)
"""

import math
import random
import logging
from typing import Dict, Any, List, Optional, Tuple, Union
from dataclasses import dataclass
from enum import Enum

try:
    import numpy as np
    NUMPY_AVAILABLE = True
except ImportError:
    NUMPY_AVAILABLE = False

logger = logging.getLogger("AbletonMCP-AI.TransitionEngine")


class CrossfadeShape(Enum):
    """Available crossfade curve shapes."""
    LINEAR = "linear"
    EXPONENTIAL = "exponential"
    LOGARITHMIC = "logarithmic"
    S_CURVE = "s_curve"
    PUNCH = "punch"
    DIP = "dip"


class FilterType(Enum):
    """Filter types for sweep transitions."""
    LOW_PASS = "low_pass"
    HIGH_PASS = "high_pass"
    BAND_PASS = "band_pass"
    NOTCH = "notch"


class TransitionFXType(Enum):
    """FX types for transition effects."""
    RISER = "riser"
    DOWNSWEEP = "downsweep"
    NOISE = "noise"
    REVERB_WASH = "reverb_wash"
    ECHO_OUT = "echo_out"
    CRASH = "crash"


@dataclass
class TransitionPoint:
    """A point in a transition automation curve."""
    time: float  # In beats
    value: float  # 0.0 - 1.0
    curve: str = "linear"  # "linear", "exponential", "s_curve"


@dataclass
class CrossfadeConfig:
    """Configuration for a crossfade transition."""
    track_out: int
    track_in: int
    start_bar: float
    duration_bars: float
    shape: CrossfadeShape = CrossfadeShape.EXPONENTIAL
    eq_kill_out: Optional[str] = None  # "low", "mid", "high", None
    eq_kill_in: Optional[str] = None
    filter_sweep: bool = False
    filter_type: FilterType = FilterType.HIGH_PASS


class TransitionEngine:
    """
    Advanced transition engine for DJ-style mixing in Ableton Live.
    
    Provides tools for:
    - Crossfades with various curves
    - EQ kills and frequency-based transitions
    - Filter sweeps
    - FX-based transitions
    - Tempo ramps
    - Creative transitions (vinyl stop, stutter, etc.)
    """

    def __init__(self, ableton_connection=None):
        self.ableton = ableton_connection
        self._active_transitions: Dict[str, Any] = {}
        
    # ========================================================================
    # T001: Crossfade Macro
    # ========================================================================
    
    def apply_crossfade(
        self,
        track_out: int,
        track_in: int,
        start_bar: float,
        duration_bars: float = 4.0,
        shape: CrossfadeShape = CrossfadeShape.EXPONENTIAL,
        curve_intensity: float = 0.5
    ) -> Dict[str, Any]:
        """
        T001: Apply a crossfade between two tracks.
        
        Args:
            track_out: Index of the outgoing track (fades out)
            track_in: Index of the incoming track (fades in)
            start_bar: Start position in bars
            duration_bars: Duration of the crossfade in bars
            shape: Crossfade curve shape
            curve_intensity: How pronounced the curve is (0.0 - 1.0)
            
        Returns:
            Dict with transition details and automation points
        """
        logger.info(
            f"[T001] Crossfade: track {track_out} -> {track_in} "
            f"at bar {start_bar} for {duration_bars} bars ({shape.value})"
        )
        
        # Generate automation curves
        out_curve = self._generate_fade_curve(1.0, 0.0, duration_bars, shape, curve_intensity)
        in_curve = self._generate_fade_curve(0.0, 1.0, duration_bars, shape, curve_intensity)
        
        result = {
            "track_out": track_out,
            "track_in": track_in,
            "start_bar": start_bar,
            "duration_bars": duration_bars,
            "shape": shape.value,
            "out_curve_points": len(out_curve),
            "in_curve_points": len(in_curve),
            "automation": {
                "out": [(p.time + start_bar * 4, p.value) for p in out_curve],
                "in": [(p.time + start_bar * 4, p.value) for p in in_curve]
            }
        }
        
        # Store for later application
        transition_id = f"crossfade_{track_out}_{track_in}_{start_bar}"
        self._active_transitions[transition_id] = result
        
        return result
    
    def _generate_fade_curve(
        self,
        start_val: float,
        end_val: float,
        duration: float,
        shape: CrossfadeShape,
        intensity: float = 0.5,
        num_points: int = 16
    ) -> List[TransitionPoint]:
        """Generate a fade curve with specified shape."""
        points = []
        
        for i in range(num_points):
            t = i / (num_points - 1)  # 0.0 to 1.0
            
            if shape == CrossfadeShape.LINEAR:
                value = start_val + (end_val - start_val) * t
                
            elif shape == CrossfadeShape.EXPONENTIAL:
                # Exponential curve for punchy fades
                exp_factor = 2.0 + intensity * 4.0
                if start_val > end_val:  # Fade out
                    value = end_val + (start_val - end_val) * (1 - t ** exp_factor)
                else:  # Fade in
                    value = start_val + (end_val - start_val) * (t ** exp_factor)
                    
            elif shape == CrossfadeShape.LOGARITHMIC:
                # Logarithmic curve for smooth fades
                log_factor = 0.5 + intensity * 2.0
                if start_val > end_val:  # Fade out
                    value = end_val + (start_val - end_val) * math.log(1 + (1-t) * log_factor) / math.log(1 + log_factor)
                else:  # Fade in
                    value = start_val + (end_val - start_val) * math.log(1 + t * log_factor) / math.log(1 + log_factor)
                    
            elif shape == CrossfadeShape.S_CURVE:
                # S-curve for smooth acceleration/deceleration
                s_factor = 2.0 + intensity * 4.0
                if start_val > end_val:  # Fade out
                    s_val = math.sin((1 - t) * math.pi / 2) ** s_factor
                    value = end_val + (start_val - end_val) * s_val
                else:  # Fade in
                    s_val = math.sin(t * math.pi / 2) ** s_factor
                    value = start_val + (end_val - start_val) * s_val
                    
            elif shape == CrossfadeShape.PUNCH:
                # Punch curve - dips slightly then comes up
                if start_val > end_val:  # Fade out
                    value = end_val + (start_val - end_val) * (1 - t ** 0.5)
                else:  # Fade in
                    punch_depth = intensity * 0.2
                    if t < 0.3:
                        value = start_val - punch_depth * (1 - t / 0.3)
                    else:
                        value = start_val + (end_val - start_val) * ((t - 0.3) / 0.7) ** 1.5
                        
            elif shape == CrossfadeShape.DIP:
                # Dip curve - both tracks dip in the middle
                dip_depth = intensity * 0.3
                if t < 0.5:
                    value = start_val - (start_val - end_val) * t * 2 - dip_depth * math.sin(t * math.pi)
                else:
                    value = start_val - (start_val - end_val) * (1 - (t - 0.5) * 2) + dip_depth * math.sin((1-t) * math.pi)
            else:
                value = start_val + (end_val - start_val) * t
            
            # Clamp value
            value = max(0.0, min(1.0, value))
            
            points.append(TransitionPoint(
                time=t * duration * 4,  # Convert to beats
                value=value,
                curve=shape.value
            ))
        
        return points
    
    # ========================================================================
    # T002: EQ Kill Logic
    # ========================================================================
    
    def apply_eq_kill(
        self,
        track_index: int,
        kill_type: str,  # "low", "mid", "high", "all"
        enable: bool = True,
        transition_bars: float = 0.0
    ) -> Dict[str, Any]:
        """
        T002: Apply or remove an EQ kill (cut low/mid/high frequencies).
        
        Args:
            track_index: Track to apply EQ kill to
            kill_type: Which frequencies to kill ("low", "mid", "high", "all")
            enable: True to enable kill, False to restore
            transition_bars: Duration for gradual kill (0 = instant)
            
        Returns:
            Dict with EQ configuration
        """
        logger.info(
            f"[T002] EQ Kill: track {track_index}, {kill_type} "
            f"{'ON' if enable else 'OFF'}"
        )
        
        # Define frequency ranges
        eq_bands = {
            "low": {"freq": 80, "q": 0.7, "gain_range": (-24, 0)},
            "mid": {"freq": 1000, "q": 0.7, "gain_range": (-24, 0)},
            "high": {"freq": 8000, "q": 0.7, "gain_range": (-24, 0)},
            "all": {"freq": 1000, "q": 0.3, "gain_range": (-60, 0)}
        }
        
        band = eq_bands.get(kill_type, eq_bands["all"])
        target_gain = band["gain_range"][0] if enable else band["gain_range"][1]
        
        return {
            "track_index": track_index,
            "kill_type": kill_type,
            "enabled": enable,
            "frequency": band["freq"],
            "q": band["q"],
            "target_gain_db": target_gain,
            "transition_bars": transition_bars,
            "method": "eq_three"  # Method: eq_three, auto_filter, or eq_eight
        }
    
    # ========================================================================
    # T003: Low-Kill Swap
    # ========================================================================
    
    def automate_low_kill_swap(
        self,
        track_out: int,
        track_in: int,
        swap_bar: float,
        kill_duration_bars: float = 2.0
    ) -> Dict[str, Any]:
        """
        T003: Automate a low-kill swap transition.
        
        Kills bass on incoming track until the swap point, then swaps
        the kills (restores incoming, kills outgoing).
        
        Args:
            track_out: Outgoing track index
            track_in: Incoming track index
            swap_bar: Bar where the swap happens
            kill_duration_bars: How long to maintain the kill after swap
            
        Returns:
            Dict with automation schedule
        """
        logger.info(
            f"[T003] Low-Kill Swap: track {track_out} -> {track_in} "
            f"at bar {swap_bar}"
        )
        
        schedule = [
            # Initial state: kill incoming track bass
            {
                "bar": swap_bar - kill_duration_bars,
                "action": "kill_low",
                "track": track_in,
                "value": True
            },
            # Swap point: restore incoming, kill outgoing
            {
                "bar": swap_bar,
                "action": "swap",
                "track_out": track_out,
                "track_in": track_in
            },
            # Release outgoing kill after transition
            {
                "bar": swap_bar + kill_duration_bars,
                "action": "restore_low",
                "track": track_out,
                "value": False
            }
        ]
        
        return {
            "track_out": track_out,
            "track_in": track_in,
            "swap_bar": swap_bar,
            "kill_duration_bars": kill_duration_bars,
            "schedule": schedule
        }
    
    # ========================================================================
    # T004: Filter Sweep Transitions
    # ========================================================================
    
    def apply_filter_sweep(
        self,
        track_index: int,
        filter_type: FilterType,
        start_bar: float,
        end_bar: float,
        start_freq: float = 20.0,
        end_freq: float = 20000.0,
        resonance: float = 0.7
    ) -> Dict[str, Any]:
        """
        T004: Apply a filter sweep automation.
        
        Args:
            track_index: Track to apply filter to
            filter_type: Type of filter sweep
            start_bar: Start position in bars
            end_bar: End position in bars
            start_freq: Starting frequency in Hz
            end_freq: Ending frequency in Hz
            resonance: Filter resonance (Q) value
            
        Returns:
            Dict with filter automation points
        """
        logger.info(
            f"[T004] Filter Sweep: track {track_index}, {filter_type.value} "
            f"from {start_freq}Hz to {end_freq}Hz "
            f"(bars {start_bar}-{end_bar})"
        )
        
        duration_bars = end_bar - start_bar
        num_points = max(8, int(duration_bars * 4))  # 1 point per beat minimum
        
        points = []
        for i in range(num_points):
            t = i / (num_points - 1)
            # Logarithmic frequency sweep
            freq = start_freq * (end_freq / start_freq) ** t
            points.append({
                "bar": start_bar + t * duration_bars,
                "frequency": freq,
                "resonance": resonance
            })
        
        return {
            "track_index": track_index,
            "filter_type": filter_type.value,
            "start_bar": start_bar,
            "end_bar": end_bar,
            "duration_bars": duration_bars,
            "points": points,
            "automation": "filter_freq"
        }
    
    # ========================================================================
    # T005: Echo-Out Transition
    # ========================================================================
    
    def apply_echo_out(
        self,
        track_index: int,
        start_bar: float,
        duration_bars: float = 4.0,
        feedback: float = 0.7,
        delay_time_beats: float = 0.375,  # 3/8 = dotted eighth
        wet_dry_curve: Optional[List[float]] = None
    ) -> Dict[str, Any]:
        """
        T005: Apply an echo-out transition effect.
        
        Creates a freeze-delay/reverb tail effect on the outgoing track.
        
        Args:
            track_index: Track to apply echo-out to
            start_bar: Start position
            duration_bars: Duration of the echo tail
            feedback: Delay feedback amount (0.0 - 1.0)
            delay_time_beats: Delay time in beats
            wet_dry_curve: Optional custom wet/dry curve
            
        Returns:
            Dict with echo configuration
        """
        logger.info(
            f"[T005] Echo-Out: track {track_index} at bar {start_bar} "
            f"for {duration_bars} bars"
        )
        
        # Default curve: fade in delay, then freeze
        if wet_dry_curve is None:
            wet_dry_curve = [
                0.0, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.0, 0.9, 0.8, 0.6, 0.4, 0.2, 0.1, 0.0
            ]
        
        points = []
        for i, wet in enumerate(wet_dry_curve):
            t = i / (len(wet_dry_curve) - 1) if len(wet_dry_curve) > 1 else 0
            points.append({
                "bar": start_bar + t * duration_bars,
                "wet": wet,
                "feedback": feedback * (1 - t * 0.5),  # Decrease feedback over time
                "delay_time": delay_time_beats
            })
        
        return {
            "track_index": track_index,
            "start_bar": start_bar,
            "duration_bars": duration_bars,
            "effect": "echo_out",
            "delay_time_beats": delay_time_beats,
            "feedback": feedback,
            "points": points,
            "device_chain": ["simple_delay", "reverb"]  # Recommended devices
        }
    
    # ========================================================================
    # T006: Tempo Ramp Transition
    # ========================================================================
    
    def apply_tempo_ramp(
        self,
        start_bpm: float,
        end_bpm: float,
        start_bar: float,
        duration_bars: float = 8.0,
        curve: str = "linear"  # "linear", "exponential", "s_curve"
    ) -> Dict[str, Any]:
        """
        T006: Apply a tempo ramp transition.
        
        Args:
            start_bpm: Starting tempo
            end_bpm: Ending tempo
            start_bar: Start position in bars
            duration_bars: Duration of the ramp
            curve: Ramp curve type
            
        Returns:
            Dict with tempo automation points
        """
        logger.info(
            f"[T006] Tempo Ramp: {start_bpm} -> {end_bpm} BPM "
            f"over {duration_bars} bars ({curve})"
        )
        
        num_points = max(16, int(duration_bars * 4))  # Fine resolution for smooth tempo
        points = []
        
        for i in range(num_points):
            t = i / (num_points - 1) if num_points > 1 else 0
            
            if curve == "exponential":
                # Exponential curve
                t = t ** 2
            elif curve == "s_curve":
                # S-curve
                t = 0.5 - 0.5 * math.cos(t * math.pi)
            
            bpm = start_bpm + (end_bpm - start_bpm) * t
            points.append({
                "bar": start_bar + t * duration_bars,
                "bpm": round(bpm, 2),
                "beat": int(start_bar * 4 + t * duration_bars * 4)
            })
        
        return {
            "start_bpm": start_bpm,
            "end_bpm": end_bpm,
            "start_bar": start_bar,
            "duration_bars": duration_bars,
            "curve": curve,
            "points": points,
            "total_change": round(end_bpm - start_bpm, 2)
        }
    
    # ========================================================================
    # T007: Volume Fader Macro
    # ========================================================================
    
    def apply_volume_fader(
        self,
        track_index: int,
        start_bar: float,
        end_bar: float,
        start_vol: float = 0.85,  # Ableton slider value (0.85 = 0dB)
        end_vol: float = 0.0,
        shape: str = "smooth_nonlinear"
    ) -> Dict[str, Any]:
        """
        T007: Apply a smooth volume fader automation.
        
        Args:
            track_index: Track to fade
            start_bar: Start position
            end_bar: End position
            start_vol: Starting volume (0.0 - 1.0, 0.85 = 0dB)
            end_vol: Ending volume
            shape: Fade curve shape
            
        Returns:
            Dict with volume automation points
        """
        logger.info(
            f"[T007] Volume Fader: track {track_index} "
            f"from {start_vol} to {end_vol} "
            f"(bars {start_bar}-{end_bar})"
        )
        
        duration_bars = end_bar - start_bar
        num_points = max(8, int(duration_bars * 4))
        
        points = []
        for i in range(num_points):
            t = i / (num_points - 1) if num_points > 1 else 0
            
            if shape == "smooth_nonlinear":
                # S-curve for smooth fade
                if start_vol > end_vol:  # Fade out
                    fade = math.cos(t * math.pi / 2)
                else:  # Fade in
                    fade = math.sin(t * math.pi / 2)
                vol = end_vol + (start_vol - end_vol) * fade
            elif shape == "exponential":
                # Exponential fade
                if start_vol > end_vol:
                    vol = end_vol + (start_vol - end_vol) * (1 - t ** 2)
                else:
                    vol = start_vol + (end_vol - start_vol) * (t ** 2)
            else:
                # Linear
                vol = start_vol + (end_vol - start_vol) * t
            
            points.append({
                "bar": start_bar + t * duration_bars,
                "volume": round(vol, 3)
            })
        
        return {
            "track_index": track_index,
            "start_bar": start_bar,
            "end_bar": end_bar,
            "start_volume": start_vol,
            "end_volume": end_vol,
            "shape": shape,
            "points": points
        }
    
    # ========================================================================
    # T008: Loop-to-Fade
    # ========================================================================
    
    def apply_loop_to_fade(
        self,
        track_index: int,
        start_bar: float,
        loop_duration_bars: float = 1.0,
        fade_duration_bars: float = 4.0,
        capture_position: Optional[float] = None
    ) -> Dict[str, Any]:
        """
        T008: Capture a 1-bar loop while fading.
        
        Args:
            track_index: Track to apply to
            start_bar: Start position
            loop_duration_bars: Duration of loop to capture
            fade_duration_bars: Total fade duration
            capture_position: Where to capture the loop (None = auto)
            
        Returns:
            Dict with loop and fade configuration
        """
        logger.info(
            f"[T008] Loop-to-Fade: track {track_index} at bar {start_bar}"
        )
        
        # Auto-capture: 1 bar before start
        if capture_position is None:
            capture_position = start_bar - loop_duration_bars
        
        return {
            "track_index": track_index,
            "start_bar": start_bar,
            "loop_duration_bars": loop_duration_bars,
            "fade_duration_bars": fade_duration_bars,
            "capture_position": capture_position,
            "actions": [
                {
                    "bar": capture_position,
                    "action": "create_loop_clip",
                    "length_beats": loop_duration_bars * 4
                },
                {
                    "bar": start_bar,
                    "action": "start_fade",
                    "volume_curve": "exponential_decay"
                },
                {
                    "bar": start_bar + fade_duration_bars,
                    "action": "end_fade",
                    "final_volume": 0.0
                }
            ]
        }
    
    # ========================================================================
    # T009: Reverse/Vinyl Stop
    # ========================================================================
    
    def apply_vinyl_stop(
        self,
        track_index: int,
        start_bar: float,
        stop_duration_beats: float = 2.0,
        include_reverse: bool = True
    ) -> Dict[str, Any]:
        """
        T009: Apply a vinyl stop (turntable stop) effect.
        
        Args:
            track_index: Track to apply to
            start_bar: Start position
            stop_duration_beats: Duration of the stop effect
            include_reverse: Whether to include reverse portion
            
        Returns:
            Dict with vinyl stop configuration
        """
        logger.info(
            f"[T009] Vinyl Stop: track {track_index} at bar {start_bar}"
        )
        
        actions = []
        
        # Pitch bend down (simulating slowing turntable)
        if include_reverse:
            actions.extend([
                {
                    "bar": start_bar,
                    "beat": 0,
                    "action": "pitch_bend",
                    "pitch": 0.0
                },
                {
                    "bar": start_bar,
                    "beat": stop_duration_beats * 0.3,
                    "action": "pitch_bend",
                    "pitch": -12.0  # Down one octave
                },
                {
                    "bar": start_bar,
                    "beat": stop_duration_beats * 0.6,
                    "action": "pitch_bend",
                    "pitch": -24.0  # Down two octaves
                },
                {
                    "bar": start_bar,
                    "beat": stop_duration_beats * 0.8,
                    "action": "reverse",
                    "duration_beats": stop_duration_beats * 0.2
                }
            ])
        else:
            actions.extend([
                {
                    "bar": start_bar,
                    "beat": 0,
                    "action": "pitch_bend",
                    "pitch": 0.0
                },
                {
                    "bar": start_bar,
                    "beat": stop_duration_beats,
                    "action": "pitch_bend",
                    "pitch": -36.0  # Stop completely
                }
            ])
        
        return {
            "track_index": track_index,
            "start_bar": start_bar,
            "stop_duration_beats": stop_duration_beats,
            "include_reverse": include_reverse,
            "actions": actions,
            "automation": ["pitch", "volume"]
        }
    
    # ========================================================================
    # T010: Transition Gap Detection
    # ========================================================================
    
    def detect_transition_gaps(
        self,
        tracks: List[int],
        start_bar: float,
        end_bar: float,
        min_gap_beats: float = 0.25
    ) -> Dict[str, Any]:
        """
        T010: Detect gaps in transition regions.
        
        Args:
            tracks: List of track indices to analyze
            start_bar: Start of region to analyze
            end_bar: End of region to analyze
            min_gap_beats: Minimum gap size to report
            
        Returns:
            Dict with gap analysis report
        """
        logger.info(
            f"[T010] Gap Detection: tracks {tracks} "
            f"bars {start_bar}-{end_bar}"
        )
        
        # This would normally query Live for actual clip positions
        # For now, return the analysis structure
        return {
            "region": {
                "start_bar": start_bar,
                "end_bar": end_bar,
                "duration_bars": end_bar - start_bar
            },
            "tracks_analyzed": tracks,
            "gaps_detected": [],  # Would be populated from Live data
            "smoothness_score": 1.0,  # 0.0 - 1.0
            "recommendations": []
        }
    
    # ========================================================================
    # T011: The Drop Transition
    # ========================================================================
    
    def apply_drop_transition(
        self,
        track_index: int,
        drop_bar: float,
        silence_beats: float = 1.0,
        build_bars: float = 4.0
    ) -> Dict[str, Any]:
        """
        T011: Apply "The Drop" transition with 1-beat silence before drop.
        
        Args:
            track_index: Track to apply to
            drop_bar: Bar where the drop occurs
            silence_beats: Duration of silence before drop
            build_bars: Length of build section
            
        Returns:
            Dict with drop transition configuration
        """
        logger.info(
            f"[T011] The Drop: track {track_index} at bar {drop_bar}"
        )
        
        silence_start = drop_bar * 4 - silence_beats  # Convert to beats
        
        return {
            "track_index": track_index,
            "drop_bar": drop_bar,
            "silence_beats": silence_beats,
            "silence_start_beat": silence_start,
            "build_start_bar": drop_bar - build_bars,
            "actions": [
                {
                    "bar": drop_bar - build_bars,
                    "action": "start_build",
                    "automation": ["filter_open", "volume_up"]
                },
                {
                    "bar": silence_start / 4,
                    "beat": silence_start % 4,
                    "action": "silence",
                    "duration_beats": silence_beats
                },
                {
                    "bar": drop_bar,
                    "beat": 0,
                    "action": "drop",
                    "automation": ["crash", "volume_snap"]
                }
            ]
        }
    
    # ========================================================================
    # T012: Noise Riser Generation
    # ========================================================================
    
    def generate_noise_riser(
        self,
        start_bar: float,
        duration_bars: float = 8.0,
        riser_type: str = "noise",  # "noise", "synth", "tonal"
        target_freq_start: float = 200,
        target_freq_end: float = 8000,
        intensity: str = "medium"  # "subtle", "medium", "heavy"
    ) -> Dict[str, Any]:
        """
        T012: Generate a noise riser sweep.
        
        Args:
            start_bar: Start position
            duration_bars: Duration of the riser
            riser_type: Type of riser sound
            target_freq_start: Starting filter frequency
            target_freq_end: Ending filter frequency
            intensity: Riser intensity level
            
        Returns:
            Dict with riser configuration
        """
        logger.info(
            f"[T012] Noise Riser: {riser_type} at bar {start_bar} "
            f"for {duration_bars} bars ({intensity})"
        )
        
        intensity_map = {
            "subtle": {"volume": 0.4, "filter_depth": 0.5},
            "medium": {"volume": 0.6, "filter_depth": 0.75},
            "heavy": {"volume": 0.85, "filter_depth": 1.0}
        }
        
        settings = intensity_map.get(intensity, intensity_map["medium"])
        
        # Generate filter sweep points
        num_points = int(duration_bars * 4)
        points = []
        for i in range(num_points):
            t = i / (num_points - 1) if num_points > 1 else 0
            # Exponential frequency sweep
            freq = target_freq_start * (target_freq_end / target_freq_start) ** t
            # Volume ramp up
            vol = settings["volume"] * (t ** 0.5)  # Square root for smooth start
            points.append({
                "bar": start_bar + t * duration_bars,
                "frequency": freq,
                "volume": vol,
                "resonance": 0.5 + t * 0.5  # Increase resonance
            })
        
        return {
            "start_bar": start_bar,
            "duration_bars": duration_bars,
            "riser_type": riser_type,
            "intensity": intensity,
            "frequency_range": [target_freq_start, target_freq_end],
            "points": points,
            "synthesis": {
                "type": riser_type,
                "waveform": "white_noise" if riser_type == "noise" else "saw",
                "filter": "low_pass",
                "envelope": "rising"
            }
        }
    
    # ========================================================================
    # T013: Acapella Overlay Logic
    # ========================================================================
    
    def apply_acapella_overlay(
        self,
        vocal_track: int,
        instrumental_tracks: List[int],
        start_bar: float,
        duration_bars: float = 16.0,
        eq_isolation: bool = True
    ) -> Dict[str, Any]:
        """
        T013: Apply acapella isolation and overlay effect.
        
        Isolates vocals by cutting instrumental frequencies and mixing
        over instrumental tracks.
        
        Args:
            vocal_track: Track containing vocals
            instrumental_tracks: Tracks to duck/scoop
            start_bar: Start position
            duration_bars: Duration of overlay
            eq_isolation: Whether to apply mid-range EQ isolation
            
        Returns:
            Dict with acapella overlay configuration
        """
        logger.info(
            f"[T013] Acapella Overlay: vocal track {vocal_track} "
            f"over tracks {instrumental_tracks}"
        )
        
        actions = []
        
        # Boost vocal mid-range
        if eq_isolation:
            actions.append({
                "bar": start_bar,
                "track": vocal_track,
                "action": "eq_boost",
                "frequency": 2500,
                "gain_db": 3,
                "q": 1.0
            })
        
        # Scoop mids from instrumental tracks
        for inst_track in instrumental_tracks:
            actions.append({
                "bar": start_bar,
                "track": inst_track,
                "action": "eq_cut",
                "frequency": 2500,
                "gain_db": -4,
                "q": 1.0
            })
        
        # Restore at end
        for track in [vocal_track] + instrumental_tracks:
            actions.append({
                "bar": start_bar + duration_bars,
                "track": track,
                "action": "eq_restore"
            })
        
        return {
            "vocal_track": vocal_track,
            "instrumental_tracks": instrumental_tracks,
            "start_bar": start_bar,
            "duration_bars": duration_bars,
            "actions": actions,
            "technique": "mid_range_scoop"
        }
    
    # ========================================================================
    # T014: Stutter Edit Macros
    # ========================================================================
    
    def apply_stutter_edit(
        self,
        track_index: int,
        start_bar: float,
        duration_beats: float = 2.0,
        stutter_division: str = "1/8",  # "1/8", "1/16", "1/32"
        fade_each: bool = True
    ) -> Dict[str, Any]:
        """
        T014: Apply stutter edit effect (1/8th, 1/16th looping).
        
        Args:
            track_index: Track to apply to
            start_bar: Start position
            duration_beats: Total duration of stutter effect
            stutter_division: Beat division for stutters
            fade_each: Whether to fade each stutter
            
        Returns:
            Dict with stutter configuration
        """
        logger.info(
            f"[T014] Stutter Edit: track {track_index} at bar {start_bar} "
            f"({stutter_division} notes)"
        )
        
        # Beat divisions
        division_map = {
            "1/4": 1.0,
            "1/8": 0.5,
            "1/16": 0.25,
            "1/32": 0.125
        }
        
        beat_length = division_map.get(stutter_division, 0.5)
        num_stutters = int(duration_beats / beat_length)
        
        stutters = []
        for i in range(num_stutters):
            start_beat = i * beat_length
            stutter = {
                "index": i,
                "start_beat": start_bar * 4 + start_beat,
                "duration_beats": beat_length,
                "action": "repeat_clip"
            }
            if fade_each:
                stutter["volume_curve"] = [1.0, 0.8, 0.6, 0.4, 0.2]
            stutters.append(stutter)
        
        return {
            "track_index": track_index,
            "start_bar": start_bar,
            "duration_beats": duration_beats,
            "stutter_division": stutter_division,
            "num_stutters": num_stutters,
            "stutters": stutters,
            "technique": "beat_repeat"
        }
    
    # ========================================================================
    # T015: Reverb Wash Transition
    # ========================================================================
    
    def apply_reverb_wash(
        self,
        track_index: int,
        start_bar: float,
        duration_bars: float = 4.0,
        max_wet: float = 1.0,
        decay_time: float = 8.0
    ) -> Dict[str, Any]:
        """
        T015: Apply a 100% wet reverb wash transition.
        
        Args:
            track_index: Track to apply to
            start_bar: Start position
            duration_bars: Duration of the wash effect
            max_wet: Maximum wetness (1.0 = 100%)
            decay_time: Reverb decay time in seconds
            
        Returns:
            Dict with reverb wash configuration
        """
        logger.info(
            f"[T015] Reverb Wash: track {track_index} at bar {start_bar}"
        )
        
        num_points = int(duration_bars * 4)
        points = []
        
        for i in range(num_points):
            t = i / (num_points - 1) if num_points > 1 else 0
            
            # Wet goes 0% -> 100% -> 0%
            if t < 0.5:
                wet = max_wet * (t * 2) ** 2  # Quadratic rise
            else:
                wet = max_wet * ((1 - t) * 2) ** 2  # Quadratic fall
            
            # Decay time increases then decreases
            if t < 0.3:
                decay = decay_time * (1 + t * 2)
            else:
                decay = decay_time * (1 + (1 - t) * 0.5)
            
            points.append({
                "bar": start_bar + t * duration_bars,
                "wet": round(wet, 3),
                "decay_time": round(decay, 1),
                "freeze": t > 0.4 and t < 0.8  # Freeze in the middle
            })
        
        return {
            "track_index": track_index,
            "start_bar": start_bar,
            "duration_bars": duration_bars,
            "max_wet": max_wet,
            "base_decay": decay_time,
            "points": points,
            "automation": ["wet_level", "decay_time", "freeze"]
        }
    
    # ========================================================================
    # T016: Impact/Crash Injection
    # ========================================================================
    
    def inject_impact_crash(
        self,
        track_index: int,
        position_bar: float,
        impact_type: str = "crash",  # "crash", "ride", "china", "fx"
        intensity: str = "medium",  # "subtle", "medium", "heavy"
        pre_delay_beats: float = 0.0
    ) -> Dict[str, Any]:
        """
        T016: Inject impact/crash cymbal on downbeat.
        
        Args:
            track_index: Track to place crash on
            position_bar: Bar position (usually on downbeat)
            impact_type: Type of impact sound
            intensity: Intensity level
            pre_delay_beats: Optional pre-delay for anticipation
            
        Returns:
            Dict with crash injection configuration
        """
        logger.info(
            f"[T016] Impact/Crash: {impact_type} at bar {position_bar}"
        )
        
        intensity_map = {
            "subtle": {"velocity": 80, "volume": 0.6},
            "medium": {"velocity": 100, "volume": 0.8},
            "heavy": {"velocity": 127, "volume": 1.0}
        }
        
        settings = intensity_map.get(intensity, intensity_map["medium"])
        
        return {
            "track_index": track_index,
            "position_bar": position_bar,
            "impact_type": impact_type,
            "intensity": intensity,
            "velocity": settings["velocity"],
            "volume": settings["volume"],
            "pre_delay_beats": pre_delay_beats,
            "pitch": 0,  # C for crash
            "automation": [
                {
                    "bar": position_bar - pre_delay_beats / 4,
                    "action": "create_clip",
                    "note": 60,  # C4 for crash
                    "velocity": settings["velocity"]
                }
            ]
        }
    
    # ========================================================================
    # T017: Backspin Simulation
    # ========================================================================
    
    def apply_backspin(
        self,
        track_index: int,
        start_bar: float,
        duration_beats: float = 2.0,
        speed_curve: str = "exponential"
    ) -> Dict[str, Any]:
        """
        T017: Apply a vinyl backspin effect.
        
        Args:
            track_index: Track to apply to
            start_bar: Start position
            duration_beats: Duration of the backspin
            speed_curve: How the speed decreases
            
        Returns:
            Dict with backspin configuration
        """
        logger.info(
            f"[T017] Backspin: track {track_index} at bar {start_bar}"
        )
        
        num_points = max(8, int(duration_beats))
        points = []
        
        for i in range(num_points):
            t = i / (num_points - 1) if num_points > 1 else 0
            
            if speed_curve == "exponential":
                # Exponential slowdown
                speed = (1 - t) ** 3
            elif speed_curve == "linear":
                speed = 1 - t
            else:  # s_curve
                speed = 0.5 + 0.5 * math.cos(t * math.pi)
            
            # Pitch follows speed (vinyl effect)
            pitch = -12 * (1 - speed)  # Down to -12 semitones
            
            points.append({
                "beat": t * duration_beats,
                "speed": round(speed, 3),
                "pitch_semitones": round(pitch, 1),
                "scratch_intensity": (1 - speed) * 0.3  # Add scratch noise
            })
        
        return {
            "track_index": track_index,
            "start_bar": start_bar,
            "duration_beats": duration_beats,
            "speed_curve": speed_curve,
            "points": points,
            "automation": ["pitch", "speed", "volume"],
            "technique": "vinyl_backspin"
        }
    
    # ========================================================================
    # T018: Advanced Crossfade Shapes
    # ========================================================================
    
    def get_crossfade_shapes(self) -> Dict[str, Any]:
        """
        T018: Return available crossfade shapes with descriptions.
        
        Returns:
            Dict with all available shapes and their characteristics
        """
        shapes = {
            "linear": {
                "description": "Straight line fade - most neutral",
                "use_case": "General purpose, no coloration",
                "characteristics": "Equal volume change throughout"
            },
            "exponential": {
                "description": "Exponential curve - punchy",
                "use_case": "Quick cuts, energetic transitions",
                "characteristics": "Fast start, slow end for fades"
            },
            "logarithmic": {
                "description": "Logarithmic curve - smooth",
                "use_case": "Smooth blends, ambient transitions",
                "characteristics": "Slow start, fast end for fades"
            },
            "s_curve": {
                "description": "Sigmoid curve - natural",
                "use_case": "Most musical transitions",
                "characteristics": "Slow-fast-slow, like human hearing"
            },
            "punch": {
                "description": "Punch curve - dip and rise",
                "use_case": "Emphasize downbeat",
                "characteristics": "Brief dip then strong arrival"
            },
            "dip": {
                "description": "Dip curve - both tracks dip",
                "use_case": "Breathing room in transition",
                "characteristics": "Both tracks quieter in middle"
            }
        }
        
        return {
            "available_shapes": list(shapes.keys()),
            "descriptions": shapes,
            "recommendations": {
                "house_techno": ["exponential", "s_curve"],
                "ambient": ["logarithmic", "s_curve"],
                "trap_hiphop": ["punch", "exponential"],
                "smooth": ["s_curve", "linear"]
            }
        }
    
    # ========================================================================
    # T019: Sub-Bass Ducking
    # ========================================================================
    
    def apply_sub_bass_ducking(
        self,
        target_track: int,
        trigger_track: int,
        reduction_db: float = -6.0,
        attack_ms: float = 5.0,
        release_ms: float = 100.0
    ) -> Dict[str, Any]:
        """
        T019: Apply sub-bass ducking (sidechain to kick).
        
        Args:
            target_track: Track to duck (usually bass/sub)
            trigger_track: Track that triggers ducking (usually kick)
            reduction_db: Amount of gain reduction
            attack_ms: Attack time in milliseconds
            release_ms: Release time in milliseconds
            
        Returns:
            Dict with sidechain configuration
        """
        logger.info(
            f"[T019] Sub-Bass Ducking: track {target_track} "
            f"triggered by track {trigger_track}"
        )
        
        return {
            "target_track": target_track,
            "trigger_track": trigger_track,
            "reduction_db": reduction_db,
            "attack_ms": attack_ms,
            "release_ms": release_ms,
            "frequency_focus": "sub",  # Focus on sub frequencies
            "sidechain_method": "compressor",  # or "auto_filter", "utility_gain"
            "ratio": 4.0,
            "threshold_db": -18.0,
            "knee": 0.0,
            "makeup_gain": 0.0
        }
    
    # ========================================================================
    # T020: Integration Test - 10-min Automated Mix
    # ========================================================================
    
    def create_automated_mix(
        self,
        duration_minutes: float = 10.0,
        num_tracks: int = 3,
        bpm_range: Tuple[float, float] = (120, 130),
        transition_interval_bars: float = 32.0
    ) -> Dict[str, Any]:
        """
        T020: Create a complete automated DJ mix.
        
        Args:
            duration_minutes: Total mix duration
            num_tracks: Number of tracks to mix between
            bpm_range: BPM range for tempo changes
            transition_interval_bars: Bars between transitions
            
        Returns:
            Dict with complete mix configuration
        """
        logger.info(
            f"[T020] Automated Mix: {duration_minutes}min, {num_tracks} tracks"
        )
        
        total_bars = int(duration_minutes * 4 * (bpm_range[0] / 60))
        num_transitions = int(total_bars / transition_interval_bars)
        
        mix_plan = {
            "duration_minutes": duration_minutes,
            "total_bars": total_bars,
            "num_tracks": num_tracks,
            "num_transitions": num_transitions,
            "tracks": [],
            "transitions": []
        }
        
        # Generate track schedule
        current_bpm = bpm_range[0]
        for i in range(num_tracks):
            track_start = i * (total_bars / num_tracks)
            track_duration = total_bars / num_tracks + 8  # Overlap
            
            mix_plan["tracks"].append({
                "index": i,
                "start_bar": track_start,
                "duration_bars": track_duration,
                "bpm": current_bpm
            })
            
            # Vary BPM slightly for each track
            current_bpm = random.uniform(bpm_range[0], bpm_range[1])
        
        # Generate transitions
        for i in range(num_transitions - 1):
            transition_bar = (i + 1) * transition_interval_bars
            track_out = i % num_tracks
            track_in = (i + 1) % num_tracks
            
            # Alternate transition types
            transition_types = [
                {"type": "crossfade", "shape": "exponential"},
                {"type": "eq_kill_swap", "kill": "low"},
                {"type": "filter_sweep", "filter": "high_pass"},
                {"type": "echo_out", "duration": 4},
            ]
            
            transition = random.choice(transition_types)
            transition.update({
                "bar": transition_bar,
                "track_out": track_out,
                "track_in": track_in
            })
            
            mix_plan["transitions"].append(transition)
        
        return mix_plan


# Helper function to get a transition engine instance
def get_transition_engine(ableton_connection=None):
    """Get a TransitionEngine instance."""
    return TransitionEngine(ableton_connection)


# T001-T020 Tool mapping for server.py integration
TRANSITION_TOOLS = {
    "T001": "apply_crossfade",
    "T002": "apply_eq_kill",
    "T003": "automate_low_kill_swap",
    "T004": "apply_filter_sweep",
    "T005": "apply_echo_out",
    "T006": "apply_tempo_ramp",
    "T007": "apply_volume_fader",
    "T008": "apply_loop_to_fade",
    "T009": "apply_vinyl_stop",
    "T010": "detect_transition_gaps",
    "T011": "apply_drop_transition",
    "T012": "generate_noise_riser",
    "T013": "apply_acapella_overlay",
    "T014": "apply_stutter_edit",
    "T015": "apply_reverb_wash",
    "T016": "inject_impact_crash",
    "T017": "apply_backspin",
    "T018": "get_crossfade_shapes",
    "T019": "apply_sub_bass_ducking",
    "T020": "create_automated_mix"
}


__all__ = [
    "TransitionEngine",
    "CrossfadeShape",
    "FilterType",
    "TransitionFXType",
    "TransitionPoint",
    "CrossfadeConfig",
    "get_transition_engine",
    "TRANSITION_TOOLS"
]
