"""
Professional Bus and Return Architecture for AbletonMCP_AI

Implements professional mixing architecture with:
- Bus groups (drums, bass, music, vocal, fx)
- Return tracks with effects (space/reverb, echo/delay, heat/saturation, glue/compression)
- Role-based mix profiles
- Master chain processing
"""

from __future__ import absolute_import, print_function, unicode_literals

# =============================================================================
# BUS GAIN CALIBRATION
# =============================================================================

BUS_GAIN_CALIBRATION = {
    'drums': {
        'volume': 0.92,
        'compressor_threshold': -16.0,
        'compressor_ratio': 4.0,
        'saturator_drive': 0.6,
        'pan': 0.0
    },
    'bass': {
        'volume': 0.88,
        'compressor_threshold': -18.0,
        'compressor_ratio': 3.0,
        'saturator_drive': 0.4,
        'pan': 0.0
    },
    'music': {
        'volume': 0.85,
        'compressor_threshold': -20.0,
        'compressor_ratio': 2.5,
        'pan': 0.0
    },
    'vocal': {
        'volume': 0.82,
        'compressor_threshold': -16.0,
        'compressor_ratio': 3.0,
        'pan': 0.0
    },
    'fx': {
        'volume': 0.78,
        'compressor_threshold': -22.0,
        'compressor_ratio': 2.0,
        'pan': 0.0
    }
}

# =============================================================================
# RETURN TRACK CONFIGURATION
# =============================================================================

RETURN_CONFIG = {
    'space': {  # Reverb
        'device': 'Reverb',
        'default_params': {
            'PreDelay': 20.0,
            'DecayTime': 2500.0,
            'Size': 0.7,
            'DryWet': 0.3
        }
    },
    'echo': {  # Delay
        'device': 'Delay',
        'default_params': {
            'DelayTime': '1/8',
            'Feedback': 0.35,
            'DryWet': 0.25
        }
    },
    'heat': {  # Saturation
        'device': 'Saturator',
        'default_params': {
            'Drive': 6.0,
            'Type': 0,  # Analog
            'DryWet': 0.2
        }
    },
    'glue': {  # Bus Compression
        'device': 'Compressor',
        'default_params': {
            'Threshold': -20.0,
            'Ratio': 2.0,
            'Attack': 10.0,
            'Release': 100.0,
            'DryWet': 0.15
        }
    }
}

# =============================================================================
# ROLE MIX PROFILES
# =============================================================================

ROLE_MIX = {
    'kick': {
        'volume': 0.85,
        'pan': 0.0,
        'sends': {'glue': 0.08},
        'bus': 'drums'
    },
    'snare': {
        'volume': 0.82,
        'pan': 0.0,
        'sends': {'space': 0.12, 'echo': 0.05, 'glue': 0.10},
        'bus': 'drums'
    },
    'clap': {
        'volume': 0.78,
        'pan': 0.0,
        'sends': {'space': 0.14, 'echo': 0.04, 'heat': 0.02, 'glue': 0.10},
        'bus': 'drums'
    },
    'hat_closed': {
        'volume': 0.72,
        'pan': 0.15,
        'sends': {'space': 0.08, 'glue': 0.05},
        'bus': 'drums'
    },
    'hat_open': {
        'volume': 0.75,
        'pan': -0.15,
        'sends': {'space': 0.15, 'glue': 0.06},
        'bus': 'drums'
    },
    'bass': {
        'volume': 0.78,
        'pan': 0.0,
        'sends': {'heat': 0.04, 'glue': 0.12},
        'bus': 'bass'
    },
    'sub_bass': {
        'volume': 0.80,
        'pan': 0.0,
        'sends': {'glue': 0.10},
        'bus': 'bass'
    },
    'lead': {
        'volume': 0.76,
        'pan': 0.25,
        'sends': {'space': 0.20, 'echo': 0.15, 'glue': 0.08},
        'bus': 'music'
    },
    'pad': {
        'volume': 0.70,
        'pan': -0.20,
        'sends': {'space': 0.35, 'echo': 0.10, 'glue': 0.06},
        'bus': 'music'
    },
    'pluck': {
        'volume': 0.74,
        'pan': 0.30,
        'sends': {'space': 0.18, 'echo': 0.12, 'glue': 0.07},
        'bus': 'music'
    },
    'chords': {
        'volume': 0.72,
        'pan': 0.0,
        'sends': {'space': 0.25, 'echo': 0.08, 'glue': 0.07},
        'bus': 'music'
    },
    'fx': {
        'volume': 0.68,
        'pan': 0.0,
        'sends': {'space': 0.40, 'echo': 0.20},
        'bus': 'fx'
    },
    'vocal': {
        'volume': 0.80,
        'pan': 0.0,
        'sends': {'space': 0.25, 'echo': 0.12, 'heat': 0.03, 'glue': 0.10},
        'bus': 'vocal'
    }
}

# =============================================================================
# MASTER CHAIN CONFIGURATION
# =============================================================================

MASTER_CHAIN = {
    'eq': {
        'device': 'EQEight',
        'params': {
            'GainLow': 0.0,
            'FreqLowest': 30.0,
            'GainMid': 0.0,
            'GainHigh': 0.0
        }
    },
    'compressor': {
        'device': 'Compressor',
        'params': {
            'Threshold': -6.0,
            'Ratio': 2.0,
            'Attack': 3.0,
            'Release': 60.0,
            'DryWet': 100.0
        }
    },
    'limiter': {
        'device': 'Limiter',
        'params': {
            'Gain': 0.0,
            'Ceiling': -0.3
        }
    }
}

# =============================================================================
# BUS ARCHITECTURE IMPLEMENTATION
# =============================================================================

class BusArchitecture:
    """Professional bus and return architecture manager."""
    
    def __init__(self, ableton_conn):
        """
        Initialize with Ableton connection.
        
        Args:
            ableton_conn: The Ableton Live connection (self from __init__.py)
        """
        self.conn = ableton_conn
        self._song = ableton_conn._song if hasattr(ableton_conn, '_song') else None
        self._bus_indices = {}  # bus_name -> track_index
        self._return_indices = {}  # return_name -> return_track_index
        
    def create_bus_track(self, bus_name, bus_type='audio'):
        """
        Creates a bus (group) track for submixing.
        
        Args:
            bus_name: Name for the bus track (e.g., "BUS Drums")
            bus_type: 'audio' or 'midi' (default 'audio')
            
        Returns:
            dict: Creation status with track_index
        """
        if self._song is None:
            return {"error": "No song connection available"}
            
        try:
            # Create appropriate track type
            if bus_type.lower() == 'midi':
                self._song.create_midi_track(-1)
            else:
                self._song.create_audio_track(-1)
                
            idx = len(self._song.tracks) - 1
            track = self._song.tracks[idx]
            track.name = str(bus_name)
            
            # Store the index
            self._bus_indices[bus_name] = idx
            
            return {
                "bus_created": True,
                "track_index": idx,
                "bus_name": str(bus_name),
                "bus_type": bus_type
            }
        except Exception as e:
            return {
                "bus_created": False,
                "error": str(e),
                "bus_name": str(bus_name)
            }
    
    def create_return_track(self, return_name, effect_type=None):
        """
        Creates a return track with optional effect.
        
        Args:
            return_name: Name for the return track (e.g., "Reverb", "Delay")
            effect_type: Effect device name to insert (e.g., "Reverb", "Delay")
            
        Returns:
            dict: Creation status with return_track_index
        """
        if self._song is None:
            return {"error": "No song connection available"}
            
        try:
            # Create return track using Live API
            if hasattr(self._song, 'create_return_track'):
                self._song.create_return_track(-1)
            else:
                # Fallback: create audio track and use as return
                self._song.create_audio_track(-1)
                
            # Return tracks are after regular tracks in Live
            if hasattr(self._song, 'return_tracks'):
                idx = len(self._song.return_tracks) - 1
                return_track = self._song.return_tracks[idx]
            else:
                # Fallback: use last created track
                idx = len(self._song.tracks) - 1
                return_track = self._song.tracks[idx]
                
            return_track.name = str(return_name)
            
            # Store the index
            self._return_indices[return_name] = idx
            
            result = {
                "return_created": True,
                "return_index": idx,
                "return_name": str(return_name)
            }
            
            # Insert effect if specified
            if effect_type:
                device_result = self._insert_device_on_return(idx, effect_type)
                result["device_inserted"] = device_result
                
            return result
            
        except Exception as e:
            return {
                "return_created": False,
                "error": str(e),
                "return_name": str(return_name)
            }
    
    def _insert_device_on_return(self, return_index, device_name):
        """Insert a device on a return track."""
        try:
            if hasattr(self._song, 'return_tracks'):
                track = self._song.return_tracks[return_index]
            else:
                track = self._song.tracks[return_index]
                
            # Use the connection's device insertion if available
            if hasattr(self.conn, '_browser_load_device'):
                return self.conn._browser_load_device(track, device_name)
            return False
        except Exception as e:
            return False
    
    def route_track_to_bus(self, track_index, bus_name):
        """
        Routes a track's output to a bus track.
        
        In Ableton Live, this is typically done by grouping tracks or setting
        output routing. Since direct API routing is limited, this sets up
        the conceptual routing and returns guidance.
        
        Args:
            track_index: Index of the source track
            bus_name: Name of the bus track to route to
            
        Returns:
            dict: Routing status
        """
        if self._song is None:
            return {"error": "No song connection available"}
            
        try:
            src_idx = int(track_index)
            src_track = self._song.tracks[src_idx]
            
            # Find the bus track
            bus_idx = None
            bus_track = None
            
            # Check our stored indices first
            if bus_name in self._bus_indices:
                bus_idx = self._bus_indices[bus_name]
                bus_track = self._song.tracks[bus_idx]
            else:
                # Search by name
                for i, t in enumerate(self._song.tracks):
                    if bus_name.lower() in str(t.name).lower():
                        bus_idx = i
                        bus_track = t
                        break
            
            if bus_track is None:
                return {
                    "routed": False,
                    "error": "Bus track '%s' not found" % bus_name
                }
            
            # Try to configure output routing through mixer device
            # Note: Full output routing API varies by Live version
            mixer = src_track.mixer_device
            
            # Attempt to set up sends to the bus if available
            sends_configured = 0
            if hasattr(mixer, 'sends'):
                for send in mixer.sends:
                    if hasattr(send, 'target_track') and send.target_track == bus_track:
                        # Send already targets this bus
                        sends_configured += 1
                        break
            
            # Try output routing if available
            output_set = False
            if hasattr(src_track, 'output_routing_type'):
                # Some Live versions support this
                try:
                    src_track.output_routing_type = bus_track
                    output_set = True
                except:
                    pass
            elif hasattr(src_track, 'output_routing_channel'):
                try:
                    src_track.output_routing_channel = bus_track
                    output_set = True
                except:
                    pass
            
            return {
                "routed": True,
                "track_index": src_idx,
                "track_name": str(src_track.name),
                "bus_index": bus_idx,
                "bus_name": str(bus_name),
                "output_routing_set": output_set,
                "sends_configured": sends_configured,
                "note": "Manual grouping in Live may be needed for complete bus routing"
            }
            
        except Exception as e:
            return {
                "routed": False,
                "track_index": track_index,
                "error": str(e)
            }
    
    def set_track_send(self, track_index, return_name, amount):
        """
        Sets send amount from a track to a return track.
        
        Args:
            track_index: Index of the source track
            return_name: Name of the return track
            amount: Send amount 0.0-1.0
            
        Returns:
            dict: Send configuration status
        """
        if self._song is None:
            return {"error": "No song connection available"}
            
        try:
            track_idx = int(track_index)
            track = self._song.tracks[track_idx]
            send_amount = float(amount)
            
            # Find return track index
            return_idx = None
            if return_name in self._return_indices:
                return_idx = self._return_indices[return_name]
            else:
                # Search in return tracks
                if hasattr(self._song, 'return_tracks'):
                    for i, rt in enumerate(self._song.return_tracks):
                        if return_name.lower() in str(rt.name).lower():
                            return_idx = i
                            break
            
            if return_idx is None:
                return {
                    "send_set": False,
                    "error": "Return track '%s' not found" % return_name
                }
            
            # Configure send via mixer device
            mixer = track.mixer_device
            sends_configured = 0
            
            if hasattr(mixer, 'sends') and return_idx < len(mixer.sends):
                send = mixer.sends[return_idx]
                if hasattr(send, 'value'):
                    send.value = send_amount
                    sends_configured = 1
            
            return {
                "send_set": sends_configured > 0,
                "track_index": track_idx,
                "track_name": str(track.name),
                "return_name": str(return_name),
                "return_index": return_idx,
                "amount": send_amount,
                "sends_configured": sends_configured
            }
            
        except Exception as e:
            return {
                "send_set": False,
                "track_index": track_index,
                "error": str(e)
            }
    
    def configure_bus_gain(self, bus_name):
        """
        Configure bus track with professional gain calibration settings.
        
        Args:
            bus_name: Name of the bus (must match BUS_GAIN_CALIBRATION keys)
            
        Returns:
            dict: Configuration status
        """
        if bus_name not in BUS_GAIN_CALIBRATION:
            return {
                "configured": False,
                "error": "Unknown bus name '%s'. Valid: %s" % (bus_name, list(BUS_GAIN_CALIBRATION.keys()))
            }
        
        config = BUS_GAIN_CALIBRATION[bus_name]
        
        # Find the bus track
        bus_idx = self._bus_indices.get(bus_name)
        if bus_idx is None:
            # Search by name pattern
            for i, t in enumerate(self._song.tracks):
                if bus_name.lower() in str(t.name).lower() or ('bus' in str(t.name).lower() and bus_name.lower() in str(t.name).lower()):
                    bus_idx = i
                    break
        
        if bus_idx is None:
            return {
                "configured": False,
                "error": "Bus track '%s' not found" % bus_name
            }
        
        try:
            track = self._song.tracks[bus_idx]
            
            # Set volume
            track.mixer_device.volume.value = config['volume']
            
            # Set pan
            track.mixer_device.panning.value = config['pan']
            
            return {
                "configured": True,
                "bus_name": bus_name,
                "bus_index": bus_idx,
                "volume": config['volume'],
                "pan": config['pan'],
                "note": "Compressor and saturator settings available for manual application"
            }
            
        except Exception as e:
            return {
                "configured": False,
                "bus_name": bus_name,
                "error": str(e)
            }
    
    def configure_return_effect(self, return_name):
        """
        Configure return track effect with default parameters.
        
        Args:
            return_name: Name of the return (must match RETURN_CONFIG keys)
            
        Returns:
            dict: Configuration status
        """
        if return_name not in RETURN_CONFIG:
            return {
                "configured": False,
                "error": "Unknown return name '%s'. Valid: %s" % (return_name, list(RETURN_CONFIG.keys()))
            }
        
        config = RETURN_CONFIG[return_name]
        
        # Find the return track
        return_idx = self._return_indices.get(return_name)
        if return_idx is None:
            # Search in return tracks
            if hasattr(self._song, 'return_tracks'):
                for i, rt in enumerate(self._song.return_tracks):
                    if return_name.lower() in str(rt.name).lower():
                        return_idx = i
                        break
        
        if return_idx is None:
            return {
                "configured": False,
                "error": "Return track '%s' not found" % return_name
            }
        
        try:
            # Get the return track
            if hasattr(self._song, 'return_tracks'):
                track = self._song.return_tracks[return_idx]
            else:
                track = self._song.tracks[return_idx]
            
            # Find the effect device
            device = None
            for d in track.devices:
                if config['device'].lower() in str(d.name).lower():
                    device = d
                    break
            
            if device is None:
                return {
                    "configured": False,
                    "return_name": return_name,
                    "error": "Device '%s' not found on return track" % config['device']
                }
            
            # Configure parameters
            params_set = 0
            if hasattr(device, 'parameters'):
                for param in device.parameters:
                    param_name = str(param.name)
                    for key, value in config['default_params'].items():
                        if key in param_name:
                            try:
                                if isinstance(value, str):
                                    # Handle string values like '1/8' for delay time
                                    # This may need manual adjustment in Live
                                    pass
                                else:
                                    param.value = float(value)
                                    params_set += 1
                            except Exception:
                                pass
                            break
            
            return {
                "configured": True,
                "return_name": return_name,
                "return_index": return_idx,
                "device": config['device'],
                "parameters_set": params_set,
                "target_params": list(config['default_params'].keys())
            }
            
        except Exception as e:
            return {
                "configured": False,
                "return_name": return_name,
                "error": str(e)
            }
    
    def apply_role_mix(self, track_index, role):
        """
        Apply role-based mix settings to a track.
        
        Args:
            track_index: Index of the track
            role: Role name (must match ROLE_MIX keys)
            
        Returns:
            dict: Application status
        """
        if role not in ROLE_MIX:
            return {
                "applied": False,
                "error": "Unknown role '%s'. Valid: %s" % (role, list(ROLE_MIX.keys()))
            }
        
        config = ROLE_MIX[role]
        
        try:
            track_idx = int(track_index)
            track = self._song.tracks[track_idx]
            
            # Set volume
            track.mixer_device.volume.value = config['volume']
            
            # Set pan
            track.mixer_device.panning.value = config['pan']
            
            # Configure sends
            sends_configured = []
            for return_name, amount in config['sends'].items():
                result = self.set_track_send(track_idx, return_name, amount)
                sends_configured.append({
                    "return": return_name,
                    "amount": amount,
                    "status": result.get("send_set", False)
                })
            
            return {
                "applied": True,
                "track_index": track_idx,
                "track_name": str(track.name),
                "role": role,
                "volume": config['volume'],
                "pan": config['pan'],
                "target_bus": config['bus'],
                "sends": sends_configured
            }
            
        except Exception as e:
            return {
                "applied": False,
                "track_index": track_index,
                "role": role,
                "error": str(e)
            }
    
    def configure_master_chain(self):
        """
        Configure master track with professional mastering chain.
        
        Returns:
            dict: Configuration status
        """
        try:
            master = self._song.master_track
            
            devices_found = {}
            
            # Check for existing devices
            for chain_type, chain_config in MASTER_CHAIN.items():
                device_name = chain_config['device']
                device = None
                
                for d in master.devices:
                    if device_name.lower() in str(d.name).lower():
                        device = d
                        break
                
                devices_found[chain_type] = {
                    "device": device_name,
                    "found": device is not None,
                    "name": str(device.name) if device else None
                }
                
                # Configure parameters if device exists
                if device and hasattr(device, 'parameters'):
                    params_set = 0
                    for param in device.parameters:
                        param_name = str(param.name)
                        for key, value in chain_config['params'].items():
                            if key in param_name:
                                try:
                                    param.value = float(value)
                                    params_set += 1
                                except Exception:
                                    pass
                                break
                    devices_found[chain_type]["params_set"] = params_set
            
            return {
                "configured": True,
                "master_track": "Master",
                "devices": devices_found,
                "recommendation": "Add EQ Eight, Compressor, and Limiter to master if not present"
            }
            
        except Exception as e:
            return {
                "configured": False,
                "error": str(e)
            }


# =============================================================================
# MODULE-LEVEL FUNCTIONS (for direct use)
# =============================================================================

def create_bus_track(ableton_conn, bus_name, bus_type='audio'):
    """
    Creates a group/bus track.
    
    Args:
        ableton_conn: The Ableton Live connection
        bus_name: Name for the bus track
        bus_type: 'audio' or 'midi'
        
    Returns:
        dict: Creation status
    """
    arch = BusArchitecture(ableton_conn)
    return arch.create_bus_track(bus_name, bus_type)


def create_return_track(ableton_conn, return_name, effect_type=None):
    """
    Creates a return track with effect.
    
    Args:
        ableton_conn: The Ableton Live connection
        return_name: Name for the return track
        effect_type: Effect device name to insert
        
    Returns:
        dict: Creation status
    """
    arch = BusArchitecture(ableton_conn)
    return arch.create_return_track(return_name, effect_type)


def route_track_to_bus(ableton_conn, track_index, bus_name):
    """
    Routes a track to a bus.
    
    Args:
        ableton_conn: The Ableton Live connection
        track_index: Index of the source track
        bus_name: Name of the bus track
        
    Returns:
        dict: Routing status
    """
    arch = BusArchitecture(ableton_conn)
    return arch.route_track_to_bus(track_index, bus_name)


def set_track_send(ableton_conn, track_index, return_name, amount):
    """
    Sets send amount to return track.
    
    Args:
        ableton_conn: The Ableton Live connection
        track_index: Index of the source track
        return_name: Name of the return track
        amount: Send amount 0.0-1.0
        
    Returns:
        dict: Send configuration status
    """
    arch = BusArchitecture(ableton_conn)
    return arch.set_track_send(track_index, return_name, amount)


def apply_professional_mix(ableton_conn, track_assignments):
    """
    Applies complete professional mix architecture.
    
    This is the main entry point for setting up a professional mix:
    1. Creates buses (drums, bass, music, vocal, fx)
    2. Creates returns (space, echo, heat, glue)
    3. Routes tracks to appropriate buses
    4. Sets send levels per role
    5. Applies master chain configuration
    6. Configures bus gain calibration
    
    Args:
        ableton_conn: The Ableton Live connection
        track_assignments: List of dicts with 'track_index', 'role', 'bus'
            Example: [
                {"track_index": 0, "role": "kick", "bus": "drums"},
                {"track_index": 1, "role": "bass", "bus": "bass"},
            ]
            
    Returns:
        dict: Complete mix application status
    """
    arch = BusArchitecture(ableton_conn)
    results = {
        "buses_created": [],
        "returns_created": [],
        "tracks_routed": [],
        "sends_configured": [],
        "master_configured": False,
        "errors": []
    }
    
    try:
        # 1. Create buses
        bus_names = ['drums', 'bass', 'music', 'vocal', 'fx']
        for bus_name in bus_names:
            bus_result = arch.create_bus_track("BUS %s" % bus_name.capitalize())
            if bus_result.get("bus_created"):
                results["buses_created"].append(bus_result)
                # Configure bus gain
                gain_result = arch.configure_bus_gain(bus_name)
                if gain_result.get("configured"):
                    results["buses_created"][-1]["gain_configured"] = True
            else:
                results["errors"].append("Bus %s: %s" % (bus_name, bus_result.get("error", "Unknown error")))
        
        # 2. Create returns with effects
        for return_name, config in RETURN_CONFIG.items():
            return_result = arch.create_return_track(
                return_name.capitalize(),
                effect_type=config['device']
            )
            if return_result.get("return_created"):
                results["returns_created"].append(return_result)
                # Configure return effect
                effect_result = arch.configure_return_effect(return_name)
                if effect_result.get("configured"):
                    results["returns_created"][-1]["effect_configured"] = True
            else:
                results["errors"].append("Return %s: %s" % (return_name, return_result.get("error", "Unknown error")))
        
        # 3. Route tracks and apply role mix
        for assignment in track_assignments:
            track_idx = assignment.get("track_index")
            role = assignment.get("role")
            bus = assignment.get("bus")
            
            if track_idx is None or role is None:
                continue
            
            # Apply role mix (includes sends)
            mix_result = arch.apply_role_mix(track_idx, role)
            if mix_result.get("applied"):
                results["tracks_routed"].append(mix_result)
            else:
                results["errors"].append("Track %s role %s: %s" % (track_idx, role, mix_result.get("error")))
            
            # Route to bus if specified
            if bus:
                route_result = arch.route_track_to_bus(track_idx, "BUS %s" % bus.capitalize())
                if route_result.get("routed"):
                    results["tracks_routed"][-1]["bus_routed"] = True
        
        # 4. Configure master chain
        master_result = arch.configure_master_chain()
        results["master_configured"] = master_result.get("configured", False)
        results["master_details"] = master_result
        
        # Summary
        results["summary"] = {
            "buses": len(results["buses_created"]),
            "returns": len(results["returns_created"]),
            "tracks_processed": len(results["tracks_routed"]),
            "errors": len(results["errors"])
        }
        
        return results
        
    except Exception as e:
        results["errors"].append("Fatal error: %s" % str(e))
        return results


def get_bus_config(bus_name):
    """
    Get bus configuration by name.
    
    Args:
        bus_name: Name of the bus (e.g., 'drums', 'bass')
        
    Returns:
        dict: Bus configuration or None
    """
    return BUS_GAIN_CALIBRATION.get(bus_name)


def get_return_config(return_name):
    """
    Get return track configuration by name.
    
    Args:
        return_name: Name of the return (e.g., 'space', 'echo')
        
    Returns:
        dict: Return configuration or None
    """
    return RETURN_CONFIG.get(return_name)


def get_role_mix(role):
    """
    Get role mix profile.
    
    Args:
        role: Role name (e.g., 'kick', 'bass', 'lead')
        
    Returns:
        dict: Role mix configuration or None
    """
    return ROLE_MIX.get(role)


def get_master_chain():
    """
    Get master chain configuration.
    
    Returns:
        dict: Master chain configuration
    """
    return MASTER_CHAIN


def list_available_buses():
    """List all available bus names."""
    return list(BUS_GAIN_CALIBRATION.keys())


def list_available_returns():
    """List all available return names."""
    return list(RETURN_CONFIG.keys())


def list_available_roles():
    """List all available role names."""
    return list(ROLE_MIX.keys())
