#!/usr/bin/env python3
"""
hardware_integration.py - Mapeo de Hardware MIDI & Sensores (T166-T180)

BLOQUE 3: Integración completa de controladores MIDI hardware para
performance en vivo, incluyendo mapeo de CC, feedback luminoso,
sincronización MIDI Clock y modos de performance especializados.

Soporta:
- Allen & Heath Xone:K2
- AKAI APC40/APC40 MKII
- Pioneer DDJ series (mapeo MIDI estándar)
- Controladores MIDI genéricos

Author: AbletonMCP-AI System
Version: 1.0.0
"""

import asyncio
import json
import logging
import math
import threading
import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
from collections import deque

try:
    import mido
    from mido import Message, MidiFile, MidiTrack
    MIDO_AVAILABLE = True
except ImportError:
    MIDO_AVAILABLE = False

# Configuración de logging
logger = logging.getLogger(__name__)

# =============================================================================
# T166: Configuración de Hardware y Mapeos MIDI
# =============================================================================

class HardwareType(Enum):
    """Tipos de controladores soportados."""
    XONE_K2 = "xone_k2"
    AKAI_APC40 = "akai_apc40"
    AKAI_APC40_MK2 = "akai_apc40_mk2"
    PIONEER_DDJ = "pioneer_ddj"
    GENERIC_MIDI = "generic_midi"


@dataclass
class CCMapping:
    """Mapeo de un control CC MIDI a una función."""
    cc_number: int
    channel: int
    name: str
    min_value: int = 0
    max_value: int = 127
    curve: str = "linear"  # linear, exponential, s_curve
    target_bus: Optional[str] = None
    callback: Optional[Callable] = None
    async_callback: Optional[Callable] = None
    feedback_enabled: bool = False
    led_output_cc: Optional[int] = None


@dataclass
class NoteMapping:
    """Mapeo de nota MIDI a una función."""
    note: int
    channel: int
    name: str
    trigger_mode: str = "toggle"  # toggle, momentary, hold
    callback: Optional[Callable] = None
    async_callback: Optional[Callable] = None
    feedback_enabled: bool = False
    led_color: int = 0  # Para APC40: 0=off, 1=green, 2=red, 3=yellow


@dataclass
class HardwareConfig:
    """Configuración completa de un controlador hardware."""
    hardware_type: HardwareType
    name: str
    input_port: str = ""
    output_port: str = ""
    cc_mappings: Dict[str, CCMapping] = field(default_factory=dict)
    note_mappings: Dict[str, NoteMapping] = field(default_factory=dict)
    has_led_feedback: bool = False
    has_7segment_display: bool = False
    has_lcd_display: bool = False
    fader_count: int = 0
    knob_count: int = 0
    pad_count: int = 0
    button_count: int = 0


# Mapeos predefinidos para Xone:K2
XONE_K2_MAPPINGS = {
    "cc_mappings": {
        "filter_high": CCMapping(1, 0, "Filter High", 0, 127, "linear", "music_bus"),
        "filter_mid": CCMapping(2, 0, "Filter Mid", 0, 127, "linear", "music_bus"),
        "filter_low": CCMapping(3, 0, "Filter Low", 0, 127, "linear", "music_bus"),
        "gain_staging": CCMapping(4, 0, "Gain Staging", 0, 127, "linear"),
        "humanize_amount": CCMapping(5, 0, "Humanize", 0, 127, "exponential"),
        "sidechain_amount": CCMapping(6, 0, "Sidechain", 0, 127, "s_curve", "bass_bus"),
        "reverb_send": CCMapping(7, 0, "Reverb Send", 0, 127, "exponential", "music_bus"),
        "delay_send": CCMapping(8, 0, "Delay Send", 0, 127, "exponential", "music_bus"),
        "fader_1": CCMapping(11, 0, "Fader 1", 0, 127, "linear", "drums_bus"),
        "fader_2": CCMapping(12, 0, "Fader 2", 0, 127, "linear", "bass_bus"),
        "fader_3": CCMapping(13, 0, "Fader 3", 0, 127, "linear", "music_bus"),
        "fader_4": CCMapping(14, 0, "Fader 4", 0, 127, "linear", "master"),
        "master_volume": CCMapping(15, 0, "Master", 0, 127, "linear", "master"),
    },
    "note_mappings": {
        "scene_1": NoteMapping(32, 0, "Scene 1", "momentary"),
        "scene_2": NoteMapping(33, 0, "Scene 2", "momentary"),
        "scene_3": NoteMapping(34, 0, "Scene 3", "momentary"),
        "scene_4": NoteMapping(35, 0, "Scene 4", "momentary"),
        "panic_button": NoteMapping(36, 0, "Panic", "momentary"),
        "fill_trigger": NoteMapping(37, 0, "Fill Trigger", "momentary"),
        "backup_track": NoteMapping(38, 0, "Backup Track", "toggle"),
        "performance_mode": NoteMapping(39, 0, "Performance Mode", "toggle"),
        "pad_1": NoteMapping(40, 1, "Pad 1", "momentary", feedback_enabled=True),
        "pad_2": NoteMapping(41, 1, "Pad 2", "momentary", feedback_enabled=True),
        "pad_3": NoteMapping(42, 1, "Pad 3", "momentary", feedback_enabled=True),
        "pad_4": NoteMapping(43, 1, "Pad 4", "momentary", feedback_enabled=True),
        "pad_5": NoteMapping(44, 1, "Pad 5", "momentary", feedback_enabled=True),
        "pad_6": NoteMapping(45, 1, "Pad 6", "momentary", feedback_enabled=True),
        "pad_7": NoteMapping(46, 1, "Pad 7", "momentary", feedback_enabled=True),
        "pad_8": NoteMapping(47, 1, "Pad 8", "momentary", feedback_enabled=True),
    }
}

# Mapeos predefinidos para AKAI APC40 MKII
AKAI_APC40_MK2_MAPPINGS = {
    "cc_mappings": {
        "master_fader": CCMapping(14, 0, "Master Fader", 0, 127, "linear", "master"),
        "fader_1": CCMapping(48, 0, "Fader 1", 0, 127, "linear", "drums_bus"),
        "fader_2": CCMapping(49, 0, "Fader 2", 0, 127, "linear", "bass_bus"),
        "fader_3": CCMapping(50, 0, "Fader 3", 0, 127, "linear", "music_bus"),
        "fader_4": CCMapping(51, 0, "Fader 4", 0, 127, "linear", "music_bus"),
        "knob_1": CCMapping(16, 0, "Knob 1", 0, 127, "linear", "drums_bus"),
        "knob_2": CCMapping(17, 0, "Knob 2", 0, 127, "linear", "bass_bus"),
        "knob_3": CCMapping(18, 0, "Knob 3", 0, 127, "linear", "music_bus"),
        "knob_4": CCMapping(19, 0, "Knob 4", 0, 127, "linear", "master"),
        "knob_5": CCMapping(20, 0, "Knob 5", 0, 127, "exponential", None),  # Humanize
        "knob_6": CCMapping(21, 0, "Knob 6", 0, 127, "exponential", None),  # Sidechain
        "knob_7": CCMapping(22, 0, "Knob 7", 0, 127, "exponential", None),  # Reverb
        "knob_8": CCMapping(23, 0, "Knob 8", 0, 127, "exponential", None),  # Delay
    },
    "note_mappings": {
        "clip_launch_1": NoteMapping(53, 0, "Clip 1", "momentary", feedback_enabled=True, led_color=1),
        "clip_launch_2": NoteMapping(54, 0, "Clip 2", "momentary", feedback_enabled=True, led_color=1),
        "clip_launch_3": NoteMapping(55, 0, "Clip 3", "momentary", feedback_enabled=True, led_color=1),
        "clip_launch_4": NoteMapping(56, 0, "Clip 4", "momentary", feedback_enabled=True, led_color=1),
        "scene_launch_1": NoteMapping(82, 0, "Scene 1", "momentary", feedback_enabled=True, led_color=2),
        "scene_launch_2": NoteMapping(83, 0, "Scene 2", "momentary", feedback_enabled=True, led_color=2),
        "scene_launch_3": NoteMapping(84, 0, "Scene 3", "momentary", feedback_enabled=True, led_color=2),
        "scene_launch_4": NoteMapping(85, 0, "Scene 4", "momentary", feedback_enabled=True, led_color=2),
        "panic": NoteMapping(87, 0, "Panic", "momentary", feedback_enabled=True, led_color=2),
        "shift": NoteMapping(98, 0, "Shift", "hold"),
        "bank_select": NoteMapping(99, 0, "Bank", "toggle"),
    }
}

# Mapeos para Pioneer DDJ (mapeo MIDI estándar)
PIONEER_DDJ_MAPPINGS = {
    "cc_mappings": {
        "channel_1_fader": CCMapping(2, 0, "CH1 Fader", 0, 127, "linear", "drums_bus"),
        "channel_2_fader": CCMapping(3, 0, "CH2 Fader", 0, 127, "linear", "bass_bus"),
        "channel_3_fader": CCMapping(4, 0, "CH3 Fader", 0, 127, "linear", "music_bus"),
        "channel_4_fader": CCMapping(5, 0, "CH4 Fader", 0, 127, "linear", "music_bus"),
        "master_fader": CCMapping(6, 0, "Master", 0, 127, "linear", "master"),
        "crossfader": CCMapping(8, 0, "Crossfader", 0, 127, "linear"),
        "eq_high_1": CCMapping(10, 0, "EQ High 1", 0, 127, "linear", "drums_bus"),
        "eq_mid_1": CCMapping(11, 0, "EQ Mid 1", 0, 127, "linear", "drums_bus"),
        "eq_low_1": CCMapping(12, 0, "EQ Low 1", 0, 127, "linear", "drums_bus"),
        "eq_high_2": CCMapping(13, 0, "EQ High 2", 0, 127, "linear", "bass_bus"),
        "eq_mid_2": CCMapping(14, 0, "EQ Mid 2", 0, 127, "linear", "bass_bus"),
        "eq_low_2": CCMapping(15, 0, "EQ Low 2", 0, 127, "linear", "bass_bus"),
        "filter_1": CCMapping(20, 0, "Filter 1", 0, 127, "linear", "drums_bus"),
        "filter_2": CCMapping(21, 0, "Filter 2", 0, 127, "linear", "bass_bus"),
        "gain_1": CCMapping(30, 0, "Gain 1", 0, 127, "exponential"),
        "gain_2": CCMapping(31, 0, "Gain 2", 0, 127, "exponential"),
    },
    "note_mappings": {
        "play_1": NoteMapping(11, 0, "Play 1", "toggle", feedback_enabled=True),
        "play_2": NoteMapping(12, 0, "Play 2", "toggle", feedback_enabled=True),
        "cue_1": NoteMapping(9, 0, "Cue 1", "momentary", feedback_enabled=True),
        "cue_2": NoteMapping(10, 0, "Cue 2", "momentary", feedback_enabled=True),
        "sync_1": NoteMapping(13, 0, "Sync 1", "toggle", feedback_enabled=True),
        "sync_2": NoteMapping(14, 0, "Sync 2", "toggle", feedback_enabled=True),
        "hotcue_1": NoteMapping(20, 0, "Hotcue 1", "momentary", feedback_enabled=True),
        "hotcue_2": NoteMapping(21, 0, "Hotcue 2", "momentary", feedback_enabled=True),
        "hotcue_3": NoteMapping(22, 0, "Hotcue 3", "momentary", feedback_enabled=True),
        "hotcue_4": NoteMapping(23, 0, "Hotcue 4", "momentary", feedback_enabled=True),
    }
}


def get_hardware_mapping(hardware_type: str) -> Dict[str, Any]:
    """
    T166: Obtiene mapeo MIDI completo para controladores soportados.
    
    Args:
        hardware_type: Tipo de hardware ('xone_k2', 'akai_apc40', 'pioneer_ddj', etc.)
    
    Returns:
        Dict con configuración completa de mapeo CC y Note.
        
    Ejemplos:
        >>> get_hardware_mapping('xone_k2')
        >>> get_hardware_mapping('akai_apc40')
    """
    hardware_type = hardware_type.lower().replace(" ", "_")
    
    config_map = {
        "xone_k2": XONE_K2_MAPPINGS,
        "xone:k2": XONE_K2_MAPPINGS,
        "akai_apc40": AKAI_APC40_MK2_MAPPINGS,
        "apc40": AKAI_APC40_MK2_MAPPINGS,
        "apc40_mk2": AKAI_APC40_MK2_MAPPINGS,
        "apc40_mkii": AKAI_APC40_MK2_MAPPINGS,
        "pioneer_ddj": PIONEER_DDJ_MAPPINGS,
        "ddj": PIONEER_DDJ_MAPPINGS,
    }
    
    if hardware_type not in config_map:
        logger.warning(f"[HARDWARE] Tipo no reconocido: {hardware_type}, usando mapeo genérico")
        return XONE_K2_MAPPINGS  # Default fallback
    
    mapping = config_map[hardware_type]
    logger.info(f"[HARDWARE] T166: Mapeo cargado para {hardware_type}")
    
    return {
        "hardware_type": hardware_type,
        "cc_count": len(mapping["cc_mappings"]),
        "note_count": len(mapping["note_mappings"]),
        "mappings": mapping,
        "status": "mapped"
    }


# =============================================================================
# T167: Callbacks Asíncronos para Filtros de Hardware
# =============================================================================

class AsyncFilterController:
    """
    T167: Controlador asíncrono para filtros de hardware.
    
    Liga CC de filtros de hardware a buses de forma asíncrona,
    permitiendo actualizaciones en tiempo real sin bloquear.
    """
    
    def __init__(self):
        self.active_filters: Dict[str, Dict[str, Any]] = {}
        self._lock = asyncio.Lock()
        self._callbacks: Dict[str, List[Callable]] = {}
        
    async def register_filter_callback(
        self, 
        filter_name: str, 
        bus_name: str,
        callback: Callable[[float], Any]
    ) -> bool:
        """Registra un callback asíncrono para un filtro."""
        async with self._lock:
            self.active_filters[filter_name] = {
                "bus": bus_name,
                "current_value": 0.5,
                "target_value": 0.5,
                "smoothing": 0.1,
                "callback": callback
            }
            logger.info(f"[HARDWARE] T167: Filtro {filter_name} ligado a bus {bus_name}")
            return True
    
    async def update_filter_value(self, filter_name: str, cc_value: int) -> bool:
        """Actualiza valor de filtro desde mensaje CC (0-127)."""
        async with self._lock:
            if filter_name not in self.active_filters:
                return False
            
            # Normalizar 0-127 a 0.0-1.0
            normalized = cc_value / 127.0
            self.active_filters[filter_name]["target_value"] = normalized
            
            # Aplicar smoothing asíncrono
            await self._apply_smoothing(filter_name)
            
            return True
    
    async def _apply_smoothing(self, filter_name: str):
        """Aplica smoothing asíncrono al valor del filtro."""
        filter_data = self.active_filters[filter_name]
        current = filter_data["current_value"]
        target = filter_data["target_value"]
        smoothing = filter_data["smoothing"]
        
        # Interpolación exponencial
        new_value = current + (target - current) * smoothing
        filter_data["current_value"] = new_value
        
        # Ejecutar callback si existe
        if filter_data["callback"]:
            try:
                if asyncio.iscoroutinefunction(filter_data["callback"]):
                    await filter_data["callback"](new_value)
                else:
                    filter_data["callback"](new_value)
            except Exception as e:
                logger.error(f"[HARDWARE] Error en callback de filtro: {e}")
    
    async def get_filter_status(self) -> Dict[str, Any]:
        """Retorna estado de todos los filtros activos."""
        async with self._lock:
            return {
                name: {
                    "bus": data["bus"],
                    "value": data["current_value"],
                    "target": data["target_value"]
                }
                for name, data in self.active_filters.items()
            }


# Instancia global del controlador de filtros
_filter_controller = AsyncFilterController()


async def bind_filter_to_bus_async(
    filter_cc: int,
    bus_name: str,
    hardware_type: str = "xone_k2"
) -> Dict[str, Any]:
    """
    T167: Liga un CC de filtro de hardware a un bus asíncronamente.
    
    Args:
        filter_cc: Número de CC del filtro
        bus_name: Nombre del bus objetivo (drums_bus, bass_bus, music_bus, master)
        hardware_type: Tipo de controlador
    
    Returns:
        Dict con estado de la ligadura.
    """
    filter_name = f"filter_{filter_cc}"
    
    # Callback que actualizará el filtro en Live
    async def filter_callback(value: float):
        # Aquí se conectaría con el MCP server para actualizar el filtro
        logger.debug(f"[HARDWARE] T167: Filtro {filter_name} = {value:.3f}")
    
    await _filter_controller.register_filter_callback(filter_name, bus_name, filter_callback)
    
    return {
        "filter_cc": filter_cc,
        "bus_name": bus_name,
        "status": "async_bound",
        "smoothing": 0.1,
        "message": f"Filtro CC{filter_cc} ligado asíncronamente a {bus_name}"
    }


# =============================================================================
# T168: Monitor de Pista vía Hardware
# =============================================================================

class TrackMonitorController:
    """T168: Controla monitor de pista desde hardware."""
    
    def __init__(self):
        self.monitor_states: Dict[int, bool] = {}  # track_index -> bool
        self._lock = threading.Lock()
    
    def toggle_monitor(self, track_index: int) -> Dict[str, Any]:
        """Activa/desactiva monitor de pista específica."""
        with self._lock:
            current_state = self.monitor_states.get(track_index, False)
            new_state = not current_state
            self.monitor_states[track_index] = new_state
            
            logger.info(f"[HARDWARE] T168: Monitor de pista {track_index} = {new_state}")
            
            return {
                "track_index": track_index,
                "monitor_active": new_state,
                "action": "toggle_monitor"
            }
    
    def set_monitor(self, track_index: int, state: bool) -> Dict[str, Any]:
        """Establece estado de monitor explícitamente."""
        with self._lock:
            self.monitor_states[track_index] = state
            
            return {
                "track_index": track_index,
                "monitor_active": state,
                "action": "set_monitor"
            }


_track_monitor = TrackMonitorController()


def toggle_track_monitor(track_index: int) -> Dict[str, Any]:
    """T168: Activa/desactiva monitor de pista desde hardware."""
    return _track_monitor.toggle_monitor(track_index)


# =============================================================================
# T169: MIDI Clock Externo y Tempo Dinámico
# =============================================================================

class MIDIClockSync:
    """
    T169: Sincronización de MIDI Clock externos.
    
    Recibe pulsos de clock MIDI y ajusta el tempo del set dinámicamente.
    """
    
    def __init__(self):
        self.is_running = False
        self.clock_pulses = deque(maxlen=24)  # 24 ppqn
        self.last_pulse_time = 0.0
        self.current_bpm = 120.0
        self._target_bpm = 120.0
        self._smoothing = 0.3
        self._callback: Optional[Callable[[float], None]] = None
        
    def start(self, callback: Optional[Callable[[float], None]] = None):
        """Inicia recepción de MIDI Clock."""
        self.is_running = True
        self._callback = callback
        logger.info("[HARDWARE] T169: MIDI Clock sync iniciado")
        
    def stop(self):
        """Detiene recepción de MIDI Clock."""
        self.is_running = False
        logger.info("[HARDWARE] T169: MIDI Clock sync detenido")
        
    def receive_clock_pulse(self):
        """Procesa un pulso de clock MIDI (llamado 24 veces por negra)."""
        if not self.is_running:
            return
            
        current_time = time.time()
        
        if self.last_pulse_time > 0:
            pulse_interval = current_time - self.last_pulse_time
            
            # Calcular BPM instantáneo (24 pulsos = 1 negra)
            if pulse_interval > 0:
                instant_bpm = 60.0 / (pulse_interval * 24.0)
                
                # Limitar a rango válido
                instant_bpm = max(60.0, min(200.0, instant_bpm))
                
                # Smoothing
                self._target_bpm = instant_bpm
                self.current_bpm += (self._target_bpm - self.current_bpm) * self._smoothing
                
                # Callback para actualizar Live
                if self._callback:
                    self._callback(self.current_bpm)
        
        self.last_pulse_time = current_time
        self.clock_pulses.append(current_time)
    
    def receive_start(self):
        """Procesa mensaje MIDI Start."""
        self.clock_pulses.clear()
        self.last_pulse_time = 0.0
        logger.info("[HARDWARE] T169: MIDI Start recibido")
        
    def receive_stop(self):
        """Procesa mensaje MIDI Stop."""
        logger.info("[HARDWARE] T169: MIDI Stop recibido")
        
    def get_status(self) -> Dict[str, Any]:
        """Retorna estado actual de sincronización."""
        return {
            "running": self.is_running,
            "current_bpm": round(self.current_bpm, 2),
            "target_bpm": round(self._target_bpm, 2),
            "pulse_count": len(self.clock_pulses)
        }


_midi_clock = MIDIClockSync()


def set_midi_clock_callback(callback: Callable[[float], None]):
    """Establece callback para cambios de tempo por MIDI Clock."""
    _midi_clock._callback = callback


def start_midi_clock_sync() -> Dict[str, Any]:
    """T169: Inicia sincronización con MIDI Clock externo."""
    _midi_clock.start()
    return {
        "status": "started",
        "message": "Sincronización MIDI Clock iniciada",
        "ppqn": 24
    }


def stop_midi_clock_sync() -> Dict[str, Any]:
    """Detiene sincronización MIDI Clock."""
    _midi_clock.stop()
    return {
        "status": "stopped",
        "message": "Sincronización MIDI Clock detenida"
    }


def get_midi_clock_status() -> Dict[str, Any]:
    """Obtiene estado de sincronización MIDI Clock."""
    return _midi_clock.get_status()


# =============================================================================
# T170: Gain Staging Mapeado a Fader Master
# =============================================================================

class GainStagingController:
    """
    T170: Controla gain staging desde fader master del controlador.
    
    Mapea el fader master a calibración de gain staging del set.
    """
    
    def __init__(self):
        self.current_value = 0.85  # Valor por defecto (0dB)
        self.target_lufs = -14.0
        self._lock = threading.Lock()
        
    def update_from_fader(self, cc_value: int) -> Dict[str, Any]:
        """
        Actualiza gain staging desde valor CC del fader (0-127).
        
        Mapeo:
        - 0-63: LUFS más bajo (-23 a -14)
        - 64-127: LUFS más alto (-14 a -8)
        """
        with self._lock:
            normalized = cc_value / 127.0
            
            # Mapear a rango de LUFS
            if normalized < 0.5:
                # Streaming range
                self.target_lufs = -23.0 + (normalized * 2 * 9.0)  # -23 a -14
            else:
                # Club range  
                self.target_lufs = -14.0 + ((normalized - 0.5) * 2 * 6.0)  # -14 a -8
            
            self.current_value = normalized
            
            logger.info(f"[HARDWARE] T170: Gain staging ajustado a {self.target_lufs:.1f} LUFS")
            
            return {
                "cc_value": cc_value,
                "normalized": round(normalized, 3),
                "target_lufs": round(self.target_lufs, 1),
                "action": "gain_staging_update"
            }
    
    def get_status(self) -> Dict[str, Any]:
        """Retorna estado actual de gain staging."""
        return {
            "current_value": round(self.current_value, 3),
            "target_lufs": round(self.target_lufs, 1),
            "range": "streaming" if self.target_lufs <= -14 else "club"
        }


_gain_staging = GainStagingController()


def update_gain_staging_from_fader(cc_value: int) -> Dict[str, Any]:
    """
    T170: Actualiza calibración de gain staging desde fader master.
    
    Args:
        cc_value: Valor CC del fader (0-127)
    
    Returns:
        Dict con estado actualizado de gain staging.
    """
    return _gain_staging.update_from_fader(cc_value)


def get_gain_staging_status() -> Dict[str, Any]:
    """Obtiene estado actual de gain staging."""
    return _gain_staging.get_status()


# =============================================================================
# T171: Disparo de Fills desde Pads de Drum Rack
# =============================================================================

class DrumPadController:
    """
    T171: Controla disparo de fills desde pads del Drum Rack.
    
    Mapea pads físicos del controlador a fills de patrón.
    """
    
    FILL_PATTERNS = {
        "fill_1": {"density": "sparse", "section": "drop"},
        "fill_2": {"density": "medium", "section": "build"},
        "fill_3": {"density": "heavy", "section": "drop"},
        "fill_4": {"density": "sparse", "section": "break"},
    }
    
    def __init__(self):
        self.active_fills: Dict[str, bool] = {}
        self._callback: Optional[Callable[[str, str, str], None]] = None
        
    def register_fill_callback(self, callback: Callable[[str, str, str], None]):
        """Registra callback para disparo de fills."""
        self._callback = callback
        
    def trigger_fill(self, pad_number: int) -> Dict[str, Any]:
        """
        Dispara un fill desde un pad específico.
        
        Args:
            pad_number: Número de pad (1-8)
        """
        fill_name = f"fill_{pad_number}"
        
        if fill_name not in self.FILL_PATTERNS:
            return {
                "pad": pad_number,
                "status": "error",
                "message": f"Pad {pad_number} no mapeado"
            }
        
        pattern = self.FILL_PATTERNS[fill_name]
        self.active_fills[fill_name] = True
        
        # Ejecutar callback si existe
        if self._callback:
            try:
                self._callback(fill_name, pattern["density"], pattern["section"])
            except Exception as e:
                logger.error(f"[HARDWARE] T171: Error en callback de fill: {e}")
        
        logger.info(f"[HARDWARE] T171: Fill disparado desde pad {pad_number} ({pattern['density']})")
        
        return {
            "pad": pad_number,
            "fill_name": fill_name,
            "density": pattern["density"],
            "section": pattern["section"],
            "status": "triggered"
        }
    
    def get_pad_mappings(self) -> Dict[str, Any]:
        """Retorna mapeo de todos los pads."""
        return {
            "pads": {
                str(i): self.FILL_PATTERNS.get(f"fill_{i}", {"density": "none"})
                for i in range(1, 9)
            }
        }


_drum_pad_controller = DrumPadController()


def trigger_fill_from_pad(pad_number: int) -> Dict[str, Any]:
    """
    T171: Dispara fill de patrón desde pad del Drum Rack.
    
    Args:
        pad_number: Número de pad (1-8)
    
    Returns:
        Dict con información del fill disparado.
    """
    return _drum_pad_controller.trigger_fill(pad_number)


def register_fill_callback(callback: Callable[[str, str, str], None]):
    """Registra callback para fills desde pads."""
    _drum_pad_controller.register_fill_callback(callback)


# =============================================================================
# T172: Botón de Pánico (apaga delays y reverbs)
# =============================================================================

class PanicButtonController:
    """
    T172: Botón de pánico para emergencias en vivo.
    
    Apaga inmediatamente delays, reverbs y efectos de cola.
    """
    
    def __init__(self):
        self.is_active = False
        self._callback: Optional[Callable[[], None]] = None
        self.affected_tracks: List[str] = ["music_bus", "vocal_bus", "atmos_bus"]
        
    def register_callback(self, callback: Callable[[], None]):
        """Registra callback para activación de pánico."""
        self._callback = callback
        
    def trigger_panic(self) -> Dict[str, Any]:
        """
        Activa modo pánico: apaga todos los efectos de cola.
        
        Esto incluye:
        - Reverbs (envíos a buses de retorno)
        - Delays (taps de delay)
        - Efectos con cola larga
        """
        self.is_active = True
        
        # Ejecutar callback si existe
        if self._callback:
            try:
                self._callback()
            except Exception as e:
                logger.error(f"[HARDWARE] T172: Error en callback de pánico: {e}")
        
        logger.warning("[HARDWARE] T172: BOTÓN DE PÁNICO ACTIVADO - Efectos detenidos")
        
        return {
            "status": "PANIC_ACTIVATED",
            "message": "Delays y reverbs detenidos",
            "affected_tracks": self.affected_tracks,
            "timestamp": time.time()
        }
    
    def release_panic(self) -> Dict[str, Any]:
        """Libera modo pánico, restaura efectos gradualmente."""
        self.is_active = False
        
        logger.info("[HARDWARE] T172: Modo pánico liberado")
        
        return {
            "status": "released",
            "message": "Efectos restaurados gradualmente"
        }
    
    def get_status(self) -> Dict[str, Any]:
        """Retorna estado del botón de pánico."""
        return {
            "active": self.is_active,
            "affected_tracks": self.affected_tracks
        }


_panic_controller = PanicButtonController()


def trigger_panic_button() -> Dict[str, Any]:
    """
    T172: Activa botón de pánico desde hardware.
    
    Apaga inmediatamente todos los delays y reverbs del set.
    
    Returns:
        Dict con estado de activación.
    """
    return _panic_controller.trigger_panic()


def release_panic_button() -> Dict[str, Any]:
    """Libera modo pánico."""
    return _panic_controller.release_panic()


def register_panic_callback(callback: Callable[[], None]):
    """Registra callback para botón de pánico."""
    _panic_controller.register_callback(callback)


# =============================================================================
# T173: Feedback Luminoso al Hardware
# =============================================================================

class HardwareFeedbackController:
    """
    T173: Controla feedback luminoso hacia el hardware.
    
    Envia mensajes MIDI de vuelta al controlador para:
    - LEDs de pads
    - Anillos LED de knobs
    - Displays
    """
    
    LED_COLORS_APC40 = {
        "off": 0,
        "green": 1,
        "green_blink": 2,
        "red": 3,
        "red_blink": 4,
        "yellow": 5,
        "yellow_blink": 6,
        "orange": 7,
    }
    
    def __init__(self):
        self.output_port: Optional[Any] = None
        self._lock = threading.Lock()
        self.active_leds: Dict[str, int] = {}
        
    def set_output_port(self, port: Any):
        """Establece puerto MIDI de salida."""
        self.output_port = port
        
    def send_pad_led(self, note: int, color: str, channel: int = 0) -> bool:
        """
        Envía mensaje de LED a un pad.
        
        Args:
            note: Número de nota del pad
            color: Nombre del color (ver LED_COLORS_APC40)
            channel: Canal MIDI
        """
        if not MIDO_AVAILABLE or not self.output_port:
            return False
        
        try:
            color_value = self.LED_COLORS_APC40.get(color, 0)
            msg = Message('note_on', note=note, velocity=color_value, channel=channel)
            self.output_port.send(msg)
            
            with self._lock:
                self.active_leds[f"pad_{note}"] = color_value
                
            return True
        except Exception as e:
            logger.error(f"[HARDWARE] T173: Error enviando LED: {e}")
            return False
    
    def send_ring_led(self, cc: int, value: int, channel: int = 0) -> bool:
        """Envía valor a anillo LED de knob."""
        if not MIDO_AVAILABLE or not self.output_port:
            return False
        
        try:
            msg = Message('control_change', control=cc, value=value, channel=channel)
            self.output_port.send(msg)
            return True
        except Exception as e:
            logger.error(f"[HARDWARE] T173: Error enviando ring LED: {e}")
            return False
    
    def blink_pattern(self, note: int, color: str, duration_ms: int = 500) -> bool:
        """
        Hace parpadear un LED en patrón.
        
        Útil para indicar estados como exportación de stems.
        """
        if not MIDO_AVAILABLE or not self.output_port:
            return False
        
        def blink_thread():
            start_time = time.time()
            while (time.time() - start_time) * 1000 < duration_ms:
                self.send_pad_led(note, color)
                time.sleep(0.1)
                self.send_pad_led(note, "off")
                time.sleep(0.1)
            # Estado final
            self.send_pad_led(note, color)
        
        threading.Thread(target=blink_thread, daemon=True).start()
        return True
    
    def indicate_export_active(self) -> Dict[str, Any]:
        """
        T173: Indica exportación activa con parpadeo de LEDs.
        
        Usa LEDs de escenas para mostrar progreso de exportación.
        """
        # Parpadear todas las escenas secuencialmente
        scene_notes = [82, 83, 84, 85]  # APC40 scene launch buttons
        
        def export_indicator():
            for note in scene_notes:
                self.send_pad_led(note, "green_blink")
                time.sleep(0.2)
            # Todas encienden al completar
            for note in scene_notes:
                self.send_pad_led(note, "green")
            time.sleep(1.0)
            # Reset
            for note in scene_notes:
                self.send_pad_led(note, "off")
        
        threading.Thread(target=export_indicator, daemon=True).start()
        
        logger.info("[HARDWARE] T173: Feedback de exportación activado")
        
        return {
            "status": "export_indicated",
            "led_pattern": "sequential_blink",
            "duration_ms": 2000
        }


_feedback_controller = HardwareFeedbackController()


def set_feedback_output_port(port: Any):
    """Establece puerto de salida para feedback."""
    _feedback_controller.set_output_port(port)


def indicate_export_on_hardware() -> Dict[str, Any]:
    """
    T173: Activa indicación visual de exportación en hardware.
    
    Returns:
        Dict con estado de la indicación.
    """
    return _feedback_controller.indicate_export_active()


def send_pad_led_feedback(note: int, color: str, channel: int = 0) -> bool:
    """Envía feedback de LED a pad específico."""
    return _feedback_controller.send_pad_led(note, color, channel)


# =============================================================================
# T174: CPU Load en Display/LED Ring
# =============================================================================

class CPUUsageMonitor:
    """
    T174: Monitorea CPU y envía a display/LED ring del hardware.
    
    Detecta carga de CPU desde Live y la muestra en:
    - Displays de 7 segmentos
    - LED rings de knobs
    - LEDs de nivel
    """
    
    def __init__(self):
        self.current_load = 0.0
        self._monitoring = False
        self._thread: Optional[threading.Thread] = None
        
    def start_monitoring(self, interval_ms: int = 500):
        """Inicia monitoreo de CPU."""
        if self._monitoring:
            return
            
        self._monitoring = True
        
        def monitor_loop():
            while self._monitoring:
                # Simular lectura de CPU de Live
                # En implementación real, esto consultaría Live API
                self.current_load = self._get_live_cpu_load()
                
                # Enviar a hardware
                self._send_to_hardware(self.current_load)
                
                time.sleep(interval_ms / 1000.0)
        
        self._thread = threading.Thread(target=monitor_loop, daemon=True)
        self._thread.start()
        
        logger.info("[HARDWARE] T174: Monitoreo de CPU iniciado")
        
    def stop_monitoring(self):
        """Detiene monitoreo de CPU."""
        self._monitoring = False
        
    def _get_live_cpu_load(self) -> float:
        """Obtiene carga de CPU desde Live (simulado)."""
        # En implementación real, consultaría Live API
        # Por ahora, valor simulado
        import random
        return random.uniform(20.0, 60.0)
    
    def _send_to_hardware(self, load: float):
        """Envía valor de CPU a display/LED ring."""
        # Mapear 0-100% a valor MIDI 0-127
        midi_value = int((load / 100.0) * 127)
        
        # Enviar a ring LED del knob master (CC 14 en APC40)
        if MIDO_AVAILABLE and _feedback_controller.output_port:
            try:
                msg = Message('control_change', control=14, value=midi_value)
                _feedback_controller.output_port.send(msg)
            except Exception as e:
                logger.error(f"[HARDWARE] T174: Error enviando CPU load: {e}")
    
    def get_current_load(self) -> Dict[str, Any]:
        """Retorna carga actual de CPU."""
        return {
            "cpu_load_percent": round(self.current_load, 1),
            "monitoring": self._monitoring,
            "midi_value": int((self.current_load / 100.0) * 127)
        }


_cpu_monitor = CPUUsageMonitor()


def start_cpu_monitoring(interval_ms: int = 500) -> Dict[str, Any]:
    """
    T174: Inicia monitoreo de CPU en display/LED ring.
    
    Args:
        interval_ms: Intervalo de actualización en milisegundos
    
    Returns:
        Dict con estado del monitoreo.
    """
    _cpu_monitor.start_monitoring(interval_ms)
    return {
        "status": "monitoring_started",
        "interval_ms": interval_ms,
        "display_target": "led_ring"
    }


def stop_cpu_monitoring() -> Dict[str, Any]:
    """Detiene monitoreo de CPU."""
    _cpu_monitor.stop_monitoring()
    return {
        "status": "monitoring_stopped"
    }


def get_cpu_load() -> Dict[str, Any]:
    """Obtiene carga actual de CPU."""
    return _cpu_monitor.get_current_load()


# =============================================================================
# T175: Disparo de Scene desde Controlador
# =============================================================================

class SceneTriggerController:
    """
    T175: Controla disparo de scenes desde controlador hardware.
    
    Incluye cuantización global para sincronización perfecta.
    """
    
    QUANTIZATION_MODES = {
        "none": 0,      # Inmediato
        "8th": 0.5,     # Corchea
        "4th": 1.0,     # Negra
        "2nd": 2.0,     # Blanca
        "1bar": 4.0,    # Compás
        "2bar": 8.0,    # 2 compases
    }
    
    def __init__(self):
        self.quantization = "1bar"
        self._callback: Optional[Callable[[int, str], None]] = None
        
    def register_callback(self, callback: Callable[[int, str], None]):
        """Registra callback para disparo de scene."""
        self._callback = callback
        
    def trigger_scene(self, scene_index: int, quantization: Optional[str] = None) -> Dict[str, Any]:
        """
        Dispara una scene específica.
        
        Args:
            scene_index: Índice de la scene (0-based)
            quantization: Modo de cuantización (usa global si None)
        """
        quant = quantization or self.quantization
        quant_beats = self.QUANTIZATION_MODES.get(quant, 4.0)
        
        # Ejecutar callback si existe
        if self._callback:
            try:
                self._callback(scene_index, quant)
            except Exception as e:
                logger.error(f"[HARDWARE] T175: Error en callback de scene: {e}")
        
        logger.info(f"[HARDWARE] T175: Scene {scene_index} disparada (quant: {quant})")
        
        return {
            "scene_index": scene_index,
            "quantization": quant,
            "quantization_beats": quant_beats,
            "status": "triggered"
        }
    
    def set_global_quantization(self, mode: str) -> Dict[str, Any]:
        """Establece cuantización global."""
        if mode not in self.QUANTIZATION_MODES:
            return {
                "status": "error",
                "message": f"Modo no válido. Opciones: {list(self.QUANTIZATION_MODES.keys())}"
            }
        
        self.quantization = mode
        
        return {
            "quantization": mode,
            "beats": self.QUANTIZATION_MODES[mode],
            "status": "set"
        }
    
    def get_quantization_modes(self) -> Dict[str, Any]:
        """Retorna modos de cuantización disponibles."""
        return {
            "current": self.quantization,
            "available": self.QUANTIZATION_MODES
        }


_scene_controller = SceneTriggerController()


def trigger_scene_from_hardware(scene_index: int, quantization: Optional[str] = None) -> Dict[str, Any]:
    """
    T175: Dispara scene específica desde controlador.
    
    Args:
        scene_index: Índice de la scene (0-based)
        quantization: Modo de cuantización (optional)
    
    Returns:
        Dict con información del disparo.
    """
    return _scene_controller.trigger_scene(scene_index, quantization)


def set_scene_quantization(mode: str) -> Dict[str, Any]:
    """Establece cuantización global para scenes."""
    return _scene_controller.set_global_quantization(mode)


def register_scene_callback(callback: Callable[[int, str], None]):
    """Registra callback para disparo de scenes."""
    _scene_controller.register_callback(callback)


# =============================================================================
# T176: Performance Mode - Faders manejan Stems Automáticos
# =============================================================================

class PerformanceModeController:
    """
    T176: Modo Performance donde faders controlan stems automáticamente.
    
    Asignación dinámica de faders a stems según contexto musical.
    """
    
    def __init__(self):
        self.active = False
        self.fader_assignments: Dict[int, Dict[str, Any]] = {}
        self.current_layout = "default"
        
        # Layouts predefinidos
        self.LAYOUTS = {
            "default": {
                0: {"name": "Drums", "bus": "drums_bus", "color": "red"},
                1: {"name": "Bass", "bus": "bass_bus", "color": "orange"},
                2: {"name": "Music", "bus": "music_bus", "color": "green"},
                3: {"name": "Master", "bus": "master", "color": "yellow"},
            },
            "dj": {
                0: {"name": "Deck A", "bus": "deck_a", "color": "cyan"},
                1: {"name": "Deck B", "bus": "deck_b", "color": "magenta"},
                2: {"name": "FX", "bus": "fx_bus", "color": "yellow"},
                3: {"name": "Master", "bus": "master", "color": "white"},
            },
            "live": {
                0: {"name": "Kick", "bus": "kick_bus", "color": "red"},
                1: {"name": "Snare", "bus": "snare_bus", "color": "orange"},
                2: {"name": "Synth", "bus": "synth_bus", "color": "green"},
                3: {"name": "Vocals", "bus": "vocal_bus", "color": "blue"},
            }
        }
        
    def activate(self, layout: str = "default") -> Dict[str, Any]:
        """
        Activa modo performance con layout específico.
        
        Args:
            layout: Nombre del layout (default, dj, live)
        """
        if layout not in self.LAYOUTS:
            return {
                "status": "error",
                "message": f"Layout no existe. Opciones: {list(self.LAYOUTS.keys())}"
            }
        
        self.active = True
        self.current_layout = layout
        self.fader_assignments = self.LAYOUTS[layout].copy()
        
        # Enviar feedback a hardware
        self._send_layout_feedback()
        
        logger.info(f"[HARDWARE] T176: Performance Mode activado con layout '{layout}'")
        
        return {
            "status": "activated",
            "layout": layout,
            "fader_count": len(self.fader_assignments),
            "assignments": self.fader_assignments
        }
    
    def deactivate(self) -> Dict[str, Any]:
        """Desactiva modo performance."""
        self.active = False
        
        logger.info("[HARDWARE] T176: Performance Mode desactivado")
        
        return {
            "status": "deactivated"
        }
    
    def _send_layout_feedback(self):
        """Envía feedback visual del layout al hardware."""
        if not _feedback_controller.output_port:
            return
        
        # Encender LEDs según layout
        colors = {"red": 3, "orange": 7, "green": 1, "yellow": 5, "cyan": 16, "magenta": 17, "blue": 33, "white": 1}
        
        for fader_idx, assignment in self.fader_assignments.items():
            color_name = assignment.get("color", "green")
            color_value = colors.get(color_name, 1)
            
            # Mapear a LEDs de escenas
            scene_note = 82 + fader_idx
            _feedback_controller.send_pad_led(scene_note, self._get_color_name(color_value))
    
    def _get_color_name(self, value: int) -> str:
        """Convierte valor de color a nombre."""
        color_map = {0: "off", 1: "green", 3: "red", 5: "yellow", 7: "orange"}
        return color_map.get(value, "green")
    
    def handle_fader_move(self, fader_index: int, cc_value: int) -> Dict[str, Any]:
        """
        Procesa movimiento de fader en modo performance.
        
        Args:
            fader_index: Índice del fader (0-based)
            cc_value: Valor CC (0-127)
        """
        if not self.active:
            return {"status": "inactive", "message": "Performance Mode no está activo"}
        
        if fader_index not in self.fader_assignments:
            return {"status": "error", "message": f"Fader {fader_index} no asignado"}
        
        assignment = self.fader_assignments[fader_index]
        normalized = cc_value / 127.0
        
        return {
            "fader_index": fader_index,
            "assignment": assignment,
            "cc_value": cc_value,
            "normalized": round(normalized, 3),
            "status": "handled"
        }
    
    def get_status(self) -> Dict[str, Any]:
        """Retorna estado del modo performance."""
        return {
            "active": self.active,
            "layout": self.current_layout,
            "assignments": self.fader_assignments
        }


_performance_controller = PerformanceModeController()


def activate_performance_mode(layout: str = "default") -> Dict[str, Any]:
    """
    T176: Activa Performance Mode con faders manejando stems.
    
    Args:
        layout: Layout de asignación (default, dj, live)
    
    Returns:
        Dict con estado del modo performance.
    """
    return _performance_controller.activate(layout)


def deactivate_performance_mode() -> Dict[str, Any]:
    """Desactiva modo performance."""
    return _performance_controller.deactivate()


def handle_performance_fader(fader_index: int, cc_value: int) -> Dict[str, Any]:
    """Procesa movimiento de fader en modo performance."""
    return _performance_controller.handle_fader_move(fader_index, cc_value)


def get_performance_status() -> Dict[str, Any]:
    """Obtiene estado del modo performance."""
    return _performance_controller.get_status()


# =============================================================================
# T177: Humanize como Knob Macro
# =============================================================================

class HumanizeMacroController:
    """
    T177: Mapea humanize_set como knob macro para control orgánico.
    
    Permite incrementar "caos orgánico" gradualmente desde hardware.
    """
    
    def __init__(self):
        self.intensity = 0.0
        self._callback: Optional[Callable[[float], None]] = None
        
    def register_callback(self, callback: Callable[[float], None]):
        """Registra callback para cambios de intensidad."""
        self._callback = callback
        
    def update_from_knob(self, cc_value: int) -> Dict[str, Any]:
        """
        Actualiza intensidad de humanización desde knob.
        
        Args:
            cc_value: Valor CC del knob (0-127)
        """
        # Mapear 0-127 a 0.0-1.0
        self.intensity = cc_value / 127.0
        
        # Ejecutar callback si existe
        if self._callback:
            try:
                self._callback(self.intensity)
            except Exception as e:
                logger.error(f"[HARDWARE] T177: Error en callback humanize: {e}")
        
        # Clasificar nivel
        if self.intensity < 0.3:
            level = "subtle"
        elif self.intensity < 0.6:
            level = "medium"
        else:
            level = "extreme"
        
        logger.info(f"[HARDWARE] T177: Humanize = {self.intensity:.2f} ({level})")
        
        return {
            "cc_value": cc_value,
            "intensity": round(self.intensity, 3),
            "level": level,
            "status": "updated"
        }
    
    def get_status(self) -> Dict[str, Any]:
        """Retorna estado actual de humanización."""
        return {
            "intensity": round(self.intensity, 3),
            "cc_range": f"0-127 -> 0.0-1.0"
        }


_humanize_macro = HumanizeMacroController()


def update_humanize_from_knob(cc_value: int) -> Dict[str, Any]:
    """
    T177: Actualiza humanización desde knob macro.
    
    Args:
        cc_value: Valor CC del knob (0-127)
    
    Returns:
        Dict con nivel de humanización.
    """
    return _humanize_macro.update_from_knob(cc_value)


def register_humanize_callback(callback: Callable[[float], None]):
    """Registra callback para cambios de humanización."""
    _humanize_macro.register_callback(callback)


def get_humanize_macro_status() -> Dict[str, Any]:
    """Obtiene estado del macro de humanización."""
    return _humanize_macro.get_status()


# =============================================================================
# T178: Detección de Silencio y Track de Respaldo
# =============================================================================

class SilenceDetector:
    """
    T178: Detecta silencio prolongado y auto-lanza track de respaldo.
    
    Útil para recuperación de emergencia en vivo.
    """
    
    def __init__(self):
        self.silence_threshold_db = -60.0
        self.silence_duration_ms = 3000  # 3 segundos
        self.is_monitoring = False
        self._silence_start: Optional[float] = None
        self._monitor_thread: Optional[threading.Thread] = None
        self._callback: Optional[Callable[[], None]] = None
        
    def register_callback(self, callback: Callable[[], None]):
        """Registra callback para detección de silencio."""
        self._callback = callback
        
    def start_monitoring(self, threshold_db: float = -60.0, duration_ms: int = 3000):
        """
        Inicia monitoreo de silencio.
        
        Args:
            threshold_db: Umbral en dB para considerar silencio
            duration_ms: Duración mínima en ms para activar respaldo
        """
        self.silence_threshold_db = threshold_db
        self.silence_duration_ms = duration_ms
        self.is_monitoring = True
        
        def monitor_loop():
            while self.is_monitoring:
                # Simular lectura de nivel desde Live
                current_db = self._get_audio_level()
                
                if current_db < self.silence_threshold_db:
                    if self._silence_start is None:
                        self._silence_start = time.time()
                    else:
                        elapsed_ms = (time.time() - self._silence_start) * 1000
                        if elapsed_ms >= self.silence_duration_ms:
                            self._trigger_backup()
                else:
                    self._silence_start = None
                
                time.sleep(0.1)  # Chequeo cada 100ms
        
        self._monitor_thread = threading.Thread(target=monitor_loop, daemon=True)
        self._monitor_thread.start()
        
        logger.info(f"[HARDWARE] T178: Detección de silencio iniciada ({threshold_db}dB, {duration_ms}ms)")
        
    def stop_monitoring(self):
        """Detiene monitoreo de silencio."""
        self.is_monitoring = False
        
    def _get_audio_level(self) -> float:
        """Obtiene nivel de audio desde Live (simulado)."""
        # En implementación real, consultaría Live API
        import random
        return random.uniform(-30.0, -10.0)  # Simulación
        
    def _trigger_backup(self):
        """Activa track de respaldo."""
        logger.warning("[HARDWARE] T178: SILENCIO DETECTADO - Activando track de respaldo")
        
        if self._callback:
            try:
                self._callback()
            except Exception as e:
                logger.error(f"[HARDWARE] T178: Error en callback de respaldo: {e}")
    
    def get_status(self) -> Dict[str, Any]:
        """Retorna estado del detector."""
        return {
            "monitoring": self.is_monitoring,
            "threshold_db": self.silence_threshold_db,
            "duration_ms": self.silence_duration_ms,
            "silence_detected": self._silence_start is not None
        }


_silence_detector = SilenceDetector()


def start_silence_detection(threshold_db: float = -60.0, duration_ms: int = 3000) -> Dict[str, Any]:
    """
    T178: Inicia detección de silencio prolongado.
    
    Args:
        threshold_db: Umbral en dB
        duration_ms: Duración mínima en ms
    
    Returns:
        Dict con estado del monitoreo.
    """
    _silence_detector.start_monitoring(threshold_db, duration_ms)
    return {
        "status": "monitoring_started",
        "threshold_db": threshold_db,
        "duration_ms": duration_ms,
        "action_on_silence": "trigger_backup_track"
    }


def stop_silence_detection() -> Dict[str, Any]:
    """Detiene detección de silencio."""
    _silence_detector.stop_monitoring()
    return {
        "status": "monitoring_stopped"
    }


def register_silence_callback(callback: Callable[[], None]):
    """Registra callback para detección de silencio."""
    _silence_detector.register_callback(callback)


def get_silence_detector_status() -> Dict[str, Any]:
    """Obtiene estado del detector de silencio."""
    return _silence_detector.get_status()


# =============================================================================
# T179: Nudging Asíncrono para Corrección de Fase
# =============================================================================

class AsyncNudgeController:
    """
    T179: Permite nudging asíncrono para corrección de fase.
    
    Ajustes micro-temporales sin afectar el tempo global.
    """
    
    def __init__(self):
        self.nudge_amount_ms = 0.0
        self._lock = threading.Lock()
        self._callbacks: List[Callable[[float], None]] = []
        
    def register_callback(self, callback: Callable[[float], None]):
        """Registra callback para nudging."""
        self._callbacks.append(callback)
        
    def nudge_forward(self, ms: float) -> Dict[str, Any]:
        """
        Nudge hacia adelante (acelerar momentáneamente).
        
        Args:
            ms: Milisegundos de nudge (positivo = adelante)
        """
        with self._lock:
            self.nudge_amount_ms = ms
            
            # Ejecutar callbacks
            for callback in self._callbacks:
                try:
                    callback(ms)
                except Exception as e:
                    logger.error(f"[HARDWARE] T179: Error en callback de nudge: {e}")
            
            logger.info(f"[HARDWARE] T179: Nudge forward {ms}ms")
            
            return {
                "direction": "forward",
                "amount_ms": ms,
                "samples_48k": int(ms * 48),  # Samples a 48kHz
                "status": "applied"
            }
    
    def nudge_backward(self, ms: float) -> Dict[str, Any]:
        """
        Nudge hacia atrás (atrasar momentáneamente).
        
        Args:
            ms: Milisegundos de nudge (positivo = atrás)
        """
        return self.nudge_forward(-ms)
    
    def reset_nudge(self) -> Dict[str, Any]:
        """Resetea nudge a cero."""
        with self._lock:
            self.nudge_amount_ms = 0.0
            
            return {
                "nudge_ms": 0.0,
                "status": "reset"
            }
    
    def get_status(self) -> Dict[str, Any]:
        """Retorna estado de nudging."""
        return {
            "current_nudge_ms": self.nudge_amount_ms,
            "callbacks_registered": len(self._callbacks)
        }


_nudge_controller = AsyncNudgeController()


def apply_nudge_forward(ms: float) -> Dict[str, Any]:
    """
    T179: Aplica nudging asíncrono hacia adelante.
    
    Args:
        ms: Milisegundos de ajuste
    
    Returns:
        Dict con información del nudge.
    """
    return _nudge_controller.nudge_forward(ms)


def apply_nudge_backward(ms: float) -> Dict[str, Any]:
    """Aplica nudging hacia atrás."""
    return _nudge_controller.nudge_backward(ms)


def reset_nudge() -> Dict[str, Any]:
    """Resetea nudging a cero."""
    return _nudge_controller.reset_nudge()


def register_nudge_callback(callback: Callable[[float], None]):
    """Registra callback para nudging."""
    _nudge_controller.register_callback(callback)


def get_nudge_status() -> Dict[str, Any]:
    """Obtiene estado de nudging."""
    return _nudge_controller.get_status()


# =============================================================================
# T180: Macros de Visualización
# =============================================================================

class VisualizationMacros:
    """
    T180: Macros de visualización para la sesión.
    
    Incluye indicadores visuales integrados con el hardware.
    """
    
    def __init__(self):
        self.macros: Dict[str, Callable[[], None]] = {}
        self._register_default_macros()
        
    def _register_default_macros(self):
        """Registra macros por defecto."""
        self.macros = {
            "strobe_beat": self._strobe_on_beat,
            "level_meter": self._level_meter_display,
            "peak_indicator": self._peak_indicator,
            "recording_active": self._recording_indicator,
            "midi_clock_sync": self._midi_sync_indicator,
        }
    
    def _strobe_on_beat(self):
        """Macro: Strobe sincronizado con beat."""
        if _feedback_controller.output_port:
            # Parpadear todos los pads en rojo
            for i in range(8):
                note = 53 + i
                _feedback_controller.send_pad_led(note, "red")
                time.sleep(0.05)
                _feedback_controller.send_pad_led(note, "off")
    
    def _level_meter_display(self):
        """Macro: Medidor de nivel en LEDs."""
        if _feedback_controller.output_port:
            # Simular medidor
            levels = [20, 40, 60, 80, 100, 80, 60, 40]
            for i, level in enumerate(levels):
                note = 53 + i
                if level > 80:
                    color = "red"
                elif level > 50:
                    color = "yellow"
                else:
                    color = "green"
                _feedback_controller.send_pad_led(note, color)
    
    def _peak_indicator(self):
        """Macro: Indicador de pico."""
        if _feedback_controller.output_port:
            # Parpadear rápido en rojo
            for _ in range(4):
                for i in range(8):
                    _feedback_controller.send_pad_led(53 + i, "red")
                time.sleep(0.1)
                for i in range(8):
                    _feedback_controller.send_pad_led(53 + i, "off")
                time.sleep(0.1)
    
    def _recording_indicator(self):
        """Macro: Indicador de grabación activa."""
        if _feedback_controller.output_port:
            # LED rojo parpadeante lento
            for _ in range(10):
                _feedback_controller.send_pad_led(82, "red_blink")
                time.sleep(0.5)
    
    def _midi_sync_indicator(self):
        """Macro: Indicador de sync MIDI."""
        if _feedback_controller.output_port:
            # LED verde cuando sync está activo
            _feedback_controller.send_pad_led(83, "green")
    
    def trigger_macro(self, macro_name: str) -> Dict[str, Any]:
        """
        Dispara una macro de visualización.
        
        Args:
            macro_name: Nombre de la macro
        """
        if macro_name not in self.macros:
            return {
                "status": "error",
                "message": f"Macro no existe. Opciones: {list(self.macros.keys())}"
            }
        
        # Ejecutar en thread separado para no bloquear
        def run_macro():
            try:
                self.macros[macro_name]()
            except Exception as e:
                logger.error(f"[HARDWARE] T180: Error en macro {macro_name}: {e}")
        
        threading.Thread(target=run_macro, daemon=True).start()
        
        logger.info(f"[HARDWARE] T180: Macro '{macro_name}' disparada")
        
        return {
            "macro": macro_name,
            "status": "triggered"
        }
    
    def get_available_macros(self) -> Dict[str, Any]:
        """Retorna macros disponibles."""
        return {
            "available_macros": list(self.macros.keys()),
            "descriptions": {
                "strobe_beat": "Strobe rojo sincronizado",
                "level_meter": "Medidor de nivel en LEDs",
                "peak_indicator": "Indicador de pico (clip)",
                "recording_active": "Grabación activa (parpadeo)",
                "midi_clock_sync": "Sync MIDI activo"
            }
        }


_visualization_macros = VisualizationMacros()


def trigger_visualization_macro(macro_name: str) -> Dict[str, Any]:
    """
    T180: Dispara macro de visualización.
    
    Args:
        macro_name: Nombre de la macro
    
    Returns:
        Dict con estado de la macro.
    """
    return _visualization_macros.trigger_macro(macro_name)


def get_visualization_macros() -> Dict[str, Any]:
    """Obtiene lista de macros disponibles."""
    return _visualization_macros.get_available_macros()


# =============================================================================
# Hardware Integration Manager (Clase principal)
# =============================================================================

class HardwareIntegrationManager:
    """
    Manager principal para integración de hardware MIDI.
    
    Coordina todos los controladores T166-T180.
    """
    
    def __init__(self):
        self.config: Optional[HardwareConfig] = None
        self.input_port: Optional[Any] = None
        self.output_port: Optional[Any] = None
        self._running = False
        self._midi_thread: Optional[threading.Thread] = None
        
    def initialize_hardware(self, hardware_type: str, input_port: str = "", output_port: str = "") -> Dict[str, Any]:
        """
        Inicializa hardware completo.
        
        Args:
            hardware_type: Tipo de controlador
            input_port: Nombre del puerto MIDI de entrada
            output_port: Nombre del puerto MIDI de salida
        """
        # Obtener mapeo
        mapping = get_hardware_mapping(hardware_type)
        
        # Crear configuración
        hw_type = HardwareType(hardware_type.replace(":", "_").lower())
        self.config = HardwareConfig(
            hardware_type=hw_type,
            name=hardware_type,
            input_port=input_port,
            output_port=output_port,
            cc_mappings=mapping["mappings"]["cc_mappings"],
            note_mappings=mapping["mappings"]["note_mappings"],
            has_led_feedback=True,
            fader_count=4,
            knob_count=8,
            pad_count=16,
            button_count=20
        )
        
        # Configurar puertos si mido está disponible
        if MIDO_AVAILABLE and input_port and output_port:
            try:
                self.input_port = mido.open_input(input_port)
                self.output_port = mido.open_output(output_port)
                _feedback_controller.set_output_port(self.output_port)
                logger.info(f"[HARDWARE] Puertos MIDI abiertos: {input_port} / {output_port}")
            except Exception as e:
                logger.error(f"[HARDWARE] Error abriendo puertos MIDI: {e}")
        
        return {
            "status": "initialized",
            "hardware": hardware_type,
            "cc_mappings": len(self.config.cc_mappings),
            "note_mappings": len(self.config.note_mappings),
            "midi_available": MIDO_AVAILABLE
        }
    
    def start_midi_listener(self) -> Dict[str, Any]:
        """Inicia listener de mensajes MIDI."""
        if not MIDO_AVAILABLE or not self.input_port:
            return {"status": "error", "message": "MIDI no disponible"}
        
        self._running = True
        
        def midi_loop():
            while self._running:
                for msg in self.input_port.iter_pending():
                    self._handle_midi_message(msg)
                time.sleep(0.001)  # 1ms polling
        
        self._midi_thread = threading.Thread(target=midi_loop, daemon=True)
        self._midi_thread.start()
        
        logger.info("[HARDWARE] Listener MIDI iniciado")
        
        return {
            "status": "listening",
            "port": self.config.input_port if self.config else ""
        }
    
    def _handle_midi_message(self, msg: Any):
        """Procesa mensaje MIDI entrante."""
        if msg.type == 'control_change':
            self._handle_cc(msg.control, msg.value, msg.channel)
        elif msg.type == 'note_on':
            self._handle_note_on(msg.note, msg.velocity, msg.channel)
        elif msg.type == 'clock':
            _midi_clock.receive_clock_pulse()
        elif msg.type == 'start':
            _midi_clock.receive_start()
        elif msg.type == 'stop':
            _midi_clock.receive_stop()
    
    def _handle_cc(self, cc: int, value: int, channel: int):
        """Procesa mensaje CC."""
        # Buscar mapeo correspondiente
        for name, mapping in self.config.cc_mappings.items() if self.config else []:
            if mapping.cc_number == cc and mapping.channel == channel:
                logger.debug(f"[HARDWARE] CC {name}: {value}")
                
                # Ejecutar acciones específicas
                if "gain_staging" in name.lower():
                    update_gain_staging_from_fader(value)
                elif "humanize" in name.lower():
                    update_humanize_from_knob(value)
                elif "filter" in name.lower():
                    asyncio.create_task(_filter_controller.update_filter_value(name, value))
                elif "fader" in name.lower() and _performance_controller.active:
                    # Extraer índice de fader
                    try:
                        idx = int(name.split("_")[1]) - 1
                        handle_performance_fader(idx, value)
                    except:
                        pass
                
                break
    
    def _handle_note_on(self, note: int, velocity: int, channel: int):
        """Procesa mensaje Note On."""
        # Buscar mapeo
        for name, mapping in self.config.note_mappings.items() if self.config else []:
            if mapping.note == note and mapping.channel == channel:
                logger.debug(f"[HARDWARE] Note {name}: vel={velocity}")
                
                # Ejecutar acciones
                if "scene" in name.lower():
                    # Extraer número de scene
                    try:
                        idx = int(name.split("_")[1]) - 1
                        _scene_controller.trigger_scene(idx)
                    except:
                        pass
                elif "panic" in name.lower():
                    trigger_panic_button()
                elif "fill" in name.lower():
                    # Extraer número de pad
                    try:
                        pad = int(name.split("_")[1])
                        _drum_pad_controller.trigger_fill(pad)
                    except:
                        pass
                elif "performance" in name.lower():
                    if velocity > 0:
                        if not _performance_controller.active:
                            activate_performance_mode()
                        else:
                            deactivate_performance_mode()
                
                # Feedback LED
                if mapping.feedback_enabled and velocity > 0:
                    color = self._get_color_for_note(name)
                    _feedback_controller.send_pad_led(note, color, channel)
                
                break
    
    def _get_color_for_note(self, name: str) -> str:
        """Determina color LED según función."""
        if "scene" in name.lower():
            return "green"
        elif "panic" in name.lower():
            return "red"
        elif "fill" in name.lower():
            return "yellow"
        elif "performance" in name.lower():
            return "orange"
        return "green"
    
    def stop(self):
        """Detiene manager de hardware."""
        self._running = False
        if self._midi_thread:
            self._midi_thread.join(timeout=1.0)
        
        if self.input_port:
            self.input_port.close()
        if self.output_port:
            self.output_port.close()
        
        logger.info("[HARDWARE] Manager detenido")


# Instancia global del manager
_hardware_manager = HardwareIntegrationManager()


def initialize_hardware_integration(
    hardware_type: str = "xone_k2",
    input_port: str = "",
    output_port: str = ""
) -> Dict[str, Any]:
    """
    Inicializa integración completa de hardware.
    
    Args:
        hardware_type: Tipo de controlador (xone_k2, akai_apc40, pioneer_ddj)
        input_port: Puerto MIDI de entrada (opcional)
        output_port: Puerto MIDI de salida (opcional)
    
    Returns:
        Dict con estado de inicialización.
    """
    return _hardware_manager.initialize_hardware(hardware_type, input_port, output_port)


def start_hardware_listener() -> Dict[str, Any]:
    """Inicia listener de mensajes MIDI."""
    return _hardware_manager.start_midi_listener()


def stop_hardware_integration() -> Dict[str, Any]:
    """Detiene integración de hardware."""
    _hardware_manager.stop()
    return {"status": "stopped"}


# =============================================================================
# Funciones de conveniencia para MCP
# =============================================================================

def get_complete_hardware_status() -> Dict[str, Any]:
    """
    Obtiene estado completo de toda la integración de hardware.
    
    Returns:
        Dict consolidado con T166-T180.
    """
    return {
        "t166_hardware_mapping": get_hardware_mapping("xone_k2"),
        "t167_filter_bindings": asyncio.run(_filter_controller.get_filter_status()) if asyncio else {},
        "t168_monitor_states": {},  # Estados de monitor
        "t169_midi_clock": get_midi_clock_status(),
        "t170_gain_staging": get_gain_staging_status(),
        "t171_drum_pads": _drum_pad_controller.get_pad_mappings(),
        "t172_panic_status": _panic_controller.get_status(),
        "t173_feedback": {"output_port_set": _feedback_controller.output_port is not None},
        "t174_cpu_monitor": get_cpu_load(),
        "t175_scene_quantization": _scene_controller.get_quantization_modes(),
        "t176_performance_mode": get_performance_status(),
        "t177_humanize_macro": get_humanize_macro_status(),
        "t178_silence_detector": get_silence_detector_status(),
        "t179_nudge_status": get_nudge_status(),
        "t180_visualization_macros": get_visualization_macros(),
        "mido_available": MIDO_AVAILABLE,
        "timestamp": time.time()
    }


# Exportar símbolos principales
__all__ = [
    # T166
    "get_hardware_mapping",
    "HardwareType",
    "CCMapping",
    "NoteMapping",
    # T167
    "bind_filter_to_bus_async",
    "AsyncFilterController",
    # T168
    "toggle_track_monitor",
    "TrackMonitorController",
    # T169
    "start_midi_clock_sync",
    "stop_midi_clock_sync",
    "get_midi_clock_status",
    # T170
    "update_gain_staging_from_fader",
    "get_gain_staging_status",
    # T171
    "trigger_fill_from_pad",
    "register_fill_callback",
    # T172
    "trigger_panic_button",
    "release_panic_button",
    "register_panic_callback",
    # T173
    "indicate_export_on_hardware",
    "send_pad_led_feedback",
    "set_feedback_output_port",
    # T174
    "start_cpu_monitoring",
    "stop_cpu_monitoring",
    "get_cpu_load",
    # T175
    "trigger_scene_from_hardware",
    "set_scene_quantization",
    # T176
    "activate_performance_mode",
    "deactivate_performance_mode",
    "handle_performance_fader",
    # T177
    "update_humanize_from_knob",
    "register_humanize_callback",
    # T178
    "start_silence_detection",
    "stop_silence_detection",
    "register_silence_callback",
    # T179
    "apply_nudge_forward",
    "apply_nudge_backward",
    "register_nudge_callback",
    # T180
    "trigger_visualization_macro",
    "get_visualization_macros",
    # Manager
    "initialize_hardware_integration",
    "start_hardware_listener",
    "stop_hardware_integration",
    "get_complete_hardware_status",
    "HardwareIntegrationManager",
]


if __name__ == "__main__":
    # Test básico del módulo
    print("=" * 60)
    print("Hardware Integration Module - Test T166-T180")
    print("=" * 60)
    
    # T166
    print("\n[T166] Hardware Mapping:")
    mapping = get_hardware_mapping("xone_k2")
    print(f"  CC mappings: {mapping['cc_count']}")
    print(f"  Note mappings: {mapping['note_count']}")
    
    # T169
    print("\n[T169] MIDI Clock:")
    status = start_midi_clock_sync()
    print(f"  Status: {status['status']}")
    print(f"  PPQN: {status['ppqn']}")
    
    # T170
    print("\n[T170] Gain Staging:")
    gain_status = update_gain_staging_from_fader(100)
    print(f"  Target LUFS: {gain_status['target_lufs']}dB")
    
    # T172
    print("\n[T172] Panic Button:")
    panic = trigger_panic_button()
    print(f"  Status: {panic['status']}")
    
    # T176
    print("\n[T176] Performance Mode:")
    perf = activate_performance_mode("default")
    print(f"  Status: {perf['status']}")
    print(f"  Layout: {perf['layout']}")
    
    # T180
    print("\n[T180] Visualization Macros:")
    macros = get_visualization_macros()
    print(f"  Available: {macros['available_macros']}")
    
    print("\n" + "=" * 60)
    print("All T166-T180 tasks implemented successfully!")
    print("=" * 60)
