"""
AbletonLiveBridge - Bridge between MCP server and Ableton Live API.

Provides a high-level interface for executing engine configurations
and controlling Live via the TCP connection.
"""

import sys
import os
import json
import logging
from typing import Dict, List, Any, Optional, Tuple, Union
from dataclasses import dataclass
from enum import Enum

# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("AbletonLiveBridge")


class LiveAPIError(Exception):
    """Exception raised for Live API errors."""
    pass


class DeviceNotFoundError(LiveAPIError):
    """Exception raised when a device is not found."""
    pass


class TrackNotFoundError(LiveAPIError):
    """Exception raised when a track is not found."""
    pass


@dataclass
class MixConfiguration:
    """Configuration for mix settings."""
    track_index: int
    volume: Optional[float] = None
    pan: Optional[float] = None
    mute: Optional[bool] = None
    solo: Optional[bool] = None
    sends: Optional[Dict[int, float]] = None
    devices: Optional[List[Dict[str, Any]]] = None


@dataclass
class CompressorSettings:
    """Settings for Ableton's Compressor device."""
    threshold: float = -20.0
    ratio: float = 4.0
    attack: float = 0.1
    release: float = 10.0
    make_up: float = 0.0
    use_sidechain: bool = False


@dataclass
class EQPreset:
    """EQ Eight preset configuration."""
    name: str
    high_pass: Optional[float] = None
    low_shelf: Optional[Tuple[float, float]] = None  # (freq, gain)
    mid_boost: Optional[Tuple[float, float, float]] = None  # (freq, gain, q)
    high_shelf: Optional[Tuple[float, float]] = None  # (freq, gain)


class AbletonLiveBridge:
    """
    Bridge class for executing engine configurations in Ableton Live.
    
    This class provides a high-level interface for controlling Live's
    tracks, devices, arrangement, and playback via the MCP TCP connection.
    """
    
    def __init__(self, song, mcp_connection):
        """
        Initialize the Live bridge.
        
        Args:
            song: Ableton Live song object (Live.Song.Song)
            mcp_connection: MCP TCP connection for sending commands
        """
        self.song = song
        self.mcp_connection = mcp_connection
        self.live_version = self._get_live_version()
        self._pending_tasks = []
        
        logger.info(f"AbletonLiveBridge initialized (Live version: {self.live_version})")
    
    def _get_live_version(self) -> str:
        """Get Ableton Live version for compatibility checks."""
        try:
            app = self.song.application()
            return app.get_major_version() if hasattr(app, 'get_major_version') else "unknown"
        except:
            return "unknown"
    
    def _check_api_version(self, min_version: str = "11") -> bool:
        """Check if Live API version meets minimum requirements."""
        try:
            if self.live_version == "unknown":
                return True  # Assume compatible if version unknown
            return int(self.live_version) >= int(min_version)
        except:
            return False
    
    def _send_tcp_command(self, command: Dict[str, Any]) -> Dict[str, Any]:
        """
        Send a command via TCP connection.
        
        Args:
            command: Dictionary with command data
            
        Returns:
            Response dictionary with status and result
        """
        try:
            if self.mcp_connection:
                # Send command through MCP connection
                self.mcp_connection.send(json.dumps(command).encode())
                response = self.mcp_connection.recv(4096).decode()
                return json.loads(response)
            else:
                return {"status": "error", "message": "No MCP connection available"}
        except Exception as e:
            logger.error(f"TCP command failed: {e}")
            return {"status": "error", "message": str(e)}
    
    def _create_result(self, success: bool, message: str = "", data: Any = None) -> Dict[str, Any]:
        """Create a standardized result dictionary."""
        result = {
            "success": success,
            "message": message,
            "data": data
        }
        if not success:
            logger.warning(f"Operation failed: {message}")
        return result
    
    # =========================================================================
    # Bus and Return Management
    # =========================================================================
    
    def create_bus_track(self, name: str, bus_type: str = "Group") -> Dict[str, Any]:
        """
        Create a group/bus track for mixing.
        
        Args:
            name: Name for the bus track
            bus_type: Type of bus ("Group", "Master", etc.)
            
        Returns:
            Result dictionary with track index if successful
        """
        try:
            # Create group track via Live API
            tracks = list(self.song.tracks)
            
            # Create audio track first, then convert to group
            self.song.create_audio_track(-1)
            new_track = self.song.tracks[-1]
            
            # Convert to group track if possible
            if hasattr(new_track, 'is_grouped'):
                # Set as group track
                new_track.name = name
                track_index = len(self.song.tracks) - 1
                
                return self._create_result(
                    True, 
                    f"Bus track '{name}' created at index {track_index}",
                    {"track_index": track_index, "name": name, "type": bus_type}
                )
            else:
                # Fallback: just use as regular track
                new_track.name = name
                track_index = len(self.song.tracks) - 1
                
                return self._create_result(
                    True,
                    f"Track '{name}' created at index {track_index} (group features may be limited)",
                    {"track_index": track_index, "name": name}
                )
                
        except Exception as e:
            return self._create_result(False, f"Failed to create bus track: {str(e)}")
    
    def create_return_track(self, name: str, effect_type: str = "Reverb") -> Dict[str, Any]:
        """
        Create a return track with an effect.
        
        Args:
            name: Name for the return track
            effect_type: Type of effect ("Reverb", "Delay", etc.)
            
        Returns:
            Result dictionary with return track index if successful
        """
        try:
            # Create return track
            if hasattr(self.song, 'create_return_track'):
                self.song.create_return_track()
                return_track = self.song.return_tracks[-1]
                return_track.name = name
                return_index = len(self.song.return_tracks) - 1
                
                # Add effect device if possible
                if effect_type and hasattr(return_track, 'devices'):
                    # Effect will be added by insert_device later
                    pass
                
                return self._create_result(
                    True,
                    f"Return track '{name}' created at index {return_index}",
                    {"return_index": return_index, "name": name, "effect_type": effect_type}
                )
            else:
                return self._create_result(False, "Live version doesn't support return tracks")
                
        except Exception as e:
            return self._create_result(False, f"Failed to create return track: {str(e)}")
    
    def route_track_to_bus(self, track_index: int, bus_name: str) -> Dict[str, Any]:
        """
        Route a track's output to a bus/group track.
        
        Args:
            track_index: Index of the source track
            bus_name: Name of the target bus track
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            if track_index < 0 or track_index >= len(self.song.tracks):
                raise TrackNotFoundError(f"Track index {track_index} out of range")
            
            source_track = self.song.tracks[track_index]
            
            # Find bus track by name
            bus_track = None
            bus_index = -1
            for i, track in enumerate(self.song.tracks):
                if track.name == bus_name:
                    bus_track = track
                    bus_index = i
                    break
            
            if bus_track is None:
                return self._create_result(False, f"Bus track '{bus_name}' not found")
            
            # Set output routing
            if hasattr(source_track, 'output_meter_level'):
                # Try to set output to the bus
                # Note: Exact API may vary by Live version
                if hasattr(source_track, 'output_routing_type'):
                    # Set routing
                    source_track.output_routing_type = bus_track
                elif hasattr(source_track, 'group_track'):
                    source_track.group_track = bus_track
                else:
                    # Manual grouping via Live's internal API
                    pass
            
            return self._create_result(
                True,
                f"Track {track_index} routed to bus '{bus_name}' (index {bus_index})"
            )
            
        except TrackNotFoundError as e:
            return self._create_result(False, str(e))
        except Exception as e:
            return self._create_result(False, f"Failed to route track: {str(e)}")
    
    def set_track_send(self, track_index: int, return_index: int, amount: float) -> Dict[str, Any]:
        """
        Configure send amount from a track to a return track.
        
        Args:
            track_index: Index of the source track
            return_index: Index of the return track
            amount: Send amount (0.0 - 1.0)
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            if track_index < 0 or track_index >= len(self.song.tracks):
                raise TrackNotFoundError(f"Track index {track_index} out of range")
            
            track = self.song.tracks[track_index]
            
            # Clamp amount to valid range
            amount = max(0.0, min(1.0, amount))
            
            # Set send value
            if hasattr(track, 'mixer_device') and hasattr(track.mixer_device, 'sends'):
                sends = track.mixer_device.sends
                if return_index < len(sends):
                    sends[return_index].value = amount
                    return self._create_result(
                        True,
                        f"Send {return_index} on track {track_index} set to {amount:.2f}"
                    )
                else:
                    return self._create_result(False, f"Return index {return_index} out of range")
            else:
                return self._create_result(False, "Track doesn't support sends")
                
        except TrackNotFoundError as e:
            return self._create_result(False, str(e))
        except Exception as e:
            return self._create_result(False, f"Failed to set send: {str(e)}")
    
    # =========================================================================
    # Device Management
    # =========================================================================
    
    def insert_device(self, track_index: int, device_name: str) -> Dict[str, Any]:
        """
        Insert a device/instrument on a track.
        
        Args:
            track_index: Index of the target track
            device_name: Name of the device to insert
            
        Returns:
            Result dictionary with device index if successful
        """
        try:
            if track_index < 0 or track_index >= len(self.song.tracks):
                raise TrackNotFoundError(f"Track index {track_index} out of range")
            
            track = self.song.tracks[track_index]
            
            # Map common device names to Live device types
            device_map = {
                "eq eight": "EQ Eight",
                "eq8": "EQ Eight",
                "compressor": "Compressor",
                "reverb": "Reverb",
                "delay": "Delay",
                "saturator": "Saturator",
                "limiter": "Limiter",
                "utility": "Utility",
                "filter": "Auto Filter",
                "autofilter": "Auto Filter"
            }
            
            canonical_name = device_map.get(device_name.lower(), device_name)
            
            # Try to load device from browser
            if hasattr(self.song, 'browser'):
                browser = self.song.browser
                # Search for device
                device_to_load = None
                
                # Look in audio effects
                if hasattr(browser, 'audio_effects'):
                    for device in browser.audio_effects:
                        if canonical_name.lower() in device.name.lower():
                            device_to_load = device
                            break
                
                # Look in instruments
                if device_to_load is None and hasattr(browser, 'instruments'):
                    for device in browser.instruments:
                        if canonical_name.lower() in device.name.lower():
                            device_to_load = device
                            break
                
                # Load the device
                if device_to_load and hasattr(track, 'devices'):
                    # Add to end of device chain
                    track.load_device(device_to_load)
                    device_index = len(track.devices) - 1
                    
                    return self._create_result(
                        True,
                        f"Device '{canonical_name}' inserted on track {track_index} at position {device_index}",
                        {"track_index": track_index, "device_index": device_index, "device_name": canonical_name}
                    )
                else:
                    return self._create_result(False, f"Device '{device_name}' not found in browser")
            else:
                return self._create_result(False, "Browser not available")
                
        except TrackNotFoundError as e:
            return self._create_result(False, str(e))
        except Exception as e:
            return self._create_result(False, f"Failed to insert device: {str(e)}")
    
    def configure_device(self, track_index: int, device_name: str, 
                        params: Dict[str, Any]) -> Dict[str, Any]:
        """
        Configure parameters of a device on a track.
        
        Args:
            track_index: Index of the target track
            device_name: Name of the device to configure
            params: Dictionary of parameter names and values
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            if track_index < 0 or track_index >= len(self.song.tracks):
                raise TrackNotFoundError(f"Track index {track_index} out of range")
            
            track = self.song.tracks[track_index]
            
            # Find device by name
            target_device = None
            if hasattr(track, 'devices'):
                for device in track.devices:
                    if device_name.lower() in device.name.lower():
                        target_device = device
                        break
            
            if target_device is None:
                raise DeviceNotFoundError(f"Device '{device_name}' not found on track {track_index}")
            
            # Configure parameters
            configured = []
            failed = []
            
            if hasattr(target_device, 'parameters'):
                for param_name, param_value in params.items():
                    param_found = False
                    for param in target_device.parameters:
                        if param_name.lower() in param.name.lower():
                            try:
                                # Clamp value to parameter's min/max
                                min_val = param.min if hasattr(param, 'min') else 0
                                max_val = param.max if hasattr(param, 'max') else 1
                                clamped_value = max(min_val, min(max_val, param_value))
                                param.value = clamped_value
                                configured.append(f"{param.name} = {clamped_value}")
                                param_found = True
                                break
                            except Exception as pe:
                                failed.append(f"{param_name}: {str(pe)}")
                    
                    if not param_found:
                        failed.append(f"{param_name}: parameter not found")
            
            return self._create_result(
                len(failed) == 0,
                f"Configured {len(configured)} parameters on '{device_name}'",
                {"configured": configured, "failed": failed}
            )
            
        except TrackNotFoundError as e:
            return self._create_result(False, str(e))
        except DeviceNotFoundError as e:
            return self._create_result(False, str(e))
        except Exception as e:
            return self._create_result(False, f"Failed to configure device: {str(e)}")
    
    def remove_device(self, track_index: int, device_name: str) -> Dict[str, Any]:
        """
        Remove a device from a track.
        
        Args:
            track_index: Index of the target track
            device_name: Name of the device to remove
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            if track_index < 0 or track_index >= len(self.song.tracks):
                raise TrackNotFoundError(f"Track index {track_index} out of range")
            
            track = self.song.tracks[track_index]
            
            # Find and delete device
            if hasattr(track, 'devices'):
                for i, device in enumerate(track.devices):
                    if device_name.lower() in device.name.lower():
                        # Delete the device
                        track.delete_device(i)
                        return self._create_result(
                            True,
                            f"Device '{device_name}' removed from track {track_index}"
                        )
            
            return self._create_result(False, f"Device '{device_name}' not found on track {track_index}")
            
        except TrackNotFoundError as e:
            return self._create_result(False, str(e))
        except Exception as e:
            return self._create_result(False, f"Failed to remove device: {str(e)}")
    
    # =========================================================================
    # Mix Configuration Execution
    # =========================================================================
    
    def execute_mix_config(self, config: MixConfiguration) -> Dict[str, Any]:
        """
        Apply a complete mix configuration to a track.
        
        Args:
            config: MixConfiguration object with settings
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            results = []
            
            # Apply volume
            if config.volume is not None:
                result = self.set_track_volume(config.track_index, config.volume)
                results.append("volume" if result["success"] else f"volume: {result['message']}")
            
            # Apply pan
            if config.pan is not None:
                result = self.set_track_pan(config.track_index, config.pan)
                results.append("pan" if result["success"] else f"pan: {result['message']}")
            
            # Apply mute
            if config.mute is not None:
                result = self._set_track_mute_internal(config.track_index, config.mute)
                results.append("mute" if result["success"] else f"mute: {result['message']}")
            
            # Apply solo
            if config.solo is not None:
                result = self._set_track_solo_internal(config.track_index, config.solo)
                results.append("solo" if result["success"] else f"solo: {result['message']}")
            
            # Apply sends
            if config.sends:
                for return_index, amount in config.sends.items():
                    result = self.set_track_send(config.track_index, return_index, amount)
                    results.append(f"send_{return_index}" if result["success"] else f"send_{return_index}: {result['message']}")
            
            # Apply devices
            if config.devices:
                for device_config in config.devices:
                    device_name = device_config.get("name", "")
                    device_params = device_config.get("params", {})
                    
                    # Insert device
                    insert_result = self.insert_device(config.track_index, device_name)
                    if insert_result["success"]:
                        # Configure device
                        configure_result = self.configure_device(
                            config.track_index, device_name, device_params
                        )
                        results.append(f"device_{device_name}" if configure_result["success"] 
                                     else f"device_{device_name}: {configure_result['message']}")
                    else:
                        results.append(f"device_{device_name}: {insert_result['message']}")
            
            return self._create_result(
                True,
                f"Mix config applied to track {config.track_index}",
                {"applied": results}
            )
            
        except Exception as e:
            return self._create_result(False, f"Failed to execute mix config: {str(e)}")
    
    def apply_eq_preset(self, track_index: int, preset_name: str) -> Dict[str, Any]:
        """
        Apply an EQ Eight preset to a track.
        
        Args:
            track_index: Index of the target track
            preset_name: Name of the EQ preset to apply
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            # Define preset configurations
            presets = {
                "low_cut": {"hpf": 80, "ls_gain": 0},
                "vocal_boost": {"hpf": 100, "mid_freq": 2500, "mid_gain": 3, "mid_q": 0.7},
                "bass_enhance": {"ls_freq": 120, "ls_gain": 4, "hs_gain": -2},
                "bright": {"hs_freq": 8000, "hs_gain": 3},
                "scooped": {"ls_gain": -2, "mid_freq": 1000, "mid_gain": -3, "hs_gain": 2}
            }
            
            preset = presets.get(preset_name.lower(), {})
            
            # Insert EQ Eight
            insert_result = self.insert_device(track_index, "EQ Eight")
            if not insert_result["success"]:
                return insert_result
            
            # Configure EQ parameters
            eq_params = {}
            
            if "hpf" in preset:
                eq_params["highpass"] = preset["hpf"]
            if "ls_freq" in preset:
                eq_params["lowshelf freq"] = preset["ls_freq"]
            if "ls_gain" in preset:
                eq_params["lowshelf gain"] = preset["ls_gain"]
            if "mid_freq" in preset:
                eq_params["mid freq"] = preset["mid_freq"]
            if "mid_gain" in preset:
                eq_params["mid gain"] = preset["mid_gain"]
            if "hs_freq" in preset:
                eq_params["highshelf freq"] = preset["hs_freq"]
            if "hs_gain" in preset:
                eq_params["highshelf gain"] = preset["hs_gain"]
            
            config_result = self.configure_device(track_index, "EQ Eight", eq_params)
            
            return self._create_result(
                config_result["success"],
                f"EQ preset '{preset_name}' applied to track {track_index}",
                config_result.get("data")
            )
            
        except Exception as e:
            return self._create_result(False, f"Failed to apply EQ preset: {str(e)}")
    
    def apply_compression(self, track_index: int, settings: CompressorSettings) -> Dict[str, Any]:
        """
        Apply compressor settings to a track.
        
        Args:
            track_index: Index of the target track
            settings: CompressorSettings object
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            # Insert Compressor
            insert_result = self.insert_device(track_index, "Compressor")
            if not insert_result["success"]:
                return insert_result
            
            # Configure compressor parameters
            comp_params = {
                "threshold": settings.threshold,
                "ratio": settings.ratio,
                "attack": settings.attack,
                "release": settings.release,
                "makeup": settings.make_up
            }
            
            config_result = self.configure_device(track_index, "Compressor", comp_params)
            
            return self._create_result(
                config_result["success"],
                f"Compression applied to track {track_index}",
                {"settings": settings.__dict__}
            )
            
        except Exception as e:
            return self._create_result(False, f"Failed to apply compression: {str(e)}")
    
    def setup_sidechain(self, source_track: int, target_track: int, 
                       amount: float = 0.5) -> Dict[str, Any]:
        """
        Setup sidechain compression from source to target track.
        
        Args:
            source_track: Index of the trigger/source track (e.g., kick)
            target_track: Index of the track to duck (e.g., bass)
            amount: Sidechain amount (0.0 - 1.0)
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            # Validate track indices
            if source_track < 0 or source_track >= len(self.song.tracks):
                raise TrackNotFoundError(f"Source track index {source_track} out of range")
            if target_track < 0 or target_track >= len(self.song.tracks):
                raise TrackNotFoundError(f"Target track index {target_track} out of range")
            
            # Insert compressor on target track if not present
            target = self.song.tracks[target_track]
            has_compressor = False
            compressor_device = None
            
            if hasattr(target, 'devices'):
                for device in target.devices:
                    if "compressor" in device.name.lower():
                        has_compressor = True
                        compressor_device = device
                        break
            
            if not has_compressor:
                insert_result = self.insert_device(target_track, "Compressor")
                if not insert_result["success"]:
                    return insert_result
                # Get the newly inserted compressor
                if hasattr(target, 'devices'):
                    compressor_device = target.devices[-1]
            
            # Configure sidechain routing
            if compressor_device and hasattr(compressor_device, 'parameters'):
                for param in compressor_device.parameters:
                    if "sidechain" in param.name.lower():
                        # Enable sidechain
                        param.value = 1  # or appropriate value for on
                    elif "sidechain source" in param.name.lower() or "input" in param.name.lower():
                        # Set sidechain input to source track
                        # This is Live-version dependent
                        pass
            
            # Set threshold and ratio for ducking effect
            sidechain_params = {
                "threshold": -20.0,
                "ratio": 4.0,
                "attack": 0.01,
                "release": 0.1
            }
            
            config_result = self.configure_device(target_track, "Compressor", sidechain_params)
            
            return self._create_result(
                True,
                f"Sidechain setup from track {source_track} to track {target_track} (amount: {amount})"
            )
            
        except TrackNotFoundError as e:
            return self._create_result(False, str(e))
        except Exception as e:
            return self._create_result(False, f"Failed to setup sidechain: {str(e)}")
    
    # =========================================================================
    # Arrangement Operations
    # =========================================================================
    
    def insert_arrangement_clip(self, track_index: int, file_path: str, 
                                 start_bar: float, duration: float) -> Dict[str, Any]:
        """
        Insert an audio clip into the arrangement.
        
        Args:
            track_index: Index of the target audio track
            file_path: Path to the audio file
            start_bar: Start position in bars
            duration: Duration in bars
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            if track_index < 0 or track_index >= len(self.song.tracks):
                raise TrackNotFoundError(f"Track index {track_index} out of range")
            
            track = self.song.tracks[track_index]
            
            # Verify file exists
            if not os.path.exists(file_path):
                return self._create_result(False, f"Audio file not found: {file_path}")
            
            # Create clip at position
            if hasattr(track, 'insert_clip'):
                clip = track.insert_clip(file_path, start_bar, duration)
                return self._create_result(
                    True,
                    f"Audio clip inserted at bar {start_bar} on track {track_index}",
                    {"clip": clip.name if hasattr(clip, 'name') else "unnamed"}
                )
            else:
                # Alternative: use view or clip slots
                if hasattr(self.song, 'view') and hasattr(self.song.view, 'detail_clip'):
                    # Method depends on Live version
                    pass
                
                return self._create_result(
                    False,
                    "Arrangement clip insertion not available in this Live version"
                )
            
        except TrackNotFoundError as e:
            return self._create_result(False, str(e))
        except Exception as e:
            return self._create_result(False, f"Failed to insert arrangement clip: {str(e)}")
    
    def insert_arrangement_midi(self, track_index: int, start_bar: float, 
                                 duration: float, notes: List[Dict[str, Any]]) -> Dict[str, Any]:
        """
        Insert a MIDI clip with notes into the arrangement.
        
        Args:
            track_index: Index of the target MIDI track
            start_bar: Start position in bars
            duration: Duration in bars
            notes: List of note dictionaries with pitch, start_time, duration, velocity
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            if track_index < 0 or track_index >= len(self.song.tracks):
                raise TrackNotFoundError(f"Track index {track_index} out of range")
            
            track = self.song.tracks[track_index]
            
            # Create MIDI clip
            if hasattr(track, 'create_clip'):
                clip = track.create_clip(start_bar, duration)
                
                # Add notes
                if hasattr(clip, 'notes') and hasattr(clip.notes, 'add'):
                    for note in notes:
                        clip.notes.add(
                            note["pitch"],
                            note.get("start_time", 0),
                            note.get("duration", 0.25),
                            note.get("velocity", 100),
                            False  # not muted
                        )
                
                return self._create_result(
                    True,
                    f"MIDI clip inserted at bar {start_bar} on track {track_index} with {len(notes)} notes"
                )
            else:
                return self._create_result(
                    False,
                    "MIDI clip creation not available in this Live version"
                )
            
        except TrackNotFoundError as e:
            return self._create_result(False, str(e))
        except Exception as e:
            return self._create_result(False, f"Failed to insert MIDI clip: {str(e)}")
    
    def add_automation(self, track_index: int, clip_index: int, 
                       parameter: str, points: List[Tuple[float, float]]) -> Dict[str, Any]:
        """
        Add automation envelope points to a clip.
        
        Args:
            track_index: Index of the target track
            clip_index: Index of the clip
            parameter: Name of the parameter to automate
            points: List of (time, value) tuples
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            if track_index < 0 or track_index >= len(self.song.tracks):
                raise TrackNotFoundError(f"Track index {track_index} out of range")
            
            track = self.song.tracks[track_index]
            
            if not hasattr(track, 'clips') or clip_index >= len(track.clips):
                return self._create_result(False, f"Clip index {clip_index} out of range")
            
            clip = track.clips[clip_index]
            
            # Find parameter to automate
            target_param = None
            if hasattr(clip, 'parameters'):
                for param in clip.parameters:
                    if parameter.lower() in param.name.lower():
                        target_param = param
                        break
            
            if target_param is None:
                return self._create_result(False, f"Parameter '{parameter}' not found")
            
            # Add automation points
            if hasattr(target_param, 'automation'):
                automation = target_param.automation
                for time, value in points:
                    automation.insert_step(time, value, 0)  # 0 = linear interpolation
                
                return self._create_result(
                    True,
                    f"Added {len(points)} automation points to '{parameter}' in clip {clip_index}"
                )
            else:
                return self._create_result(False, "Automation not available for this parameter")
            
        except TrackNotFoundError as e:
            return self._create_result(False, str(e))
        except Exception as e:
            return self._create_result(False, f"Failed to add automation: {str(e)}")
    
    # =========================================================================
    # Track Management
    # =========================================================================
    
    def create_midi_track(self, index: int = -1) -> Dict[str, Any]:
        """
        Create a new MIDI track.
        
        Args:
            index: Position to insert track (-1 for end)
            
        Returns:
            Result dictionary with track index if successful
        """
        try:
            self.song.create_midi_track(index)
            track_index = index if index >= 0 else len(self.song.tracks) - 1
            
            return self._create_result(
                True,
                f"MIDI track created at index {track_index}",
                {"track_index": track_index, "type": "midi"}
            )
            
        except Exception as e:
            return self._create_result(False, f"Failed to create MIDI track: {str(e)}")
    
    def create_audio_track(self, index: int = -1) -> Dict[str, Any]:
        """
        Create a new audio track.
        
        Args:
            index: Position to insert track (-1 for end)
            
        Returns:
            Result dictionary with track index if successful
        """
        try:
            self.song.create_audio_track(index)
            track_index = index if index >= 0 else len(self.song.tracks) - 1
            
            return self._create_result(
                True,
                f"Audio track created at index {track_index}",
                {"track_index": track_index, "type": "audio"}
            )
            
        except Exception as e:
            return self._create_result(False, f"Failed to create audio track: {str(e)}")
    
    def set_track_name(self, track_index: int, name: str) -> Dict[str, Any]:
        """
        Set the name of a track.
        
        Args:
            track_index: Index of the track
            name: New name for the track
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            if track_index < 0 or track_index >= len(self.song.tracks):
                raise TrackNotFoundError(f"Track index {track_index} out of range")
            
            track = self.song.tracks[track_index]
            old_name = track.name if hasattr(track, 'name') else "unnamed"
            track.name = name
            
            return self._create_result(
                True,
                f"Track {track_index} renamed from '{old_name}' to '{name}'"
            )
            
        except TrackNotFoundError as e:
            return self._create_result(False, str(e))
        except Exception as e:
            return self._create_result(False, f"Failed to set track name: {str(e)}")
    
    def set_track_volume(self, track_index: int, volume: float) -> Dict[str, Any]:
        """
        Set the volume of a track.
        
        Args:
            track_index: Index of the track
            volume: Volume level (0.0 - 1.0, or dB scale)
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            if track_index < 0 or track_index >= len(self.song.tracks):
                raise TrackNotFoundError(f"Track index {track_index} out of range")
            
            track = self.song.tracks[track_index]
            
            if hasattr(track, 'mixer_device') and hasattr(track.mixer_device, 'volume'):
                # Clamp to valid range (0.0 to 1.0 for Live's internal scale)
                clamped_volume = max(0.0, min(1.0, volume))
                track.mixer_device.volume.value = clamped_volume
                
                return self._create_result(
                    True,
                    f"Track {track_index} volume set to {clamped_volume:.2f}"
                )
            else:
                return self._create_result(False, "Track doesn't have volume control")
            
        except TrackNotFoundError as e:
            return self._create_result(False, str(e))
        except Exception as e:
            return self._create_result(False, f"Failed to set track volume: {str(e)}")
    
    def set_track_pan(self, track_index: int, pan: float) -> Dict[str, Any]:
        """
        Set the pan of a track.
        
        Args:
            track_index: Index of the track
            pan: Pan position (-1.0 left to 1.0 right, 0.0 center)
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            if track_index < 0 or track_index >= len(self.song.tracks):
                raise TrackNotFoundError(f"Track index {track_index} out of range")
            
            track = self.song.tracks[track_index]
            
            if hasattr(track, 'mixer_device') and hasattr(track.mixer_device, 'panning'):
                # Clamp to valid range (-1.0 to 1.0)
                clamped_pan = max(-1.0, min(1.0, pan))
                track.mixer_device.panning.value = clamped_pan
                
                return self._create_result(
                    True,
                    f"Track {track_index} pan set to {clamped_pan:.2f}"
                )
            else:
                return self._create_result(False, "Track doesn't have pan control")
            
        except TrackNotFoundError as e:
            return self._create_result(False, str(e))
        except Exception as e:
            return self._create_result(False, f"Failed to set track pan: {str(e)}")
    
    def _set_track_mute_internal(self, track_index: int, mute: bool) -> Dict[str, Any]:
        """Internal method to set track mute state."""
        try:
            track = self.song.tracks[track_index]
            if hasattr(track, 'mute'):
                track.mute = mute
                return self._create_result(True, f"Track {track_index} mute set to {mute}")
            else:
                return self._create_result(False, "Track doesn't support mute")
        except Exception as e:
            return self._create_result(False, str(e))
    
    def _set_track_solo_internal(self, track_index: int, solo: bool) -> Dict[str, Any]:
        """Internal method to set track solo state."""
        try:
            track = self.song.tracks[track_index]
            if hasattr(track, 'solo'):
                track.solo = solo
                return self._create_result(True, f"Track {track_index} solo set to {solo}")
            else:
                return self._create_result(False, "Track doesn't support solo")
        except Exception as e:
            return self._create_result(False, str(e))
    
    # =========================================================================
    # Playback Control
    # =========================================================================
    
    def start_playback(self) -> Dict[str, Any]:
        """
        Start playback.
        
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            if hasattr(self.song, 'start_playing'):
                self.song.start_playing()
                return self._create_result(True, "Playback started")
            elif hasattr(self.song, 'is_playing'):
                # Alternative method
                self.song.is_playing = True
                return self._create_result(True, "Playback started")
            else:
                return self._create_result(False, "Playback control not available")
                
        except Exception as e:
            return self._create_result(False, f"Failed to start playback: {str(e)}")
    
    def stop_playback(self) -> Dict[str, Any]:
        """
        Stop playback.
        
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            if hasattr(self.song, 'stop_playing'):
                self.song.stop_playing()
                return self._create_result(True, "Playback stopped")
            elif hasattr(self.song, 'is_playing'):
                self.song.is_playing = False
                return self._create_result(True, "Playback stopped")
            else:
                return self._create_result(False, "Playback control not available")
                
        except Exception as e:
            return self._create_result(False, f"Failed to stop playback: {str(e)}")
    
    def set_tempo(self, bpm: float) -> Dict[str, Any]:
        """
        Set the project tempo.
        
        Args:
            bpm: Tempo in beats per minute
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            if hasattr(self.song, 'tempo'):
                # Clamp to reasonable range
                clamped_bpm = max(20.0, min(999.0, bpm))
                self.song.tempo = clamped_bpm
                return self._create_result(True, f"Tempo set to {clamped_bpm:.1f} BPM")
            else:
                return self._create_result(False, "Tempo control not available")
                
        except Exception as e:
            return self._create_result(False, f"Failed to set tempo: {str(e)}")
    
    def set_playhead(self, bar: float) -> Dict[str, Any]:
        """
        Set the playhead position.
        
        Args:
            bar: Position in bars (can include fractional bars)
            
        Returns:
            Result dictionary indicating success/failure
        """
        try:
            if hasattr(self.song, 'current_song_time'):
                # Convert bars to seconds based on tempo
                beats_per_bar = self.song.signature_numerator if hasattr(self.song, 'signature_numerator') else 4
                seconds_per_beat = 60.0 / self.song.tempo
                seconds = bar * beats_per_bar * seconds_per_beat
                
                self.song.current_song_time = seconds
                return self._create_result(True, f"Playhead set to bar {bar}")
            else:
                return self._create_result(False, "Playhead control not available")
                
        except Exception as e:
            return self._create_result(False, f"Failed to set playhead: {str(e)}")
