"""
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
        """
        logger.info(f"[BUS] Creating bus track: name='{name}', type='{bus_type}'")
        
        try:
            # Get track count before creation
            tracks_before = len(self.song.tracks)
            logger.debug(f"[BUS] Tracks before creation: {tracks_before}")
            
            # Create audio track (in Live, group tracks are created differently)
            self.song.create_audio_track(-1)
            
            # Get track count after creation
            tracks_after = len(self.song.tracks)
            logger.debug(f"[BUS] Tracks after creation: {tracks_after}")
            
            # Verify track was actually created
            if tracks_after <= tracks_before:
                logger.error(f"[BUS] Track creation failed: no new track added")
                return self._create_result(False, "Failed to create track - no new track added")
            
            new_track = self.song.tracks[-1]
            track_index = len(self.song.tracks) - 1
            
            # Set the name
            old_name = getattr(new_track, 'name', 'unnamed')
            new_track.name = name
            
            # Verify name was set
            actual_name = getattr(new_track, 'name', None)
            if actual_name != name:
                logger.warning(f"[BUS] Name verification failed: expected '{name}', got '{actual_name}'")
            else:
                logger.info(f"[BUS] Name set successfully: '{actual_name}'")
            
            # Check if this can be a group track
            is_group_capable = hasattr(new_track, 'is_grouped') or hasattr(new_track, 'group_track')
            logger.debug(f"[BUS] Group track capabilities: is_grouped={hasattr(new_track, 'is_grouped')}, group_track={hasattr(new_track, 'group_track')}")
            
            result_data = {
                "track_index": track_index,
                "name": actual_name,
                "type": bus_type,
                "tracks_before": tracks_before,
                "tracks_after": tracks_after,
                "is_group_capable": is_group_capable
            }
            
            logger.info(f"[BUS] Bus track '{name}' created successfully at index {track_index}")
            
            return self._create_result(
                True,
                f"Bus track '{name}' created at index {track_index}",
                result_data
            )
                
        except Exception as e:
            logger.error(f"[BUS] Failed to create bus track: {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
        """
        logger.info(f"[ROUTE] Routing track {track_index} to bus '{bus_name}'")
        
        try:
            if track_index < 0 or track_index >= len(self.song.tracks):
                raise TrackNotFoundError(f"Track index {track_index} out of range (0-{len(self.song.tracks)-1})")
            
            source_track = self.song.tracks[track_index]
            source_name = getattr(source_track, 'name', f'track_{track_index}')
            logger.debug(f"[ROUTE] Source track: index={track_index}, name='{source_name}'")
            
            # Find bus track by name
            bus_track = None
            bus_index = -1
            for i, track in enumerate(self.song.tracks):
                track_name = getattr(track, 'name', '')
                if bus_name.lower() in track_name.lower():
                    bus_track = track
                    bus_index = i
                    logger.debug(f"[ROUTE] Found bus track at index {i}: '{track_name}'")
                    break
            
            if bus_track is None:
                logger.error(f"[ROUTE] Bus track '{bus_name}' not found")
                return self._create_result(False, f"Bus track '{bus_name}' not found")
            
            # Log available routing attributes
            routing_attrs = ['output_routing_type', 'output_routing_channel', 'output_routing', 'group_track', 'is_grouped']
            available_attrs = [attr for attr in routing_attrs if hasattr(source_track, attr)]
            logger.debug(f"[ROUTE] Available routing attributes on source: {available_attrs}")
            
            # Try various routing methods
            routing_applied = False
            routing_method = None
            
            # Method 1: output_routing_type
            if hasattr(source_track, 'output_routing_type'):
                try:
                    old_routing = source_track.output_routing_type
                    source_track.output_routing_type = bus_track
                    # Verify change
                    new_routing = source_track.output_routing_type
                    if new_routing == bus_track:
                        routing_applied = True
                        routing_method = 'output_routing_type'
                        logger.info(f"[ROUTE] Applied via output_routing_type")
                except Exception as e:
                    logger.debug(f"[ROUTE] output_routing_type failed: {e}")
            
            # Method 2: group_track
            if not routing_applied and hasattr(source_track, 'group_track'):
                try:
                    source_track.group_track = bus_track
                    routing_applied = True
                    routing_method = 'group_track'
                    logger.info(f"[ROUTE] Applied via group_track")
                except Exception as e:
                    logger.debug(f"[ROUTE] group_track failed: {e}")
            
            # Method 3: output_routing
            if not routing_applied and hasattr(source_track, 'output_routing'):
                try:
                    source_track.output_routing = bus_track
                    routing_applied = True
                    routing_method = 'output_routing'
                    logger.info(f"[ROUTE] Applied via output_routing")
                except Exception as e:
                    logger.debug(f"[ROUTE] output_routing failed: {e}")
            
            # Log mixer device capabilities for sends
            mixer = getattr(source_track, 'mixer_device', None)
            if mixer:
                sends = getattr(mixer, 'sends', None)
                logger.debug(f"[ROUTE] Mixer sends available: {sends is not None}")
                if sends:
                    logger.debug(f"[ROUTE] Number of sends: {len(sends)}")
            
            result_data = {
                "track_index": track_index,
                "track_name": source_name,
                "bus_index": bus_index,
                "bus_name": bus_name,
                "routing_applied": routing_applied,
                "routing_method": routing_method,
                "available_attrs": available_attrs
            }
            
            if routing_applied:
                logger.info(f"[ROUTE] Successfully routed track {track_index} to bus '{bus_name}' via {routing_method}")
                return self._create_result(
                    True,
                    f"Track {track_index} ('{source_name}') routed to bus '{bus_name}' via {routing_method}",
                    result_data
                )
            else:
                logger.warning(f"[ROUTE] Could not apply automatic routing - manual grouping may be needed")
                return self._create_result(
                    False,
                    f"Could not apply automatic routing for track {track_index} to '{bus_name}' - manual grouping may be needed",
                    result_data
                )
            
        except TrackNotFoundError as e:
            logger.error(f"[ROUTE] Track not found: {e}")
            return self._create_result(False, str(e))
        except Exception as e:
            logger.error(f"[ROUTE] Failed to route track: {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
        """
        logger.info(f"[SEND] Setting send on track {track_index} -> return {return_index} to {amount:.3f}")
        
        try:
            # Validate track index
            if track_index < 0 or track_index >= len(self.song.tracks):
                raise TrackNotFoundError(f"Track index {track_index} out of range (0-{len(self.song.tracks)-1})")
            
            track = self.song.tracks[track_index]
            track_name = getattr(track, 'name', f'track_{track_index}')
            logger.debug(f"[SEND] Target track: index={track_index}, name='{track_name}'")
            
            # Clamp amount to valid range
            amount = max(0.0, min(1.0, amount))
            
            # Check mixer device
            mixer = getattr(track, 'mixer_device', None)
            if mixer is None:
                logger.error(f"[SEND] Track {track_index} has no mixer_device")
                return self._create_result(False, "Track has no mixer device")
            
            logger.debug(f"[SEND] Mixer device available")
            
            # Check sends
            sends = getattr(mixer, 'sends', None)
            if sends is None:
                logger.error(f"[SEND] Track {track_index} mixer has no sends")
                return self._create_result(False, "Track mixer has no sends")
            
            num_sends = len(sends)
            logger.debug(f"[SEND] Number of sends available: {num_sends}")
            
            # Validate return index
            if return_index < 0 or return_index >= num_sends:
                logger.error(f"[SEND] Return index {return_index} out of range (0-{num_sends-1})")
                return self._create_result(False, f"Return index {return_index} out of range (0-{num_sends-1})")
            
            # Get the send
            send = sends[return_index]
            send_value_attr = getattr(send, 'value', None)
            
            if send_value_attr is None:
                logger.error(f"[SEND] Send {return_index} has no value attribute")
                return self._create_result(False, "Send has no value attribute")
            
            # Log before value
            old_value = send.value
            logger.debug(f"[SEND] Old value: {old_value:.3f}, setting to: {amount:.3f}")
            
            # Set the value
            send.value = amount
            
            # Verify the change
            new_value = send.value
            value_set = abs(new_value - amount) < 0.001  # Allow small floating point difference
            
            if value_set:
                logger.info(f"[SEND] Successfully set send {return_index} on track {track_index} to {new_value:.3f}")
            else:
                logger.warning(f"[SEND] Value verification failed: expected {amount:.3f}, got {new_value:.3f}")
            
            result_data = {
                "track_index": track_index,
                "track_name": track_name,
                "return_index": return_index,
                "amount_requested": amount,
                "amount_actual": new_value,
                "amount_old": old_value,
                "value_verified": value_set
            }
            
            return self._create_result(
                True,
                f"Send {return_index} on track {track_index} set to {new_value:.3f}",
                result_data
            )
                
        except TrackNotFoundError as e:
            logger.error(f"[SEND] Track not found: {e}")
            return self._create_result(False, str(e))
        except Exception as e:
            logger.error(f"[SEND] Failed to set send: {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 using TCP command.
        
        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:
            # Verify file exists
            if not os.path.exists(file_path):
                return self._create_result(False, f"Audio file not found: {file_path}")
            
            # Use TCP command to create arrangement audio clip
            resp = self._send_tcp_command({
                "type": "create_arrangement_audio_pattern",
                "params": {
                    "track_index": track_index,
                    "file_path": file_path,
                    "positions": [start_bar],
                    "name": "Arrangement Clip"
                }
            })
            
            if resp.get("status") == "success":
                return self._create_result(
                    True,
                    f"Audio clip created at bar {start_bar} on track {track_index}",
                    {"track_index": track_index, "start_bar": start_bar}
                )
            else:
                return self._create_result(
                    False, 
                    resp.get("message", "Failed to create audio clip")
                )
            
        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 using TCP command.
        
        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:
            # Use TCP command to create arrangement MIDI clip
            resp = self._send_tcp_command({
                "type": "create_arrangement_midi_clip",
                "params": {
                    "track_index": track_index,
                    "start_time": start_bar,
                    "length": duration,
                    "notes": notes
                }
            })
            
            if resp.get("status") == "success":
                return self._create_result(
                    True,
                    f"MIDI clip created at bar {start_bar} on track {track_index} with {len(notes)} notes",
                    {"track_index": track_index, "start_bar": start_bar, "note_count": len(notes)}
                )
            else:
                return self._create_result(
                    False,
                    resp.get("message", "Failed to create MIDI clip")
                )
            
        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)}")
    
    # =========================================================================
    # Parameter Discovery Integration (Agent 9)
    # =========================================================================
    
    def discover_device_parameters(self, track_index: int, device_index: int = None) -> Dict[str, Any]:
        """
        Discover and enumerate all parameters for a device on a track.
        
        Agent 9: Device Parameter Discovery System Integration
        
        This method uses the ParameterDiscovery system to discover all available
        parameters for a device, enabling intelligent parameter mapping and
        fuzzy matching for device control.
        
        Args:
            track_index: Index of the track containing the device
            device_index: Optional index of the device (if None, enumerates all devices)
            
        Returns:
            Result dictionary with device parameters information
        """
        try:
            from .parameter_discovery import ParameterDiscovery
            
            discovery = ParameterDiscovery()
            discovery.set_live_bridge(self)
            
            devices_data = []
            
            # Get devices to enumerate
            track = self.song.tracks[track_index]
            if device_index is not None:
                device_indices = [device_index]
            else:
                device_indices = range(len(getattr(track, 'devices', [])))
            
            for dev_idx in device_indices:
                try:
                    params = discovery.enumerate_device_parameters(track_index, dev_idx, self.song)
                    device = track.devices[dev_idx]
                    
                    devices_data.append({
                        "device_index": dev_idx,
                        "device_name": str(device.name),
                        "class_name": str(getattr(device, 'class_name', '')),
                        "parameter_count": len(params),
                        "parameters": [p.to_dict() for p in params]
                    })
                except Exception as e:
                    logger.warning(f"Error discovering device {dev_idx}: {e}")
                    devices_data.append({
                        "device_index": dev_idx,
                        "error": str(e)
                    })
            
            return self._create_result(
                True,
                f"Discovered parameters for {len(devices_data)} device(s) on track {track_index}",
                {
                    "track_index": track_index,
                    "track_name": str(track.name),
                    "device_count": len(devices_data),
                    "devices": devices_data
                }
            )
            
        except ImportError:
            return self._create_result(False, "ParameterDiscovery module not available")
        except Exception as e:
            return self._create_result(False, f"Failed to discover device parameters: {str(e)}")
    
    def find_device_parameter(self, track_index: int, device_index: int, 
                              target_name: str, threshold: float = 0.6) -> Dict[str, Any]:
        """
        Find a parameter by name using fuzzy matching.
        
        Args:
            track_index: Index of the track
            device_index: Index of the device
            target_name: Parameter name to search for
            threshold: Minimum similarity score (0.0-1.0)
            
        Returns:
            Result dictionary with matched parameter information
        """
        try:
            from .parameter_discovery import ParameterDiscovery
            
            discovery = ParameterDiscovery()
            params = discovery.enumerate_device_parameters(track_index, device_index, self.song)
            
            # Find parameter
            matched_param = discovery.find_parameter(target_name, params, threshold)
            
            if matched_param:
                return self._create_result(
                    True,
                    f"Found parameter '{matched_param.name}' matching '{target_name}'",
                    {
                        "track_index": track_index,
                        "device_index": device_index,
                        "target_name": target_name,
                        "matched_name": matched_param.name,
                        "parameter_index": matched_param.index,
                        "min_value": matched_param.min_value,
                        "max_value": matched_param.max_value,
                        "current_value": matched_param.default_value
                    }
                )
            else:
                # Return suggestions
                available_names = [p.name for p in params]
                suggestions = discovery.get_parameter_suggestions(target_name, available_names, max_suggestions=5)
                
                return self._create_result(
                    False,
                    f"Parameter '{target_name}' not found",
                    {
                        "target_name": target_name,
                        "suggestions": [{"name": name, "score": score} for name, score in suggestions],
                        "available_parameters": available_names[:20]  # First 20 for reference
                    }
                )
                
        except ImportError:
            return self._create_result(False, "ParameterDiscovery module not available")
        except Exception as e:
            return self._create_result(False, f"Failed to find device parameter: {str(e)}")
    
    # =========================================================================
    # Multi-Sample Injection for Expansive Production System
    # =========================================================================
    
    def insert_arrangement_audio_varied(self, track_index: int, samples: List[str], 
                                        positions: List[float], 
                                        variation_mode: str = "round_robin") -> Dict[str, Any]:
        """
        Insert multiple audio samples into the arrangement with variation modes.
        
        Supports multiple injection strategies for creating dynamic, varied arrangements:
        - round_robin: Cycles through samples at each position
        - layered: Injects multiple samples at same position with velocity/level
        - pattern: Uses pattern sequence for sample selection
        - section_based: Different samples for different sections
        
        Args:
            track_index: Index of the target audio track
            samples: List of file paths to audio samples
            positions: List of start positions in bars
            variation_mode: Mode for sample variation ("round_robin", "layered", "pattern", "section_based")
            
        Returns:
            Result dictionary with status, clips_created, method, and details
        """
        logger.info(f"[MULTI-SAMPLE] Starting varied injection: track={track_index}, "
                   f"mode={variation_mode}, samples={len(samples)}, positions={len(positions)}")
        
        try:
            # Validate inputs
            if not samples:
                return self._create_result(False, "No samples provided")
            if not positions:
                return self._create_result(False, "No positions provided")
            
            # Check if Live API is available
            if not hasattr(self, 'song') or self.song is None:
                logger.warning("[MULTI-SAMPLE] Live API not available, using simulation mode")
                return self._simulate_multi_sample_injection(track_index, samples, positions, variation_mode)
            
            # Validate track index
            if track_index < 0 or track_index >= len(self.song.tracks):
                raise TrackNotFoundError(f"Track index {track_index} out of range")
            
            # Verify all sample files exist
            missing_samples = [s for s in samples if not os.path.exists(s)]
            if missing_samples:
                return self._create_result(False, f"Sample files not found: {missing_samples}")
            
            # Route to appropriate injection method
            if variation_mode == "round_robin":
                return self._inject_round_robin(track_index, samples, positions)
            elif variation_mode == "layered":
                return self._inject_layered(track_index, samples, positions)
            elif variation_mode == "pattern":
                return self._inject_pattern(track_index, samples, positions)
            elif variation_mode == "section_based":
                return self._inject_section_based(track_index, samples, positions)
            else:
                return self._create_result(False, f"Unknown variation mode: {variation_mode}")
                
        except TrackNotFoundError as e:
            logger.error(f"[MULTI-SAMPLE] Track not found: {e}")
            return self._create_result(False, str(e))
        except Exception as e:
            logger.error(f"[MULTI-SAMPLE] Failed to insert varied audio: {e}")
            return self._create_result(False, f"Failed to insert varied audio: {str(e)}")
    
    def _inject_round_robin(self, track_index: int, samples: List[str], 
                            positions: List[float]) -> Dict[str, Any]:
        """
        Inject samples using round-robin rotation at each position via TCP.
        
        Cycles through the sample list, assigning each sample to consecutive positions.
        Example: samples [A, B, C], positions [0, 4, 8, 12, 16] -> A@0, B@4, C@8, A@12, B@16
        
        Args:
            track_index: Index of the target audio track
            samples: List of file paths to audio samples
            positions: List of start positions in bars
            
        Returns:
            Result dictionary with clips_created and details
        """
        logger.info(f"[ROUND-ROBIN] Injecting {len(samples)} samples at {len(positions)} positions via TCP")
        
        clips_created = []
        
        for i, position in enumerate(positions):
            # Cycle through samples
            sample_index = i % len(samples)
            sample_path = samples[sample_index]
            
            try:
                # Use TCP command to create arrangement audio clip
                resp = self._send_tcp_command({
                    "type": "create_arrangement_audio_pattern",
                    "params": {
                        "track_index": track_index,
                        "file_path": sample_path,
                        "positions": [position],
                        "name": f"RR_Clip_{i}"
                    }
                })
                
                if resp.get("status") == "success":
                    clip_info = {
                        "position": position,
                        "sample": os.path.basename(sample_path),
                        "sample_index": sample_index,
                        "clip_name": f"RR_Clip_{i}"
                    }
                    clips_created.append(clip_info)
                    logger.debug(f"[ROUND-ROBIN] Created clip at bar {position} with sample {sample_index}")
                else:
                    logger.warning(f"[ROUND-ROBIN] Failed to create clip at {position}: {resp.get('message')}")
                    clips_created.append({
                        "position": position,
                        "sample": os.path.basename(sample_path),
                        "error": resp.get("message", "TCP command failed")
                    })
                        
            except Exception as e:
                logger.warning(f"[ROUND-ROBIN] Failed to inject at position {position}: {e}")
                clips_created.append({
                    "position": position,
                    "sample": os.path.basename(sample_path),
                    "error": str(e)
                })
        
        success_count = len([c for c in clips_created if 'error' not in c])
        
        return self._create_result(
            success_count > 0,
            f"Round-robin injection: {success_count}/{len(positions)} clips created",
            {
                "method": "round_robin",
                "track_index": track_index,
                "clips_created": clips_created,
                "success_count": success_count,
                "total_positions": len(positions)
            }
        )
    
    def _inject_layered(self, track_index: int, samples: List[str], 
                        positions: List[float]) -> Dict[str, Any]:
        """
        Inject multiple samples at the same position with varied velocities/levels via TCP.
        
        Creates layered sounds by placing multiple samples at each position,
        with decreasing velocity for each layer to create depth.
        
        Args:
            track_index: Index of the target audio track
            samples: List of file paths to audio samples
            positions: List of start positions in bars
            
        Returns:
            Result dictionary with clips_created and layer details
        """
        logger.info(f"[LAYERED] Injecting {len(samples)} samples as layers at {len(positions)} positions via TCP")
        
        clips_created = []
        
        # Calculate velocity levels for layering (primary is full, others are reduced)
        base_velocity = 1.0
        layer_decay = 0.7  # Each layer is 70% of the previous
        
        for position in positions:
            position_clips = []
            
            for layer_idx, sample_path in enumerate(samples):
                try:
                    # Calculate velocity for this layer
                    layer_velocity = base_velocity * (layer_decay ** layer_idx)
                    
                    # Offset each layer slightly in time for fuller sound
                    time_offset = layer_idx * 0.01  # 0.01 bar offset per layer
                    actual_position = position + time_offset
                    
                    # Use TCP command to create arrangement audio clip
                    resp = self._send_tcp_command({
                        "type": "create_arrangement_audio_pattern",
                        "params": {
                            "track_index": track_index,
                            "file_path": sample_path,
                            "positions": [actual_position],
                            "name": f"Layer_{layer_idx}_{position}"
                        }
                    })
                    
                    if resp.get("status") == "success":
                        clip_info = {
                            "position": actual_position,
                            "base_position": position,
                            "sample": os.path.basename(sample_path),
                            "layer_index": layer_idx,
                            "velocity": layer_velocity,
                            "clip_name": f"Layer_{layer_idx}_{position}"
                        }
                        position_clips.append(clip_info)
                    else:
                        logger.warning(f"[LAYERED] Failed to create layer {layer_idx} at {position}: {resp.get('message')}")
                        position_clips.append({
                            "position": position,
                            "sample": os.path.basename(sample_path),
                            "layer_index": layer_idx,
                            "error": resp.get("message", "TCP command failed")
                        })
                        
                except Exception as e:
                    logger.warning(f"[LAYERED] Failed to inject layer {layer_idx} at position {position}: {e}")
                    position_clips.append({
                        "position": position,
                        "sample": os.path.basename(sample_path),
                        "layer_index": layer_idx,
                        "error": str(e)
                    })
            
            clips_created.extend(position_clips)
        
        success_count = len([c for c in clips_created if 'error' not in c])
        total_expected = len(positions) * len(samples)
        
        return self._create_result(
            success_count > 0,
            f"Layered injection: {success_count}/{total_expected} clips created across {len(positions)} positions",
            {
                "method": "layered",
                "track_index": track_index,
                "clips_created": clips_created,
                "success_count": success_count,
                "total_expected": total_expected,
                "layers_per_position": len(samples),
                "layer_decay": layer_decay
            }
        )
    
    def _inject_pattern(self, track_index: int, samples: List[str], 
                        positions: List[float]) -> Dict[str, Any]:
        """
        Inject samples using a pattern-based sequence via TCP.
        
        Uses the samples list as a pattern sequence that repeats across positions.
        Different from round-robin in that it treats the samples as a rhythmic pattern.
        
        Args:
            track_index: Index of the target audio track
            samples: List of file paths to audio samples (defines the pattern)
            positions: List of start positions in bars
            
        Returns:
            Result dictionary with clips_created and pattern details
        """
        logger.info(f"[PATTERN] Injecting pattern of {len(samples)} samples at {len(positions)} positions via TCP")
        
        clips_created = []
        
        # Pattern sequence - use samples in order as the pattern
        pattern_length = len(samples)
        
        for i, position in enumerate(positions):
            # Select sample based on pattern position
            pattern_index = i % pattern_length
            sample_path = samples[pattern_index]
            
            try:
                # Use TCP command to create arrangement audio clip
                resp = self._send_tcp_command({
                    "type": "create_arrangement_audio_pattern",
                    "params": {
                        "track_index": track_index,
                        "file_path": sample_path,
                        "positions": [position],
                        "name": f"Pat_{pattern_index}_{i}"
                    }
                })
                
                if resp.get("status") == "success":
                    clip_info = {
                        "position": position,
                        "sample": os.path.basename(sample_path),
                        "pattern_index": pattern_index,
                        "pattern_iteration": i // pattern_length,
                        "clip_name": f"Pat_{pattern_index}_{i}"
                    }
                    clips_created.append(clip_info)
                    logger.debug(f"[PATTERN] Created clip at bar {position} with pattern index {pattern_index}")
                else:
                    logger.warning(f"[PATTERN] Failed to create clip at {position}: {resp.get('message')}")
                    clips_created.append({
                        "position": position,
                        "sample": os.path.basename(sample_path),
                        "pattern_index": pattern_index,
                        "error": resp.get("message", "TCP command failed")
                    })
                    
            except Exception as e:
                logger.warning(f"[PATTERN] Failed to inject at position {position}: {e}")
                clips_created.append({
                    "position": position,
                    "sample": os.path.basename(sample_path),
                    "pattern_index": pattern_index,
                    "error": str(e)
                })
        
        success_count = len([c for c in clips_created if 'error' not in c])
        pattern_iterations = len(positions) // pattern_length
        
        return self._create_result(
            success_count > 0,
            f"Pattern injection: {success_count}/{len(positions)} clips created with pattern length {pattern_length}",
            {
                "method": "pattern",
                "track_index": track_index,
                "clips_created": clips_created,
                "success_count": success_count,
                "pattern_length": pattern_length,
                "pattern_iterations": pattern_iterations,
                "remainder_positions": len(positions) % pattern_length
            }
        )
    
    def _inject_section_based(self, track_index: int, samples: List[str], 
                              positions: List[float]) -> Dict[str, Any]:
        """
        Inject samples based on song sections via TCP.
        
        Divides positions into sections and assigns different samples to each section.
        Useful for intro/verse/chorus variations.
        
        Args:
            track_index: Index of the target audio track
            samples: List of file paths to audio samples (one per section)
            positions: List of start positions in bars
            
        Returns:
            Result dictionary with clips_created and section details
        """
        logger.info(f"[SECTION] Injecting {len(samples)} samples across sections at {len(positions)} positions via TCP")
        
        clips_created = []
        
        # Divide positions into sections based on number of samples
        num_sections = len(samples)
        positions_per_section = len(positions) // num_sections
        remainder = len(positions) % num_sections
        
        section_idx = 0
        position_idx = 0
        
        while position_idx < len(positions):
            # Calculate how many positions for this section
            section_count = positions_per_section + (1 if section_idx < remainder else 0)
            sample_path = samples[section_idx % len(samples)]
            
            for pos_in_section in range(section_count):
                if position_idx >= len(positions):
                    break
                    
                position = positions[position_idx]
                
                try:
                    # Use TCP command to create arrangement audio clip
                    resp = self._send_tcp_command({
                        "type": "create_arrangement_audio_pattern",
                        "params": {
                            "track_index": track_index,
                            "file_path": sample_path,
                            "positions": [position],
                            "name": f"Sec_{section_idx}_{pos_in_section}"
                        }
                    })
                    
                    if resp.get("status") == "success":
                        clip_info = {
                            "position": position,
                            "sample": os.path.basename(sample_path),
                            "section_index": section_idx,
                            "position_in_section": pos_in_section,
                            "clip_name": f"Sec_{section_idx}_{pos_in_section}"
                        }
                        clips_created.append(clip_info)
                    else:
                        logger.warning(f"[SECTION] Failed to create clip at {position}: {resp.get('message')}")
                        clips_created.append({
                            "position": position,
                            "sample": os.path.basename(sample_path),
                            "section_index": section_idx,
                            "error": resp.get("message", "TCP command failed")
                        })
                        
                except Exception as e:
                    logger.warning(f"[SECTION] Failed to inject at position {position}: {e}")
                    clips_created.append({
                        "position": position,
                        "sample": os.path.basename(sample_path),
                        "section_index": section_idx,
                        "error": str(e)
                    })
                
                position_idx += 1
            
            section_idx += 1
        
        success_count = len([c for c in clips_created if 'error' not in c])
        
        return self._create_result(
            success_count > 0,
            f"Section-based injection: {success_count}/{len(positions)} clips across {num_sections} sections",
            {
                "method": "section_based",
                "track_index": track_index,
                "clips_created": clips_created,
                "success_count": success_count,
                "num_sections": num_sections,
                "positions_per_section": positions_per_section,
                "sections_with_extra": remainder
            }
        )
    
    def _simulate_multi_sample_injection(self, track_index: int, samples: List[str], 
                                         positions: List[float], 
                                         variation_mode: str) -> Dict[str, Any]:
        """
        Simulate multi-sample injection when Live API is not available.
        
        Creates a mock result showing what would be created, useful for testing
        and development without a Live connection.
        
        Args:
            track_index: Index of the target audio track (mock)
            samples: List of file paths to audio samples
            positions: List of start positions in bars
            variation_mode: Mode for sample variation
            
        Returns:
            Result dictionary with simulated clip information
        """
        logger.info(f"[SIMULATE] Multi-sample injection in simulation mode: {variation_mode}")
        
        simulated_clips = []
        
        if variation_mode == "round_robin":
            for i, position in enumerate(positions):
                sample_index = i % len(samples)
                simulated_clips.append({
                    "position": position,
                    "sample": os.path.basename(samples[sample_index]),
                    "sample_index": sample_index,
                    "clip_name": f"Clip_{i}",
                    "simulated": True
                })
                
        elif variation_mode == "layered":
            layer_decay = 0.7
            for position in positions:
                for layer_idx, sample_path in enumerate(samples):
                    layer_velocity = 1.0 * (layer_decay ** layer_idx)
                    time_offset = layer_idx * 0.01
                    simulated_clips.append({
                        "position": position + time_offset,
                        "base_position": position,
                        "sample": os.path.basename(sample_path),
                        "layer_index": layer_idx,
                        "velocity": layer_velocity,
                        "clip_name": f"Clip_L{layer_idx}_{position}",
                        "simulated": True
                    })
                    
        elif variation_mode == "pattern":
            pattern_length = len(samples)
            for i, position in enumerate(positions):
                pattern_index = i % pattern_length
                simulated_clips.append({
                    "position": position,
                    "sample": os.path.basename(samples[pattern_index]),
                    "pattern_index": pattern_index,
                    "pattern_iteration": i // pattern_length,
                    "clip_name": f"Clip_P{pattern_index}_{i}",
                    "simulated": True
                })
                
        elif variation_mode == "section_based":
            num_sections = len(samples)
            positions_per_section = len(positions) // num_sections
            remainder = len(positions) % num_sections
            
            section_idx = 0
            position_idx = 0
            
            while position_idx < len(positions):
                section_count = positions_per_section + (1 if section_idx < remainder else 0)
                sample_path = samples[section_idx % len(samples)]
                
                for pos_in_section in range(section_count):
                    if position_idx >= len(positions):
                        break
                    position = positions[position_idx]
                    simulated_clips.append({
                        "position": position,
                        "sample": os.path.basename(sample_path),
                        "section_index": section_idx,
                        "position_in_section": pos_in_section,
                        "clip_name": f"Clip_S{section_idx}_{pos_in_section}",
                        "simulated": True
                    })
                    position_idx += 1
                
                section_idx += 1
        
        return self._create_result(
            True,
            f"[SIMULATION] {variation_mode}: {len(simulated_clips)} clips would be created",
            {
                "method": variation_mode,
                "mode": "simulation",
                "track_index": track_index,
                "clips_created": simulated_clips,
                "simulated_count": len(simulated_clips),
                "total_positions": len(positions),
                "sample_count": len(samples)
            }
        )
