"""
Curve Interpolation Engine - Advanced automation curve algorithms.

This module provides various interpolation methods for automation curves:
- Linear interpolation
- Bezier curves with control points
- S-curve (sigmoid) interpolation
- Exponential interpolation
- Stepped interpolation

Also includes grid quantization utilities for precise automation timing.

Author: AbletonMCP_AI (Agente 6)
"""
import math
from typing import List, Tuple, Optional, Union, Dict, Any
from dataclasses import dataclass, field
from enum import Enum


# =============================================================================
# CONSTANTS
# =============================================================================

# Grid quantization values (in beats)
GRID_QUARTER = 1.0        # 1/4 note = 1 beat
GRID_EIGHTH = 0.5         # 1/8 note = 0.5 beat
GRID_SIXTEENTH = 0.25     # 1/16 note = 0.25 beat
GRID_THIRTYSECOND = 0.125 # 1/32 note = 0.125 beat

# Default curve resolution (points per beat)
DEFAULT_RESOLUTION = 16


# =============================================================================
# DATA CLASSES
# =============================================================================

class CurveType(Enum):
    """Enumeration of available curve types."""
    LINEAR = "linear"
    BEZIER = "bezier"
    S_CURVE = "s_curve"
    EXPONENTIAL = "exponential"
    STEPPED = "stepped"


@dataclass
class AutomationPoint:
    """Single automation point with time and value."""
    time: float  # Time in beats
    value: float  # Parameter value (typically 0.0 - 1.0)
    
    def to_dict(self) -> Dict[str, float]:
        return {"time": self.time, "value": self.value}


@dataclass
class CurveConfig:
    """Configuration for curve generation."""
    curve_type: CurveType = CurveType.LINEAR
    resolution: int = DEFAULT_RESOLUTION  # Points per beat
    quantize_grid: Optional[float] = None  # Grid size for quantization
    
    # Bezier specific
    control_points: List[Tuple[float, float]] = field(default_factory=list)
    
    # Exponential specific
    exponent: float = 2.0
    
    # S-curve specific
    steepness: float = 6.0
    
    # Stepped specific
    steps: int = 4


# =============================================================================
# UTILITY FUNCTIONS
# =============================================================================

def quantize_time(time: float, grid: float) -> float:
    """
    Quantize a time value to the nearest grid division.
    
    Args:
        time: Time in beats
        grid: Grid size in beats (e.g., 0.25 for 1/16th)
    
    Returns:
        Quantized time value
    """
    if grid <= 0:
        return time
    return round(time / grid) * grid


def quantize_points(points: List[Tuple[float, float]], 
                    grid: float) -> List[Tuple[float, float]]:
    """
    Quantize all points in a list to a grid.
    
    Args:
        points: List of (time, value) tuples
        grid: Grid size in beats
    
    Returns:
        List of quantized (time, value) tuples
    """
    return [(quantize_time(t, grid), v) for t, v in points]


# =============================================================================
# INTERPOLATION FUNCTIONS
# =============================================================================

def linear_interpolation(points: List[Tuple[float, float]], 
                         resolution: int = DEFAULT_RESOLUTION) -> List[AutomationPoint]:
    """
    Generate linear interpolation between automation points.
    
    Creates evenly spaced points between each pair of control points
    using linear interpolation.
    
    Args:
        points: List of (time, value) control points
        resolution: Number of points per beat
    
    Returns:
        List of interpolated AutomationPoint objects
    
    Example:
        >>> points = [(0.0, 0.0), (4.0, 1.0)]
        >>> result = linear_interpolation(points, resolution=4)
        >>> len(result)  # 4 beats * 4 points + 1 end point = 17
        17
    """
    if len(points) < 2:
        return [AutomationPoint(t, v) for t, v in points]
    
    result = []
    
    for i in range(len(points) - 1):
        start_time, start_val = points[i]
        end_time, end_val = points[i + 1]
        
        # Calculate number of steps for this segment
        duration = end_time - start_time
        num_steps = max(1, int(duration * resolution))
        
        for step in range(num_steps + 1):
            t = step / num_steps
            interp_time = start_time + t * duration
            interp_value = start_val + t * (end_val - start_val)
            result.append(AutomationPoint(interp_time, interp_value))
    
    # Remove duplicates at segment boundaries
    unique_result = []
    last_time = None
    for point in result:
        if last_time is None or abs(point.time - last_time) > 0.0001:
            unique_result.append(point)
            last_time = point.time
    
    return unique_result


def bezier_interpolation(points: List[Tuple[float, float]],
                        control_points: List[Tuple[float, float]],
                        resolution: int = DEFAULT_RESOLUTION) -> List[AutomationPoint]:
    """
    Generate Bezier curve interpolation.
    
    Creates smooth curves using cubic Bezier interpolation with control points.
    
    Args:
        points: List of (time, value) anchor points
        control_points: List of (time, value) control points
        resolution: Number of points per beat
    
    Returns:
        List of interpolated AutomationPoint objects
    
    Note:
        For each segment between two anchor points, you need 2 control points.
        If insufficient control points provided, falls back to linear.
    """
    if len(points) < 2:
        return [AutomationPoint(t, v) for t, v in points]
    
    result = []
    segments = len(points) - 1
    controls_per_segment = 2
    
    for i in range(segments):
        p0 = points[i]
        p3 = points[i + 1]
        
        # Get control points for this segment
        control_start_idx = i * controls_per_segment
        if control_start_idx + 1 < len(control_points):
            p1 = control_points[control_start_idx]
            p2 = control_points[control_start_idx + 1]
        else:
            # Fallback: use linear interpolation for control points
            p1 = (p0[0] + (p3[0] - p0[0]) * 0.33, p0[1] + (p3[1] - p0[1]) * 0.33)
            p2 = (p0[0] + (p3[0] - p0[0]) * 0.66, p0[1] + (p3[1] - p0[1]) * 0.66)
        
        # Calculate number of steps
        duration = p3[0] - p0[0]
        num_steps = max(1, int(duration * resolution))
        
        # Cubic Bezier formula: B(t) = (1-t)^3*P0 + 3(1-t)^2*t*P1 + 3(1-t)*t^2*P2 + t^3*P3
        for step in range(num_steps + 1):
            t = step / num_steps
            t_inv = 1 - t
            
            # Cubic Bezier coefficients
            c0 = t_inv * t_inv * t_inv
            c1 = 3 * t_inv * t_inv * t
            c2 = 3 * t_inv * t * t
            c3 = t * t * t
            
            interp_time = c0 * p0[0] + c1 * p1[0] + c2 * p2[0] + c3 * p3[0]
            interp_value = c0 * p0[1] + c1 * p1[1] + c2 * p2[1] + c3 * p3[1]
            
            result.append(AutomationPoint(interp_time, interp_value))
    
    return result


def s_curve_interpolation(points: List[Tuple[float, float]],
                         steepness: float = 6.0,
                         resolution: int = DEFAULT_RESOLUTION) -> List[AutomationPoint]:
    """
    Generate S-curve (sigmoid) interpolation.
    
    Creates smooth S-shaped transitions using the sigmoid function.
    Useful for natural-sounding filter sweeps and volume fades.
    
    Args:
        points: List of (time, value) control points
        steepness: Steepness of the S-curve (higher = steeper transition)
        resolution: Number of points per beat
    
    Returns:
        List of interpolated AutomationPoint objects
    
    Example:
        >>> points = [(0.0, 0.0), (4.0, 1.0)]
        >>> result = s_curve_interpolation(points, steepness=6.0)
    """
    if len(points) < 2:
        return [AutomationPoint(t, v) for t, v in points]
    
    result = []
    
    for i in range(len(points) - 1):
        start_time, start_val = points[i]
        end_time, end_val = points[i + 1]
        
        duration = end_time - start_time
        value_range = end_val - start_val
        num_steps = max(1, int(duration * resolution))
        
        for step in range(num_steps + 1):
            t = step / num_steps
            interp_time = start_time + t * duration
            
            # Sigmoid function: maps 0->1 to S-curve
            # Using scaled logistic function
            if t < 0.001:
                s_val = 0.0
            elif t > 0.999:
                s_val = 1.0
            else:
                # Logistic function centered at 0.5
                s_val = 1.0 / (1.0 + math.exp(-steepness * (t - 0.5)))
                # Normalize to 0-1 range
                min_s = 1.0 / (1.0 + math.exp(steepness * 0.5))
                max_s = 1.0 / (1.0 + math.exp(-steepness * 0.5))
                s_val = (s_val - min_s) / (max_s - min_s)
            
            interp_value = start_val + s_val * value_range
            result.append(AutomationPoint(interp_time, interp_value))
    
    return result


def exponential_interpolation(points: List[Tuple[float, float]],
                             factor: float = 2.0,
                             resolution: int = DEFAULT_RESOLUTION) -> List[AutomationPoint]:
    """
    Generate exponential curve interpolation.
    
    Creates exponential growth or decay curves.
    Useful for natural-sounding reverb/delay feedback automation.
    
    Args:
        points: List of (time, value) control points
        factor: Exponential factor (1.0 = linear, >1 = exponential growth, <1 = decay)
        resolution: Number of points per beat
    
    Returns:
        List of interpolated AutomationPoint objects
    
    Example:
        >>> points = [(0.0, 0.0), (4.0, 1.0)]
        >>> result = exponential_interpolation(points, factor=2.0)
    """
    if len(points) < 2:
        return [AutomationPoint(t, v) for t, v in points]
    
    result = []
    
    for i in range(len(points) - 1):
        start_time, start_val = points[i]
        end_time, end_val = points[i + 1]
        
        duration = end_time - start_time
        value_range = end_val - start_val
        num_steps = max(1, int(duration * resolution))
        
        for step in range(num_steps + 1):
            t = step / num_steps
            interp_time = start_time + t * duration
            
            # Exponential interpolation
            if factor == 1.0:
                exp_val = t  # Linear fallback
            else:
                exp_val = (math.pow(factor, t) - 1.0) / (factor - 1.0)
            
            interp_value = start_val + exp_val * value_range
            result.append(AutomationPoint(interp_time, interp_value))
    
    return result


def stepped_interpolation(points: List[Tuple[float, float]],
                         steps: int = 4,
                         resolution: int = DEFAULT_RESOLUTION) -> List[AutomationPoint]:
    """
    Generate stepped interpolation.
    
    Creates discrete steps between control points, similar to sample-and-hold.
    Useful for quantized effects like gating or stepped filter changes.
    
    Args:
        points: List of (time, value) control points
        steps: Number of steps between points
        resolution: Number of points per beat (used for step placement)
    
    Returns:
        List of interpolated AutomationPoint objects
    
    Example:
        >>> points = [(0.0, 0.0), (4.0, 1.0)]
        >>> result = stepped_interpolation(points, steps=8)
    """
    if len(points) < 2:
        return [AutomationPoint(t, v) for t, v in points]
    
    result = []
    
    for i in range(len(points) - 1):
        start_time, start_val = points[i]
        end_time, end_val = points[i + 1]
        
        duration = end_time - start_time
        value_range = end_val - start_val
        
        # Generate step values
        for step in range(steps + 1):
            step_ratio = step / steps
            step_time = start_time + step_ratio * duration
            step_value = start_val + step_ratio * value_range
            
            # Hold this value until next step
            next_step_ratio = (step + 1) / steps
            hold_end_time = start_time + next_step_ratio * duration
            
            # Add point at step start
            result.append(AutomationPoint(step_time, step_value))
            
            # Add point just before next step (for hold effect)
            if step < steps:
                hold_point_time = min(hold_end_time - 0.001, start_time + duration)
                if hold_point_time > step_time:
                    result.append(AutomationPoint(hold_point_time, step_value))
    
    return result


# =============================================================================
# CURVE GENERATION DISPATCHER
# =============================================================================

def generate_curve(points: List[Tuple[float, float]],
                  config: CurveConfig) -> List[AutomationPoint]:
    """
    Generate automation curve based on configuration.
    
    Dispatcher function that routes to the appropriate interpolation
    function based on curve_type in config.
    
    Args:
        points: List of (time, value) control points
        config: CurveConfig with curve parameters
    
    Returns:
        List of interpolated AutomationPoint objects
    
    Example:
        >>> config = CurveConfig(curve_type=CurveType.S_CURVE, steepness=8.0)
        >>> points = [(0.0, 0.0), (4.0, 1.0)]
        >>> result = generate_curve(points, config)
    """
    # Apply grid quantization if specified
    if config.quantize_grid is not None:
        points = quantize_points(points, config.quantize_grid)
    
    # Dispatch to appropriate interpolation function
    if config.curve_type == CurveType.LINEAR:
        return linear_interpolation(points, config.resolution)
    
    elif config.curve_type == CurveType.BEZIER:
        return bezier_interpolation(points, config.control_points, config.resolution)
    
    elif config.curve_type == CurveType.S_CURVE:
        return s_curve_interpolation(points, config.steepness, config.resolution)
    
    elif config.curve_type == CurveType.EXPONENTIAL:
        return exponential_interpolation(points, config.exponent, config.resolution)
    
    elif config.curve_type == CurveType.STEPPED:
        return stepped_interpolation(points, config.steps, config.resolution)
    
    else:
        # Default to linear
        return linear_interpolation(points, config.resolution)


# =============================================================================
# HIGH-LEVEL AUTOMATION BUILDERS
# =============================================================================

def create_filter_sweep(start_time: float,
                       duration: float,
                       start_freq: float = 200.0,
                       end_freq: float = 20000.0,
                       curve_type: CurveType = CurveType.S_CURVE,
                       steepness: float = 6.0,
                       resolution: int = DEFAULT_RESOLUTION) -> List[AutomationPoint]:
    """
    Create a filter frequency sweep automation.
    
    Args:
        start_time: Start time in beats
        duration: Duration in beats
        start_freq: Starting frequency in Hz
        end_freq: Ending frequency in Hz
        curve_type: Type of curve (S_CURVE recommended for filters)
        steepness: Steepness for S-curve
        resolution: Points per beat
    
    Returns:
        List of automation points
    """
    points = [(start_time, start_freq), (start_time + duration, end_freq)]
    config = CurveConfig(
        curve_type=curve_type,
        steepness=steepness,
        resolution=resolution
    )
    return generate_curve(points, config)


def create_volume_fade(start_time: float,
                      duration: float,
                      start_vol: float = 0.0,
                      end_vol: float = 1.0,
                      curve_type: CurveType = CurveType.EXPONENTIAL,
                      exponent: float = 2.0,
                      resolution: int = DEFAULT_RESOLUTION) -> List[AutomationPoint]:
    """
    Create a volume fade automation.
    
    Args:
        start_time: Start time in beats
        duration: Duration in beats
        start_vol: Starting volume (0.0-1.0)
        end_vol: Ending volume (0.0-1.0)
        curve_type: Type of curve (EXPONENTIAL recommended for volume)
        exponent: Exponent for exponential curve
        resolution: Points per beat
    
    Returns:
        List of automation points
    """
    points = [(start_time, start_vol), (start_time + duration, end_vol)]
    config = CurveConfig(
        curve_type=curve_type,
        exponent=exponent,
        resolution=resolution
    )
    return generate_curve(points, config)


def create_send_automation(start_time: float,
                          duration: float,
                          start_send: float = 0.0,
                          end_send: float = 0.5,
                          steps: int = 8,
                          resolution: int = DEFAULT_RESOLUTION) -> List[AutomationPoint]:
    """
    Create a send level automation with stepped interpolation.
    
    Useful for quantized reverb/delay sends on beats.
    
    Args:
        start_time: Start time in beats
        duration: Duration in beats
        start_send: Starting send amount (0.0-1.0)
        end_send: Ending send amount (0.0-1.0)
        steps: Number of discrete steps
        resolution: Points per beat
    
    Returns:
        List of automation points
    """
    points = [(start_time, start_send), (start_time + duration, end_send)]
    config = CurveConfig(
        curve_type=CurveType.STEPPED,
        steps=steps,
        resolution=resolution
    )
    return generate_curve(points, config)


# =============================================================================
# GRID QUANTIZATION HELPERS
# =============================================================================

class GridQuantization:
    """Helper class for grid quantization operations."""
    
    QUARTER = GRID_QUARTER
    EIGHTH = GRID_EIGHTH
    SIXTEENTH = GRID_SIXTEENTH
    THIRTYSECOND = GRID_THIRTYSECOND
    
    @staticmethod
    def quantize_to_grid(time: float, grid: float) -> float:
        """Quantize time to grid."""
        return quantize_time(time, grid)
    
    @staticmethod
    def quantize_points_to_grid(points: List[Tuple[float, float]], 
                                grid: float) -> List[Tuple[float, float]]:
        """Quantize multiple points to grid."""
        return quantize_points(points, grid)
    
    @staticmethod
    def get_grid_name(grid: float) -> str:
        """Get human-readable name for grid size."""
        names = {
            GRID_QUARTER: "1/4",
            GRID_EIGHTH: "1/8",
            GRID_SIXTEENTH: "1/16",
            GRID_THIRTYSECOND: "1/32"
        }
        return names.get(grid, f"{grid:.3f}")


# =============================================================================
# COMPATIBILITY EXPORTS
# =============================================================================

# Export all interpolation functions with consistent naming
__all__ = [
    # Core interpolation functions
    'linear_interpolation',
    'bezier_interpolation',
    's_curve_interpolation',
    'exponential_interpolation',
    'stepped_interpolation',
    
    # Dispatcher
    'generate_curve',
    
    # High-level builders
    'create_filter_sweep',
    'create_volume_fade',
    'create_send_automation',
    
    # Data classes
    'AutomationPoint',
    'CurveConfig',
    'CurveType',
    
    # Grid quantization
    'GridQuantization',
    'quantize_time',
    'quantize_points',
    
    # Constants
    'GRID_QUARTER',
    'GRID_EIGHTH',
    'GRID_SIXTEENTH',
    'GRID_THIRTYSECOND',
    'DEFAULT_RESOLUTION',
]
