"""
AbletonMCP_AI MCP Server - Clean FastMCP server for Ableton Live 12.
Communicates with the Ableton Remote Script via TCP socket on port 9877.
"""
import json
import logging
import os
import socket
import sys
import time
from contextlib import asynccontextmanager
from pathlib import Path
from typing import Optional

from mcp.server.fastmcp import FastMCP, Context

# ------------------------------------------------------------------
# Paths
# ------------------------------------------------------------------
BASE_DIR = Path(__file__).resolve().parent.parent.parent  # MIDI Remote Scripts root
PROJECT_DIR = Path(__file__).resolve().parent.parent       # AbletonMCP_AI
MCP_DIR = Path(__file__).resolve().parent                   # AbletonMCP_AI/mcp
ENGINE_DIR = MCP_DIR / "engines"

# Add engine dir to path so we can import them
for p in (str(ENGINE_DIR), str(MCP_DIR), str(PROJECT_DIR)):
    if p not in sys.path:
        sys.path.insert(0, p)

# ------------------------------------------------------------------
# Logging
# ------------------------------------------------------------------
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s: %(message)s")
logger = logging.getLogger("AbletonMCP-AI")

# ------------------------------------------------------------------
# Ableton TCP connection
# ------------------------------------------------------------------
ABLETON_HOST = "127.0.0.1"
ABLETON_PORT = 9877
TERMINATOR = b"\n"

# Tool timeouts (seconds)
TIMEOUTS = {
    "get_session_info": 5.0,
    "get_tracks": 5.0,
    "get_scenes": 5.0,
    "get_master_info": 5.0,
    "set_tempo": 10.0,
    "start_playback": 10.0,
    "stop_playback": 10.0,
    "toggle_playback": 10.0,
    "stop_all_clips": 10.0,
    "clear_project": 30.0,
    "create_midi_track": 15.0,
    "create_audio_track": 15.0,
    "set_track_name": 10.0,
    "set_track_volume": 10.0,
    "set_track_pan": 10.0,
    "set_track_mute": 10.0,
    "set_track_solo": 10.0,
    "set_master_volume": 10.0,
    "create_clip": 15.0,
    "add_notes_to_clip": 15.0,
    "fire_clip": 10.0,
    "fire_scene": 10.0,
    "set_scene_name": 10.0,
    "create_scene": 15.0,
    "set_metronome": 10.0,
    "set_loop": 10.0,
    "set_signature": 10.0,
    "create_arrangement_audio_pattern": 30.0,
    "load_sample_to_drum_rack": 30.0,
    "generate_track": 300.0,
    "generate_song": 300.0,
    "select_samples_for_genre": 30.0,
    # Sprint 2 - Phase 1 & 2: Advanced Production Tools
    "generate_complete_reggaeton": 60.0,
    "generate_from_reference": 60.0,
    "load_sample_to_clip": 15.0,
    "duplicate_clip": 15.0,
    "create_arrangement_audio_clip": 20.0,
    "set_warp_markers": 15.0,
    "reverse_clip": 10.0,
    "pitch_shift_clip": 15.0,
    "time_stretch_clip": 15.0,
    "slice_clip": 20.0,
    # Fase 3: Mixing & Effects
    "create_bus_track": 15.0,
    "route_track_to_bus": 10.0,
    "create_return_track": 15.0,
    "set_track_send": 10.0,
    "insert_device": 15.0,
    "configure_eq": 15.0,
    "configure_compressor": 15.0,
    "setup_sidechain": 15.0,
    "auto_gain_staging": 20.0,
    "analyze_levels": 15.0,
    "apply_master_chain": 20.0,
    # Fase 4: Workflow & Export
    "export_project": 60.0,
    "get_project_summary": 10.0,
    "suggest_improvements": 15.0,
    "validate_project": 15.0,
    "humanize_track": 15.0,
    # Phase 1 & 2 - Bridge Engines to Ableton (T001-T040)
    "produce_reggaeton": 300.0,
    "produce_from_reference": 300.0,
    "produce_arrangement": 300.0,
    "complete_production": 300.0,
    "batch_produce": 600.0,
    "generate_midi_clip": 30.0,
    "generate_dembow_clip": 30.0,
    "generate_bass_clip": 30.0,
    "generate_chords_clip": 30.0,
    "generate_melody_clip": 30.0,
    "create_drum_kit": 30.0,
    "build_track_from_samples": 60.0,
    "generate_track_from_config": 120.0,
    "generate_section": 60.0,
    "apply_human_feel": 30.0,
    "add_percussion_fills": 30.0,
    # Phase 2 - Arrangement & Automation
    "build_arrangement_structure": 60.0,
    "create_arrangement_midi_clip": 30.0,
    "create_arrangement_audio_clip": 30.0,
    "fill_arrangement_with_song": 300.0,
    "automate_filter": 30.0,
    "create_fx_automation": 30.0,
    # Musical intelligence / workflow / quality
    "analyze_project_key": 20.0,
    "harmonize_track": 30.0,
    "generate_counter_melody": 30.0,
    "detect_energy_curve": 20.0,
    "balance_sections": 20.0,
    "variate_loop": 30.0,
    "add_call_and_response": 30.0,
    "generate_breakdown": 30.0,
    "generate_drop_variation": 30.0,
    "create_outro": 30.0,
    "render_stems": 120.0,
    "render_full_mix": 120.0,
    "render_instrumental": 120.0,
    "full_quality_check": 30.0,
    "fix_quality_issues": 60.0,
    "duplicate_project": 30.0,
    # Intelligent Track Generation (T200+)
    "generate_intelligent_track": 300.0,
    "generate_expansive_track": 600.0,
    "create_radio_edit": 60.0,
    "create_dj_edit": 60.0,
    "undo": 10.0,
    "redo": 10.0,
    "save_checkpoint": 20.0,
    "health_check": 10.0,
    # Agente 3: Transitions & Fills
    "create_fx_hit": 30.0,
    "create_transition_fill": 30.0,
    "create_intro_buildup": 30.0,
    # Agente 4: White Noise Generator
    "create_white_noise": 30.0,
    # Agente 5: Multi-Parameter Automation
    "add_parameter_automation": 30.0,
    # Agente 8: Parallel Compression System
    "create_parallel_compression": 30.0,
    # Agente 13: Extended Chords Engine
    "generate_advanced_chords": 30.0,
    # Agente 14: Professional Melody Engine (motivic)
    "generate_motivic_melody": 30.0,
    # Agente 15: Reggaeton Rhythm Patterns Library
    "get_rhythmic_pattern": 15.0,
    # Agente 18: Professional Workflow Orchestrator
    "produce_professional_track": 600.0,
    # Agente 12: VST/AU Plugin Support
    "load_vst_plugin": 30.0,
    "configure_vst_parameter": 15.0,
    "scan_vst_plugins": 30.0,
    "get_vst_presets": 15.0,
    # Agente 19: Quality Assurance Suite
    "validate_project_qa": 15.0,
    "suggest_improvements_qa": 15.0,
    # Sprint 5: DJ Professional Track
    "generate_dj_professional_track": 600.0,
    # Sprint 5.5: Advanced Production Tools
    "inject_sample_batch": 10.0,
    "validate_coherence": 15.0,
    "build_section_real": 15.0,
    "select_coherent_kit": 20.0,
    "produce_radio_edit_4min": 600.0,
    "get_production_progress": 5.0,
    # BPM Analyzer Integration
    "analyze_all_bpm": 600.0,  # 10 minutes for analyzing 800+ samples
    "select_bpm_coherent_pool": 20.0,
    "warp_clip_to_bpm": 30.0,
    # Spectral Coherence Production
    "produce_with_spectral_coherence": 300.0,
}


def _send_to_ableton(cmd_type: str, params: dict = None, timeout: float = 15.0) -> dict:
    """Send a command to the Ableton Remote Script and return the response."""
    sock = None
    try:
        sock = socket.create_connection((ABLETON_HOST, ABLETON_PORT), timeout=timeout)
        sock.settimeout(timeout)

        msg = json.dumps({"type": cmd_type, "params": params or {}}) + "\n"
        sock.sendall(msg.encode("utf-8"))

        buf = b""
        while True:
            chunk = sock.recv(65536)
            if not chunk:
                break
            buf += chunk
            if TERMINATOR in buf:
                raw, _, _ = buf.partition(TERMINATOR)
                return json.loads(raw.decode("utf-8"))

        return {"status": "error", "message": "No response terminator received"}
    except socket.timeout:
        return {"status": "error", "message": f"Command '{cmd_type}' timed out after {timeout}s"}
    except ConnectionRefusedError:
        return {"status": "error", "message": f"Cannot connect to Ableton on {ABLETON_HOST}:{ABLETON_PORT}. Is the Remote Script loaded?"}
    except Exception as e:
        return {"status": "error", "message": str(e)}
    finally:
        if sock:
            try:
                sock.close()
            except Exception:
                pass


def _ok(data: dict) -> str:
    return json.dumps({"status": "success", "result": data}, indent=2)


def _err(msg: str) -> str:
    return json.dumps({"status": "error", "message": msg}, indent=2)


def _ableton_result(resp: dict) -> dict:
    """Return the nested Ableton payload when present."""
    result = resp.get("result", {})
    return result if isinstance(result, dict) else {}


def _proxy_ableton_command(cmd_type: str, params: dict = None, timeout: Optional[float] = None,
                           defaults: dict = None) -> str:
    """Execute a TCP command against Ableton and wrap the nested result."""
    resp = _send_to_ableton(cmd_type, params or {}, timeout=timeout or TIMEOUTS.get(cmd_type, 15.0))
    if resp.get("status") != "success":
        return _err(resp.get("message", "Unknown error"))

    payload = dict(defaults or {})
    payload.update(_ableton_result(resp))
    return _ok(payload)


def _warm_engine_imports() -> None:
    """Preload heavy engine modules before the first MCP tool call.

    FastMCP handles tool calls on the request path. Some lazy imports work fine in
    direct Python calls but stall badly when they happen inside a live stdio
    CallToolRequest. Warming the heavy workflow modules at startup keeps those
    imports off the request path and avoids false MCP timeouts.
    """
    warmers = [
        ("ProductionWorkflow", lambda: __import__("engines.production_workflow", fromlist=["ProductionWorkflow"]).ProductionWorkflow()),
        ("WorkflowEngine", lambda: __import__("engines.workflow_engine", fromlist=["WorkflowEngine"]).WorkflowEngine()),
        ("MusicalIntelligenceEngine", lambda: __import__("engines.musical_intelligence", fromlist=["MusicalIntelligenceEngine"]).MusicalIntelligenceEngine()),
    ]
    for name, warmer in warmers:
        try:
            warmer()
            logger.info("Warm preload ready: %s", name)
        except Exception:
            logger.exception("Warm preload failed: %s", name)


# ------------------------------------------------------------------
# Lifespan / startup
# ------------------------------------------------------------------
@asynccontextmanager
async def server_lifespan(server: FastMCP):
    logger.info("AbletonMCP-AI Server starting...")
    _warm_engine_imports()
    # Non-blocking: try to connect to Ableton but don't block startup if unavailable
    try:
        sock = socket.create_connection((ABLETON_HOST, ABLETON_PORT), timeout=2.0)
        sock.settimeout(2.0)
        msg = json.dumps({"type": "get_session_info", "params": {}}) + "\n"
        sock.sendall(msg.encode("utf-8"))
        buf = b""
        sock.settimeout(3.0)
        try:
            while TERMINATOR not in buf:
                chunk = sock.recv(4096)
                if not chunk:
                    break
                buf += chunk
            if TERMINATOR in buf:
                raw = buf.split(TERMINATOR)[0]
                info = json.loads(raw.decode("utf-8"))
                r = info.get("result", {})
                logger.info("Connected to Ableton Live: %d BPM, %d tracks",
                           r.get("tempo", 0), r.get("num_tracks", 0))
        except Exception:
            logger.warning("Ableton connected but session info unavailable")
        sock.close()
    except ConnectionRefusedError:
        logger.warning("Ableton Live not reachable on %s:%d. Load AbletonMCP_AI as Control Surface.", ABLETON_HOST, ABLETON_PORT)
    except Exception as e:
        logger.warning("Ableton connection check failed: %s", str(e))
    yield
    logger.info("AbletonMCP-AI Server shutting down")


mcp = FastMCP("Ableton Live MCP", lifespan=server_lifespan)


# ==================================================================
# DEBUG - No dependencies, always works
# ==================================================================
@mcp.tool()
def ping(ctx: Context) -> str:
    """Simple ping test. Use this to verify MCP connectivity without needing Ableton."""
    tool_count = len(getattr(getattr(mcp, "_tool_manager", None), "_tools", {}))
    return json.dumps({"status": "ok", "message": "pong", "tools": tool_count})


# ==================================================================
# INFO TOOLS
# ==================================================================
@mcp.tool()
def get_session_info(ctx: Context) -> str:
    """Get current Ableton Live session information."""
    resp = _send_to_ableton("get_session_info", timeout=TIMEOUTS["get_session_info"])
    if resp.get("status") == "success":
        r = resp["result"]
        return _ok({
            "tempo": r.get("tempo"),
            "num_tracks": r.get("num_tracks"),
            "num_scenes": r.get("num_scenes"),
            "is_playing": r.get("is_playing"),
            "current_song_time": r.get("current_song_time"),
            "metronome": r.get("metronome"),
            "master_volume": r.get("master_volume"),
        })
    return _err(resp.get("message", "Unknown error"))


@mcp.tool()
def get_tracks(ctx: Context) -> str:
    """Get list of all tracks in the current project."""
    resp = _send_to_ableton("get_tracks", timeout=TIMEOUTS["get_tracks"])
    if resp.get("status") == "success":
        return _ok(resp.get("result", {}))
    return _err(resp.get("message", "Unknown error"))


@mcp.tool()
def get_scenes(ctx: Context) -> str:
    """Get list of all scenes."""
    resp = _send_to_ableton("get_scenes", timeout=TIMEOUTS["get_scenes"])
    if resp.get("status") == "success":
        return _ok(resp.get("result", {}))
    return _err(resp.get("message", "Unknown error"))


@mcp.tool()
def get_arrangement_clips(ctx: Context, track_index: int = None) -> str:
    """Read all clips currently placed in Arrangement View.

    Use this to understand the current song structure â€” which clips exist,
    where they start, how long they are, and which tracks they're on.

    Essential for understanding a project before modifying it.

    Args:
        track_index: Optional. If provided, only returns clips for that track.
                     If omitted, returns clips for all tracks.

    Returns:
        - clips: list with track_index, track_name, name, start_time (beats),
                 end_time, length, is_midi, color, muted, looping
        - total_clips: total count
        - arrangement_length_beats: total song length in beats
        - unique_start_positions: sorted list of clip start points (bar map)
    """
    params = {}
    if track_index is not None:
        params["track_index"] = track_index
    return _proxy_ableton_command("get_arrangement_clips", params, timeout=30.0)


@mcp.tool()
def get_master_info(ctx: Context) -> str:
    """Get master track information."""
    resp = _send_to_ableton("get_master_info", timeout=TIMEOUTS["get_master_info"])
    if resp.get("status") == "success":
        return _ok(resp.get("result", {}))
    return _err(resp.get("message", "Unknown error"))


@mcp.tool()
def health_check(ctx: Context) -> str:
    """T050: Run a comprehensive health check of the AbletonMCP_AI system.

    Runs 5 checks:
      1. TCP server connection
      2. Song accessibility
      3. Tracks accessibility
      4. Browser accessibility
      5. update_display drain loop active

    Returns a score 0-5 with detailed status for each check.
    This should be the first command run after opening Ableton.
    """
    resp = _send_to_ableton("health_check", timeout=TIMEOUTS["health_check"])
    if resp.get("status") == "success":
        r = resp.get("result", {})
        score = r.get("score", 0)
        status = r.get("status", "UNKNOWN")
        checks = r.get("checks", [])
        recommendation = r.get("recommendation", "")

        check_summary = []
        for c in checks:
            icon = "OK" if c.get("passed") else "FAIL"
            check_summary.append("  [%s] %s: %s" % (icon, c.get("name", "?"), c.get("detail", "")))

        return _ok({
            "score": "%d/5" % score,
            "status": status,
            "checks": check_summary,
            "recommendation": recommendation,
        })
    return _err(resp.get("message", "Unknown error"))


# ==================================================================
# TRANSPORT
# ==================================================================
@mcp.tool()
def start_playback(ctx: Context) -> str:
    """Start playback."""
    resp = _send_to_ableton("start_playback", timeout=TIMEOUTS["start_playback"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def stop_playback(ctx: Context) -> str:
    """Stop playback."""
    resp = _send_to_ableton("stop_playback", timeout=TIMEOUTS["stop_playback"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def toggle_playback(ctx: Context) -> str:
    """Toggle playback (start if stopped, stop if playing)."""
    resp = _send_to_ableton("toggle_playback", timeout=TIMEOUTS["toggle_playback"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def stop_all_clips(ctx: Context) -> str:
    """Stop all clips in Session View."""
    resp = _send_to_ableton("stop_all_clips", timeout=TIMEOUTS["stop_all_clips"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def clear_project(ctx: Context) -> str:
    """Clear entire project - delete all tracks and clips. Useful for starting fresh.
    
    Returns:
        Confirmation message with number of tracks deleted.
    """
    resp = _send_to_ableton("clear_project", timeout=TIMEOUTS["clear_project"])
    if resp.get("status") == "success":
        result = resp.get("result", {})
        deleted = result.get("tracks_deleted", 0)
        return _ok("Project cleared. %d tracks deleted. Ready for new production." % deleted)
    return _err(resp.get("message", "Failed to clear project"))


# ==================================================================
# PROJECT SETTINGS
# ==================================================================
@mcp.tool()
def set_tempo(ctx: Context, tempo: float) -> str:
    """Set the project tempo in BPM."""
    if not 20 <= tempo <= 300:
        return _err(f"Invalid tempo: {tempo}. Must be 20-300 BPM.")
    resp = _send_to_ableton("set_tempo", {"tempo": tempo}, timeout=TIMEOUTS["set_tempo"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def set_time_signature(ctx: Context, numerator: int = 4, denominator: int = 4) -> str:
    """Set the project time signature."""
    resp = _send_to_ableton("set_signature", {"numerator": numerator, "denominator": denominator},
                            timeout=TIMEOUTS["set_signature"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def set_metronome(ctx: Context, enabled: bool) -> str:
    """Enable or disable metronome."""
    resp = _send_to_ableton("set_metronome", {"enabled": enabled}, timeout=TIMEOUTS["set_metronome"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


# ==================================================================
# TRACKS
# ==================================================================
@mcp.tool()
def create_midi_track(ctx: Context, index: int = -1) -> str:
    """Create a new MIDI track. index=-1 appends at the end."""
    resp = _send_to_ableton("create_midi_track", {"index": index}, timeout=TIMEOUTS["create_midi_track"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def create_audio_track(ctx: Context, index: int = -1) -> str:
    """Create a new audio track. index=-1 appends at the end."""
    resp = _send_to_ableton("create_audio_track", {"index": index}, timeout=TIMEOUTS["create_audio_track"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def set_track_name(ctx: Context, track_index: int, name: str) -> str:
    """Set the name of a track."""
    resp = _send_to_ableton("set_track_name", {"track_index": track_index, "name": name},
                            timeout=TIMEOUTS["set_track_name"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def set_track_volume(ctx: Context, track_index: int, volume: float) -> str:
    """Set track volume (0.0 - 1.0)."""
    if not 0.0 <= volume <= 1.0:
        return _err(f"Invalid volume: {volume}. Must be 0.0-1.0.")
    resp = _send_to_ableton("set_track_volume", {"track_index": track_index, "volume": volume},
                            timeout=TIMEOUTS["set_track_volume"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def set_track_pan(ctx: Context, track_index: int, pan: float) -> str:
    """Set track pan (-1.0 left to 1.0 right)."""
    if not -1.0 <= pan <= 1.0:
        return _err(f"Invalid pan: {pan}. Must be -1.0 to 1.0.")
    resp = _send_to_ableton("set_track_pan", {"track_index": track_index, "pan": pan},
                            timeout=TIMEOUTS["set_track_pan"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def set_track_mute(ctx: Context, track_index: int, mute: bool) -> str:
    """Mute or unmute a track."""
    resp = _send_to_ableton("set_track_mute", {"track_index": track_index, "mute": mute},
                            timeout=TIMEOUTS["set_track_mute"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def set_track_solo(ctx: Context, track_index: int, solo: bool) -> str:
    """Solo or unsolo a track."""
    resp = _send_to_ableton("set_track_solo", {"track_index": track_index, "solo": solo},
                            timeout=TIMEOUTS["set_track_solo"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def set_master_volume(ctx: Context, volume: float) -> str:
    """Set master track volume (0.0 - 1.0)."""
    if not 0.0 <= volume <= 1.0:
        return _err(f"Invalid volume: {volume}. Must be 0.0-1.0.")
    resp = _send_to_ableton("set_master_volume", {"volume": volume}, timeout=TIMEOUTS["set_master_volume"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


# ==================================================================
# CLIPS & SESSION VIEW
# ==================================================================
@mcp.tool()
def create_clip(ctx: Context, track_index: int, clip_index: int = 0, length: float = 4.0) -> str:
    """Create a MIDI clip in Session View."""
    resp = _send_to_ableton("create_clip", {"track_index": track_index, "clip_index": clip_index, "length": length},
                            timeout=TIMEOUTS["create_clip"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def add_notes_to_clip(ctx: Context, track_index: int, clip_index: int, notes: list) -> str:
    """Add MIDI notes to a clip. notes is a list of dicts with keys: pitch, start_time, duration, velocity."""
    resp = _send_to_ableton("add_notes_to_clip",
                            {"track_index": track_index, "clip_index": clip_index, "notes": notes},
                            timeout=TIMEOUTS["add_notes_to_clip"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def fire_clip(ctx: Context, track_index: int, clip_index: int = 0) -> str:
    """Fire a clip in Session View."""
    resp = _send_to_ableton("fire_clip", {"track_index": track_index, "clip_index": clip_index},
                            timeout=TIMEOUTS["fire_clip"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def fire_scene(ctx: Context, scene_index: int) -> str:
    """Fire a scene in Session View."""
    resp = _send_to_ableton("fire_scene", {"scene_index": scene_index}, timeout=TIMEOUTS["fire_scene"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def set_scene_name(ctx: Context, scene_index: int, name: str) -> str:
    """Set the name of a scene."""
    resp = _send_to_ableton("set_scene_name", {"scene_index": scene_index, "name": name},
                            timeout=TIMEOUTS["set_scene_name"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def create_scene(ctx: Context, index: int = -1) -> str:
    """Create a new scene."""
    resp = _send_to_ableton("create_scene", {"index": index}, timeout=TIMEOUTS["create_scene"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


# ==================================================================
# ARRANGEMENT VIEW - Audio clips
# ==================================================================
@mcp.tool()
def create_arrangement_audio_pattern(ctx: Context, track_index: int, file_path: str,
                                     positions: list = None, name: str = "") -> str:
    """Create audio clips in Arrangement View from a .wav file."""
    if positions is None:
        positions = [0]
    resp = _send_to_ableton("create_arrangement_audio_pattern",
                            {"track_index": track_index, "file_path": file_path,
                             "positions": positions, "name": name},
                            timeout=TIMEOUTS["create_arrangement_audio_pattern"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


# ==================================================================
# GENERATION & SAMPLE SELECTION
# ==================================================================
@mcp.tool()
def generate_track(ctx: Context, genre: str, style: str = "", bpm: float = 0,
                   key: str = "", structure: str = "standard") -> str:
    """Generate a track using AI."""
    resp = _send_to_ableton("generate_track",
                            {"genre": genre, "style": style, "bpm": bpm, "key": key, "structure": structure},
                            timeout=TIMEOUTS["generate_track"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def generate_song(ctx: Context, genre: str, style: str = "", bpm: float = 0,
                  key: str = "", structure: str = "standard") -> str:
    """Generate a complete song."""
    resp = _send_to_ableton("generate_track",
                            {"genre": genre, "style": style, "bpm": bpm, "key": key, "structure": structure},
                            timeout=TIMEOUTS["generate_song"])
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def select_samples_for_genre(ctx: Context, genre: str, key: str = "", bpm: float = 0) -> str:
    """Select samples for a genre from the local library."""
    # Import the sample selector engine
    try:
        from engines.sample_selector import SampleSelector, get_selector
        selector = get_selector()
        if selector is None:
            return _err("Sample selector not available. Check libreria/reggaeton path.")
        group = selector.select_for_genre(genre, key if key else None, bpm if bpm > 0 else None)
        result = {
            "genre": group.genre,
            "key": group.key,
            "bpm": group.bpm,
            "drums": {},
            "bass": [],
            "synths": [],
            "fx": [],
        }
        kit = group.drums
        if kit.kick:
            result["drums"]["kick"] = kit.kick.name
        if kit.snare:
            result["drums"]["snare"] = kit.snare.name
        if kit.clap:
            result["drums"]["clap"] = kit.clap.name
        if kit.hat_closed:
            result["drums"]["hat_closed"] = kit.hat_closed.name
        if kit.hat_open:
            result["drums"]["hat_open"] = kit.hat_open.name
        result["bass"] = [s.name for s in (group.bass or [])[:5]]
        result["synths"] = [s.name for s in (group.synths or [])[:5]]
        result["fx"] = [s.name for s in (group.fx or [])[:3]]
        return _ok(result)
    except ImportError:
        return _err("Sample selector engine not available.")
    except Exception as e:
        return _err(f"Error selecting samples: {str(e)}")


# ==================================================================
# LIBRARY ANALYSIS TOOLS (Sprint 1 Integration)
# ==================================================================
REGGAETON_LIB = r"C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\libreria\reggaeton"

# Cache for expensive engine instances
_analyzer_cache = None
_embedding_cache = None
_matcher_cache = None

def _get_analyzer():
    """Lazy-load the LibreriaAnalyzer with caching."""
    global _analyzer_cache
    if _analyzer_cache is None:
        logger.info("Initializing LibreriaAnalyzer cache")
        from engines.libreria_analyzer import LibreriaAnalyzer
        _analyzer_cache = LibreriaAnalyzer(REGGAETON_LIB, verbose=False)
        logger.info("LibreriaAnalyzer cache ready")
    return _analyzer_cache

def _get_embedding_engine():
    """Lazy-load the EmbeddingEngine with caching."""
    global _embedding_cache
    if _embedding_cache is None:
        from engines.embedding_engine import EmbeddingEngine
        _embedding_cache = EmbeddingEngine()
    return _embedding_cache

def _get_matcher():
    """Lazy-load the ReferenceMatcher with caching."""
    global _matcher_cache
    if _matcher_cache is None:
        from engines.reference_matcher import ReferenceMatcher
        ref_path = REGGAETON_LIB + "\\reggaeton_ejemplo.mp3"
        _matcher_cache = ReferenceMatcher(reference_path=ref_path if os.path.isfile(ref_path) else None)
    return _matcher_cache


@mcp.tool()
def analyze_library(ctx: Context, force_reanalyze: bool = False) -> str:
    """Analyze all samples in the reggaeton library. Extracts BPM, Key, MFCCs, etc."""
    try:
        analyzer = _get_analyzer()
        result = analyzer.analyze_all(force_reanalyze=force_reanalyze)
        return _ok({
            "total_analyzed": len(result),
            "cache_file": str(analyzer.cache_path),
        })
    except Exception as e:
        return _err(f"Error analyzing library: {str(e)}")


@mcp.tool()
def get_library_stats(ctx: Context) -> str:
    """Get statistics about the analyzed library."""
    try:
        logger.info("get_library_stats: start")
        analyzer = _get_analyzer()
        # Try to load cache from disk first (fast)
        if not analyzer.features:
            analyzer._load_cache()
        # If still no features, return basic file count without full analysis
        if not analyzer.features:
            import glob as _glob
            audio_files = _glob.glob(os.path.join(REGGAETON_LIB, "**", "*.wav"), recursive=True)
            audio_files += _glob.glob(os.path.join(REGGAETON_LIB, "**", "*.mp3"), recursive=True)
            audio_files += _glob.glob(os.path.join(REGGAETON_LIB, "**", "*.aif"), recursive=True)
            audio_files += _glob.glob(os.path.join(REGGAETON_LIB, "**", "*.flac"), recursive=True)
            # Count by folder (role)
            roles = {}
            for f in audio_files:
                parts = f.replace(REGGAETON_LIB, "").split(os.sep)
                role = parts[1] if len(parts) > 1 else "unknown"
                roles[role] = roles.get(role, 0) + 1
            return _ok({
                "total_files_found": len(audio_files),
                "files_by_role": roles,
                "note": "Full spectral analysis not yet performed. Call analyze_library first.",
            })
        stats = analyzer.get_stats()
        logger.info("get_library_stats: done")
        return _ok(stats)
    except Exception as e:
        logger.exception("get_library_stats: failed")
        return _err(f"Error getting library stats: {str(e)}")


@mcp.tool()
def get_similar_samples(ctx: Context, sample_path: str, top_n: int = 10) -> str:
    """Find samples similar to a given sample using embeddings."""
    try:
        emb_engine = _get_embedding_engine()
        results = emb_engine.find_similar(sample_path, top_n=top_n)
        return _ok({"reference": sample_path, "similar": results})
    except Exception as e:
        return _err(f"Error finding similar samples: {str(e)}")


@mcp.tool()
def find_samples_like_audio(ctx: Context, audio_path: str, top_n: int = 20, role: str = "") -> str:
    """Find samples similar to an external audio file (e.g., reggaeton_ejemplo.mp3)."""
    try:
        emb_engine = _get_embedding_engine()
        results = emb_engine.find_by_reference(audio_path, top_n=top_n)
        if role:
            results = [r for r in results if r.get("role", "") == role][:top_n]
        return _ok({"reference": audio_path, "similar": results})
    except Exception as e:
        return _err(f"Error finding samples like audio: {str(e)}")


@mcp.tool()
def get_user_sound_profile(ctx: Context) -> str:
    """Get the user's sound profile based on reggaeton_ejemplo.mp3."""
    try:
        matcher = _get_matcher()
        profile = matcher.get_user_profile()
        return _ok(profile)
    except Exception as e:
        return _err(f"Error getting user profile: {str(e)}")


@mcp.tool()
def get_recommended_samples(ctx: Context, role: str = "", count: int = 5) -> str:
    """Get recommended samples for a role based on user's sound profile."""
    try:
        from engines.reference_matcher import get_recommended_samples as _rec
        results = _rec(role if role else None, count)
        return _ok({"role": role or "all", "samples": results})
    except Exception as e:
        return _err(f"Error getting recommended samples: {str(e)}")


@mcp.tool()
def compare_two_samples(ctx: Context, path1: str, path2: str) -> str:
    """Compare two samples and return similarity score and feature differences."""
    try:
        emb_engine = _get_embedding_engine()
        e1 = emb_engine.get_embedding(path1)
        e2 = emb_engine.get_embedding(path2)
        if e1 is None or e2 is None:
            return _err("One or both samples not found in embeddings index")
        from engines.embedding_engine import cosine_similarity
        sim = cosine_similarity(e1, e2)
        f1 = emb_engine.analyzer.get_features(path1) if hasattr(emb_engine, 'analyzer') else {}
        f2 = emb_engine.analyzer.get_features(path2) if hasattr(emb_engine, 'analyzer') else {}
        return _ok({
            "similarity": float(sim),
            "sample1": {"path": path1, "features": f1},
            "sample2": {"path": path2, "features": f2},
        })
    except Exception as e:
        return _err(f"Error comparing samples: {str(e)}")


@mcp.tool()
def browse_library(ctx: Context, pack: str = "", role: str = "", bpm_min: float = 0, bpm_max: float = 0, key: str = "") -> str:
    """Browse the library with filters for pack, role, BPM range, and key."""
    try:
        analyzer = _get_analyzer()
        if not analyzer.features:
            analyzer.analyze_all()
        results = []
        for path, feats in analyzer.features.items():
            if pack and pack.lower() not in feats.get("pack", "").lower():
                continue
            if role and role.lower() != feats.get("role", "").lower():
                continue
            if key and key.lower() not in feats.get("key", "").lower():
                continue
            bpm = feats.get("bpm", 0)
            if bpm_min > 0 and bpm < bpm_min:
                continue
            if bpm_max > 0 and bpm > bpm_max:
                continue
            results.append({"path": path, **feats})
        return _ok({"total": len(results), "samples": results[:50]})
    except Exception as e:
        return _err(f"Error browsing library: {str(e)}")


# ==================================================================
# BPM ANALYZER INTEGRATION (T090-T094)
# ==================================================================

@mcp.tool()
def analyze_all_bpm(ctx: Context, force_reanalyze: bool = False) -> str:
    """Analyze BPM of all samples in the reggaeton library using librosa.
    
    This tool analyzes all 800+ samples in the library, extracting BPM,
    confidence scores, and spectral embeddings. Results are stored in
    the SQLite metadata store for fast retrieval.
    
    Args:
        force_reanalyze: Reanalyze all samples even if already in database
    
    Returns:
        JSON with analysis results:
        - analyzed: Number of samples successfully analyzed
        - total: Total number of samples found
        - progress: Analysis progress percentage
        - elapsed_minutes: Time taken for analysis
        - sample_results: First 20 sample results for preview
        - errors: Any errors encountered (first 10)
    
    Note:
        This operation takes approximately 30 minutes for 800 samples.
        Progress is logged every 50 samples.
    """
    resp = _send_to_ableton("analyze_all_bpm", {"force_reanalyze": force_reanalyze},
                            timeout=TIMEOUTS["analyze_all_bpm"])
    if resp.get("status") == "success":
        r = resp.get("result", {})
        return _ok({
            "analyzed": r.get("analyzed", 0),
            "total": r.get("total", 0),
            "progress": r.get("progress", "0%"),
            "elapsed_minutes": r.get("elapsed_minutes", 0),
            "library_path": r.get("library_path", ""),
            "sample_preview": r.get("sample_results", [])[:5],  # Show first 5
            "errors": r.get("errors")[:3] if r.get("errors") else None,  # Show first 3 errors
            "note": "Full results stored in metadata store. Use browse_library or get_library_stats to query."
        })
    return _err(resp.get("message", "Unknown error during BPM analysis"))


@mcp.tool()
def select_bpm_coherent_pool(ctx: Context, target_bpm: float = 95, tolerance: float = 5, pool_size: int = 20) -> str:
    """Select samples that match target BPM within tolerance.
    
    Uses librosa-analyzed BPM data from the metadata store to find
    samples that will work well together at a specific tempo.
    
    Args:
        target_bpm: Target tempo to match (default 95)
        tolerance: BPM tolerance (default ±5)
        pool_size: Number of samples to return (default 20)
    
    Returns:
        JSON with selected samples and coherence scores.
    """
    try:
        from engines.metadata_store import SampleMetadataStore
        import os
        
        # Initialize store
        db_path = os.path.join(os.path.dirname(__file__), "..", "..", "libreria", "metadata.db")
        store = SampleMetadataStore(db_path)
        store.init_database()
        
        # Get coherent pool
        pool = store.get_coherent_pool(target_bpm, tolerance=tolerance)
        
        # Get details for each sample
        results = []
        for path in pool[:pool_size]:
            features = store.get_sample_features(path)
            if features:
                results.append({
                    "path": path,
                    "bpm": features.bpm,
                    "key": features.key,
                    "category": features.categories[0] if features.categories else "unknown"
                })
        
        store.close()
        
        return _ok({
            "target_bpm": target_bpm,
            "tolerance": tolerance,
            "pool_size": len(pool),
            "returned": len(results),
            "samples": results
        })
    except Exception as e:
        return _err(f"Error selecting BPM coherent pool: {str(e)}")


@mcp.tool()
def warp_clip_to_bpm(ctx: Context, track_index: int, clip_index: int, 
                     original_bpm: float, target_bpm: float) -> str:
    """Warp audio clip from original BPM to target BPM.
    
    Automatically selects warp mode (Complex Pro/Complex/Beats) based on
    the BPM difference.
    
    Args:
        track_index: Track containing clip
        clip_index: Clip slot index
        original_bpm: Original sample BPM (from analysis)
        target_bpm: Target project BPM
    
    Returns:
        JSON with warp result including warp mode used.
    """
    resp = _send_to_ableton("auto_warp_sample",  # Uses internal method
                            {"track_index": track_index, "clip_index": clip_index,
                             "original_bpm": original_bpm, "target_bpm": target_bpm},
                            timeout=TIMEOUTS["warp_clip_to_bpm"])
    if resp.get("status") == "success":
        r = resp.get("result", {})
        return _ok({
            "warped": r.get("warped", False),
            "warp_mode": r.get("warp_mode", "unknown"),
            "original_bpm": r.get("original_bpm", original_bpm),
            "target_bpm": r.get("target_bpm", target_bpm),
            "delta_pct": r.get("delta_pct", 0),
            "warp_factor": r.get("warp_factor", 1.0)
        })
    return _err(resp.get("message", "Unknown error during warp"))


# ==================================================================
# ADVANCED PRODUCTION TOOLS (Sprint 2 - Phase 1 & 2)
# ==================================================================

@mcp.tool()
def generate_complete_reggaeton(ctx: Context, bpm: float = 95, key: str = "Am",
                                 style: str = "classic", structure: str = "verse-chorus",
                                 use_samples: bool = True) -> str:
    """Generate a complete reggaeton project with all elements.

    Args:
        bpm: Tempo in BPM (default 95)
        key: Musical key (default Am)
        style: Reggaeton style (classic, dembow, perreo, moombahton)
        structure: Song structure (verse-chorus, full, intro-drop)
        use_samples: Whether to use samples from the library

    Returns:
        JSON with project summary including tracks created, samples used, and arrangement.
    """
    try:
        from engines.production_workflow import ProductionWorkflow
        workflow = ProductionWorkflow()
        result = workflow.generate_complete_reggaeton(
            bpm=bpm,
            key=key,
            style=style,
            structure=structure,
            use_samples=use_samples
        )
        return _ok({
            "project_type": "complete_reggaeton",
            "bpm": bpm,
            "key": key,
            "style": style,
            "structure": structure,
            "tracks_created": result.get("tracks", []),
            "samples_used": result.get("samples", {}),
            "arrangement": result.get("arrangement", {}),
            "duration_bars": result.get("duration_bars", 64),
        })
    except ImportError:
        return _err("Production workflow engine not available.")
    except Exception as e:
        return _err(f"Error generating complete reggaeton: {str(e)}")


@mcp.tool()
def generate_from_reference(ctx: Context, reference_audio_path: str) -> str:
    """Generate a track using a reference audio file for style matching.

    Analyzes the reference audio using the reference_matcher engine,
    finds similar samples from the library, and generates a track
    with matching sonic characteristics.

    Args:
        reference_audio_path: Path to the reference audio file (.mp3, .wav)

    Returns:
        JSON with generated tracks info, matched samples, and similarity scores.
    """
    try:
        from engines.production_workflow import ProductionWorkflow

        if not os.path.isfile(reference_audio_path):
            return _err(f"Reference audio not found: {reference_audio_path}")

        workflow = ProductionWorkflow()
        result = workflow.generate_from_reference(reference_audio_path)
        return _ok({
            "reference": reference_audio_path,
            **(result if isinstance(result, dict) else {"result": result}),
        })
    except ImportError as e:
        return _err(f"Required engine not available: {str(e)}")
    except Exception as e:
        return _err(f"Error generating from reference: {str(e)}")


@mcp.tool()
def load_sample_to_clip(ctx: Context, track_index: int, clip_index: int, sample_path: str) -> str:
    """Load an audio sample into a Session View clip slot.

    Args:
        track_index: Index of the target track
        clip_index: Index of the clip slot
        sample_path: Absolute path to the audio file (.wav, .mp3)

    Returns:
        JSON with status of the load operation.
    """
    if not os.path.isfile(sample_path):
        return _err(f"Sample not found: {sample_path}")

    resp = _send_to_ableton(
        "load_sample_to_clip",
        {"track_index": track_index, "clip_index": clip_index, "sample_path": sample_path},
        timeout=TIMEOUTS["load_sample_to_clip"]
    )
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def duplicate_clip(ctx: Context, source_track: int, source_clip: int, 
                   target_track: int, target_clip: int) -> str:
    """Duplicate/clone a clip from one Session View slot to another.
    
    Args:
        source_track: Source track index
        source_clip: Source clip slot index
        target_track: Target track index (can be same as source)
        target_clip: Target clip slot index
    
    Returns:
        JSON with duplication status and clip info.
    """
    resp = _send_to_ableton(
        "duplicate_clip",
        {"source_track": source_track, "source_clip": source_clip,
         "target_track": target_track, "target_clip": target_clip},
        timeout=TIMEOUTS["duplicate_clip"]
    )
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def load_sample_to_drum_rack(ctx: Context, track_index: int, sample_path: str,
                             pad_note: int = 36) -> str:
    """Load a sample into a specific pad (note) of a Drum Rack.

    Args:
        track_index: Index of the track containing the Drum Rack
        pad_note: MIDI note number for the pad (default 36 = C1)
        sample_path: Absolute path to the audio file

    Returns:
        JSON with status of the load operation.
    """
    if not os.path.isfile(sample_path):
        return _err(f"Sample not found: {sample_path}")

    resp = _send_to_ableton(
        "load_sample_to_drum_rack_pad",
        {"track_index": track_index, "pad_note": pad_note, "sample_path": sample_path},
        timeout=TIMEOUTS["load_sample_to_drum_rack"]
    )
    return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))


@mcp.tool()
def set_warp_markers(ctx: Context, track_index: int, clip_index: int, markers: list) -> str:
    """Configure warp markers for an audio clip.

    Sets custom warp markers to adjust timing and groove of audio clips.

    Args:
        track_index: Index of the track containing the clip
        clip_index: Index of the clip
        markers: List of warp marker positions in bars [{"position": 0.0, "warp_to": 0.0}, ...]

    Returns:
        JSON with status and number of markers set.
    """
    resp = _send_to_ableton(
        "set_warp_markers",
        {"track_index": track_index, "clip_index": clip_index, "markers": markers},
        timeout=TIMEOUTS["set_warp_markers"]
    )
    if resp.get("status") == "success":
        return _ok({
            "track_index": track_index,
            "clip_index": clip_index,
            "markers_set": len(markers),
            "markers": markers,
        })
    return _err(resp.get("message"))


@mcp.tool()
def reverse_clip(ctx: Context, track_index: int, clip_index: int) -> str:
    """Reverse an audio or MIDI clip.

    Args:
        track_index: Index of the track containing the clip
        clip_index: Index of the clip to reverse

    Returns:
        JSON with status of the reverse operation.
    """
    return _proxy_ableton_command(
        "reverse_clip",
        {"track_index": track_index, "clip_index": clip_index},
        timeout=TIMEOUTS["reverse_clip"],
        defaults={"track_index": track_index, "clip_index": clip_index},
    )


@mcp.tool()
def pitch_shift_clip(ctx: Context, track_index: int, clip_index: int, semitones: float) -> str:
    """Pitch shift a clip without affecting tempo (using Complex Pro).

    Args:
        track_index: Index of the track containing the clip
        clip_index: Index of the clip
        semitones: Number of semitones to shift (positive or negative)

    Returns:
        JSON with new pitch value and status.
    """
    if not -24.0 <= semitones <= 24.0:
        return _err(f"Invalid pitch shift: {semitones}. Must be -24 to +24 semitones.")

    return _proxy_ableton_command(
        "pitch_shift_clip",
        {"track_index": track_index, "clip_index": clip_index, "semitones": semitones},
        timeout=TIMEOUTS["pitch_shift_clip"],
        defaults={"track_index": track_index, "clip_index": clip_index, "pitch_shift_semitones": semitones},
    )


@mcp.tool()
def time_stretch_clip(ctx: Context, track_index: int, clip_index: int, factor: float) -> str:
    """Time stretch a clip without affecting pitch.

    Args:
        track_index: Index of the track containing the clip
        clip_index: Index of the clip
        factor: Stretch factor (1.0 = normal, 2.0 = half speed/double length, 0.5 = double speed)

    Returns:
        JSON with new duration and status.
    """
    if not 0.25 <= factor <= 4.0:
        return _err(f"Invalid stretch factor: {factor}. Must be 0.25x to 4.0x.")

    return _proxy_ableton_command(
        "time_stretch_clip",
        {"track_index": track_index, "clip_index": clip_index, "factor": factor},
        timeout=TIMEOUTS["time_stretch_clip"],
        defaults={"track_index": track_index, "clip_index": clip_index, "stretch_factor": factor},
    )


@mcp.tool()
def slice_clip(ctx: Context, track_index: int, clip_index: int, num_slices: int = 8) -> str:
    """Slice an audio clip into multiple segments.

    Divides a clip into equal slices, useful for creating drum racks
    or rearranging audio segments.

    Args:
        track_index: Index of the track containing the clip
        clip_index: Index of the clip to slice
        num_slices: Number of slices to create (default 8, max 64)

    Returns:
        JSON with number of slices created and their positions.
    """
    if not 2 <= num_slices <= 64:
        return _err(f"Invalid number of slices: {num_slices}. Must be 2-64.")

    return _proxy_ableton_command(
        "slice_clip",
        {"track_index": track_index, "clip_index": clip_index, "num_slices": num_slices},
        timeout=TIMEOUTS["slice_clip"],
        defaults={"track_index": track_index, "clip_index": clip_index, "num_slices": num_slices},
    )


# ==================================================================
# FASE 3: MIXING & EFFECTS
# ==================================================================

@mcp.tool()
def create_bus_track(ctx: Context, bus_type: str = "Group") -> str:
    """Create a group track (bus) for mixing."""
    return _proxy_ableton_command(
        "create_bus_track",
        {"bus_type": bus_type},
        timeout=TIMEOUTS["create_bus_track"],
        defaults={"bus_type": bus_type},
    )


@mcp.tool()
def route_track_to_bus(ctx: Context, track_index: int, bus_name: str) -> str:
    """Route a track to a bus/group track."""
    return _proxy_ableton_command(
        "route_track_to_bus",
        {"track_index": track_index, "bus_name": bus_name},
        timeout=TIMEOUTS["route_track_to_bus"],
        defaults={"track_index": track_index, "bus_name": bus_name},
    )


@mcp.tool()
def create_return_track(ctx: Context, effect_type: str = "Reverb") -> str:
    """Create a return track with an effect."""
    try:
        from engines.mixing_engine import ReturnEffect, get_mixing_engine

        normalized = effect_type.strip().upper().replace(" ", "_")
        if normalized not in ReturnEffect.__members__:
            return _err(
                f"Unknown return effect '{effect_type}'. Available: {', '.join(ReturnEffect.__members__.keys())}"
            )

        engine = get_mixing_engine()
        result = engine.return_manager.create_return_track(ReturnEffect[normalized])
        return _ok({
            "effect_type": effect_type,
            "return_index": int(result.track_index),
            "track_name": result.name,
            "parameters": result.effect_parameters,
        })
    except Exception as e:
        return _err(f"Error creating return track: {str(e)}")


@mcp.tool()
def set_track_send(ctx: Context, track_index: int, return_index: int, amount: float) -> str:
    """Configure send amount from a track to a return track."""
    if not 0.0 <= amount <= 1.0:
        return _err(f"Invalid send amount: {amount}. Must be 0.0-1.0.")
    try:
        from engines.mixing_engine import get_mixing_engine

        engine = get_mixing_engine()
        if engine.return_manager.set_track_send(track_index, return_index, amount):
            return _ok({"track_index": track_index, "return_index": return_index, "amount": amount})
        return _err("Failed to set send")
    except Exception as e:
        return _err(f"Error setting track send: {str(e)}")


@mcp.tool()
def insert_device(ctx: Context, track_index: int, device_name: str) -> str:
    """Insert a device/plugin on a track."""
    resp = _send_to_ableton("insert_device", {"track_index": track_index, "device_name": device_name},
                            timeout=TIMEOUTS["insert_device"])
    if resp.get("status") == "success":
        return _ok({"track_index": track_index, "device": device_name, "device_index": resp.get("device_index")})
    return _err(resp.get("message", "Failed to insert device"))


@mcp.tool()
def configure_eq(ctx: Context, track_index: int, preset: str = "default") -> str:
    """Configure EQ Eight on a track with a preset."""
    return _proxy_ableton_command(
        "configure_eq",
        {"track_index": track_index, "preset": preset},
        timeout=TIMEOUTS["configure_eq"],
        defaults={"track_index": track_index, "preset": preset},
    )


@mcp.tool()
def configure_compressor(ctx: Context, track_index: int, preset: str = "default",
                         threshold: float = -20.0, ratio: float = 4.0) -> str:
    """Configure Compressor on a track."""
    try:
        from engines.mixing_engine import get_compression_settings

        compressor = get_compression_settings()
        result = compressor.configure_compressor(
            track_index,
            threshold=threshold,
            ratio=ratio,
            preset=None if preset == "default" else preset,
        )
        if result.get("success"):
            return _ok({
                "track_index": track_index,
                "preset": preset,
                "threshold": threshold,
                "ratio": ratio,
                "settings": result.get("settings", {})
            })
        return _err(result.get("message", "Failed to configure compressor"))
    except Exception as e:
        return _err(f"Error configuring compressor: {str(e)}")


@mcp.tool()
def setup_sidechain(ctx: Context, source_track: int, target_track: int, amount: float = 0.5) -> str:
    """Setup sidechain compression from source track to target track."""
    if not 0.0 <= amount <= 1.0:
        return _err(f"Invalid sidechain amount: {amount}. Must be 0.0-1.0.")
    return _proxy_ableton_command(
        "setup_sidechain",
        {"source_track": source_track, "target_track": target_track, "amount": amount},
        timeout=TIMEOUTS["setup_sidechain"],
        defaults={"source_track": source_track, "target_track": target_track, "amount": amount},
    )


@mcp.tool()
def auto_gain_staging(ctx: Context) -> str:
    """Automatically adjust gain staging for all tracks."""
    try:
        from engines.mixing_engine import get_gain_staging

        tracks_resp = _send_to_ableton("get_tracks", timeout=TIMEOUTS["get_tracks"])
        if tracks_resp.get("status") != "success":
            return _err(tracks_resp.get("message", "Failed to read tracks from Ableton"))

        tracks = _ableton_result(tracks_resp).get("tracks", [])
        track_config = [
            {"track_index": t.get("index", 0), "name": t.get("name", ""), "role": t.get("name", "")}
            for t in tracks
        ]

        result = get_gain_staging().auto_gain_staging(track_config)
        if result.get("success"):
            return _ok({
                "tracks_adjusted": result.get("total_tracks", 0),
                "adjustments": result.get("applied_levels", []),
                "headroom_ok": result.get("headroom_ok", False),
            })
        return _err(result.get("message", "Failed to adjust gain staging"))
    except Exception as e:
        return _err(f"Error in auto gain staging: {str(e)}")


@mcp.tool()
def apply_master_chain(ctx: Context, preset: str = "standard") -> str:
    """Apply a mastering chain to the master track."""
    try:
        from engines.mixing_engine import get_master_chain

        selected_preset = "reggaeton_streaming" if preset == "standard" else preset
        result = get_master_chain().apply_master_chain(selected_preset)
        if result.get("success"):
            return _ok({
                "preset": selected_preset,
                "devices_added": result.get("chain_applied", []),
                "master_track": "Master"
            })
        return _err(result.get("message", "Failed to apply master chain"))
    except Exception as e:
        return _err(f"Error applying master chain: {str(e)}")


@mcp.tool()
def create_parallel_compression(ctx: Context, track_index: int,
                                ratio: float = 4.0,
                                threshold: float = -20.0,
                                makeup_gain: float = 0.0,
                                preset: str = "",
                                name: str = "") -> str:
    """Create a parallel compression chain for punch and clarity.

    Implements New York-style parallel compression where:
    1. Original track remains uncompressed (dry)
    2. Duplicate track gets heavy compression (wet)
    3. Both are blended for punch and clarity

    Args:
        track_index: Index of the track to apply parallel compression
        ratio: Compression ratio (default 4.0). Ignored if preset is used.
        threshold: Threshold in dB (default -20.0). Ignored if preset is used.
        makeup_gain: Makeup gain in dB (default 0.0). Ignored if preset is used.
        preset: Preset name - "drum_parallel", "vocal_parallel", or "bus_parallel"
        name: Optional custom name for the compression chain

    Returns:
        JSON with chain creation status, track indices, and settings.

    Presets:
        drum_parallel: 8:1 ratio, fast attack (2ms), fast release (30ms), 35% wet
        vocal_parallel: 4:1 ratio, medium attack (8ms), medium release (80ms), 45% wet
        bus_parallel: 2:1 ratio, slow attack (15ms), slow release (150ms), 25% wet
    """
    try:
        resp = _send_to_ableton(
            "create_parallel_compression",
            {
                "track_index": track_index,
                "ratio": ratio,
                "threshold": threshold,
                "makeup_gain": makeup_gain,
                "preset": preset,
                "name": name,
            },
            timeout=TIMEOUTS["create_parallel_compression"]
        )
        if resp.get("status") == "success":
            return _ok(resp.get("result", {}))
        return _err(resp.get("message", "Failed to create parallel compression"))
    except Exception as e:
        return _err(f"Error creating parallel compression: {str(e)}")


# ==================================================================
# FASE 4: WORKFLOW & EXPORT
# ==================================================================

@mcp.tool()
def export_project(ctx: Context, path: str, format: str = "wav") -> str:
    """Export the project to audio file."""
    try:
        from engines.workflow_engine import WorkflowEngine
        engine = WorkflowEngine()
        result = engine.export_project(path, format)
        if result.get("success"):
            return _ok({
                "export_path": path,
                "format": format,
                "duration": result.get("duration"),
                "file_size": result.get("file_size")
            })
        return _err(result.get("message", "Failed to export project"))
    except Exception as e:
        return _err(f"Error exporting project: {str(e)}")


@mcp.tool()


@mcp.tool()
def discover_device_parameters(ctx: Context, track_index: int, device_index: int = None) -> str:
    """T090: Discover and enumerate all parameters for a device on a track.
    
    Agent 9: Device Parameter Discovery System
    
    This tool discovers all available parameters for a device, enabling
    intelligent parameter mapping and fuzzy matching for device control.
    
    Args:
        track_index: Index of the track containing the device
        device_index: Optional index of the device (if None, enumerates all devices)
        
    Returns:
        JSON with device information including:
        - track_index, track_name
        - device_count
        - devices: list with device_index, device_name, class_name, parameters
        - Each parameter includes: name, index, min, max, value, is_enabled
    """
    return _proxy_ableton_command(
        "discover_device_parameters",
        {"track_index": track_index, "device_index": device_index},
        timeout=TIMEOUTS.get("get_tracks", 15.0),
        defaults={"track_index": track_index, "device_index": device_index},
    )


def get_project_summary(ctx: Context) -> str:
    """Get a summary of the current project from Ableton Live."""
    try:
        resp = _send_to_ableton("get_session_info", timeout=5.0)
        if resp.get("status") != "success":
            return _err(f"Cannot get session info: {resp.get('message')}")
        session = resp.get("result", {})
        tracks_resp = _send_to_ableton("get_tracks", timeout=5.0)
        tracks = tracks_resp.get("result", {}).get("tracks", []) if tracks_resp.get("status") == "success" else []
        midi_count = sum(1 for t in tracks if t.get("is_midi"))
        audio_count = sum(1 for t in tracks if t.get("is_audio"))
        device_names = list(set(d for t in tracks for d in t.get("devices", [])))
        return _ok({
            "track_count": session.get("num_tracks", len(tracks)),
            "midi_tracks": midi_count,
            "audio_tracks": audio_count,
            "return_tracks": session.get("num_return_tracks", 0),
            "clips": sum(t.get("clip_slots", 0) for t in tracks),
            "scenes": session.get("num_scenes", 0),
            "devices_used": device_names[:20],
            "duration_minutes": 0,
            "project_name": "Live Project",
            "tempo": session.get("tempo", 0),
            "is_playing": session.get("is_playing", False),
        })
    except Exception as e:
        return _err(f"Error getting project summary: {str(e)}")


@mcp.tool()
def suggest_improvements(ctx: Context) -> str:
    """Get AI suggestions for improving the project."""
    try:
        from engines.workflow_engine import WorkflowEngine
        engine = WorkflowEngine()
        result = engine.suggest_improvements()
        return _ok({
            "suggestions": result.get("suggestions", []),
            "priority": result.get("priority", "medium"),
            "categories": result.get("categories", {}),
            "estimated_impact": result.get("estimated_impact", "medium")
        })
    except Exception as e:
        return _err(f"Error generating suggestions: {str(e)}")


@mcp.tool()
def validate_project(ctx: Context) -> str:
    """Validate project consistency and best practices using live Ableton data."""
    try:
        tracks_resp = _send_to_ableton("get_tracks", timeout=5.0)
        tracks = tracks_resp.get("result", {}).get("tracks", []) if tracks_resp.get("status") == "success" else []
        session_resp = _send_to_ableton("get_session_info", timeout=5.0)
        session = session_resp.get("result", {}) if session_resp.get("status") == "success" else {}
        issues = []
        warnings = []
        passed = []
        track_count = len(tracks)
        if track_count == 0:
            issues.append("No tracks in project")
        else:
            passed.append(f"{track_count} tracks found")
        midi_tracks = [t for t in tracks if t.get("is_midi")]
        audio_tracks = [t for t in tracks if t.get("is_audio")]
        if not midi_tracks and not audio_tracks:
            warnings.append("All tracks appear to be return or master tracks")
        if session.get("tempo", 0) < 60 or session.get("tempo", 0) > 200:
            warnings.append(f"Unusual tempo: {session.get('tempo')} BPM")
        else:
            passed.append(f"Tempo OK: {session.get('tempo')} BPM")
        muted = [t["name"] for t in tracks if t.get("mute")]
        if muted:
            warnings.append(f"Muted tracks: {', '.join(muted)}")
        empty = [t["name"] for t in tracks if t.get("clip_slots", 0) == 0]
        if empty:
            warnings.append(f"Tracks with no clip slots: {', '.join(empty)}")
        score = max(0, 100 - len(issues) * 25 - len(warnings) * 10)
        return _ok({
            "is_valid": len(issues) == 0,
            "issues": issues,
            "warnings": warnings,
            "passed_checks": passed,
            "score": score,
            "track_count": track_count,
            "midi_count": len(midi_tracks),
            "audio_count": len(audio_tracks),
        })
    except Exception as e:
        return _err(f"Error validating project: {str(e)}")


@mcp.tool()
def validate_session_production(ctx: Context, bpm: float = 95, key: str = "Am", 
                                 num_scenes: int = 8) -> str:
    """Validate Session View production for professional consistency.
    
    Performs comprehensive validation across four critical dimensions:
    
    1. **BPM Coherence**: Verifies all loaded samples are within ±5 BPM of project tempo
    2. **Key Harmony**: Verifies all MIDI clips use the correct key/scale
    3. **Sample Rotation**: Verifies no consecutive scenes use the same sample
    4. **Energy Matching**: Verifies sample RMS matches scene energy requirements
    
    Args:
        bpm: Project tempo in BPM (default 95)
        key: Musical key (default "Am")
        num_scenes: Number of scenes to validate (default 8)
    
    Returns:
        JSON with validation results:
        - bpm_coherence: Score and details
        - key_harmony: Score and details
        - sample_rotation: Score and details
        - energy_matching: Score and details
        - overall_score: Average score (0.0-1.0)
        - passed: True if overall_score >= 0.85
        - summary: Human-readable summary
        - detailed_report: Full violation report
    
    Example:
        validate_session_production(bpm=95, key="Am", num_scenes=13)
    """
    try:
        logger.info(f"Validating Session View production: {bpm} BPM, {key}, {num_scenes} scenes")
        
        from engines import SessionValidator, init_metadata_store
        
        # Initialize metadata store
        ms = init_metadata_store()
        
        # Get song object from Ableton
        from AbletonMCP_AI import get_song
        song = get_song()
        
        # Create validator and run validation
        validator = SessionValidator(song, ms)
        results = validator.validate_production(bpm, key, num_scenes)
        
        # Generate detailed report
        detailed_report = validator.get_detailed_report(results)
        
        # Format response
        response = {
            "status": "success",
            "validation_results": results,
            "detailed_report": detailed_report,
        }
        
        # Add recommendations if failed
        if not results['passed']:
            response["recommendations"] = [
                "Review samples with BPM outside ±5 tolerance",
                "Transpose MIDI clips to match project key",
                "Vary samples between consecutive scenes",
                "Select samples with appropriate energy for each section"
            ]
        
        return json.dumps(response, indent=2)
        
    except Exception as e:
        logger.error(f"Error in validate_session_production: {e}")
        return _err(f"Error validating Session View production: {str(e)}")


@mcp.tool()
def humanize_track(ctx: Context, track_index: int, intensity: float = 0.5) -> str:
    """Apply humanization to a MIDI track (velocity and timing variations)."""
    if not 0.0 <= intensity <= 1.0:
        return _err(f"Invalid intensity: {intensity}. Must be 0.0-1.0.")
    return _proxy_ableton_command(
        "humanize_track",
        {"track_index": track_index, "intensity": intensity},
        timeout=TIMEOUTS["humanize_track"],
        defaults={"track_index": track_index, "intensity": intensity},
    )


# ==================================================================
# FASE 5: PHASE 1 - BRIDGE ENGINES â†’ ABLETON (T001-T015 + T081-T085)
# ==================================================================

# ------------------------------------------------------------------
# Production Pipeline Tools (T081-T085)
# ------------------------------------------------------------------

@mcp.tool()
def produce_reggaeton(ctx: Context, bpm: float = 95, key: str = "Am",
                        style: str = "classic", structure: str = "verse-chorus",
                        record_arrangement: bool = True) -> str:
    """Generate a complete reggaeton production pipeline (T081) - Session View based.

    DEPRECATED: Consider using build_arrangement_timeline() for direct Arrangement View creation.

    This tool creates content in Session View clips first. For direct timeline-based
    composition without the Session View intermediate step, use build_arrangement_timeline().

    MIGRATION GUIDE:
    - OLD: produce_reggaeton() â†’ Session View clips â†’ manual arrangement
    - NEW: build_arrangement_timeline() â†’ Direct Arrangement View placement

    Args:
        bpm: Tempo in BPM (default 95)
        key: Musical key (default Am)
        style: Reggaeton style (classic, dembow, perreo, moombahton)
        structure: Song structure (verse-chorus, full, intro-drop)
        record_arrangement: Record to Arrangement View automatically (default True)

    Returns:
        JSON with complete production summary.
    """
    try:
        logger.info("produce_reggaeton: start bpm=%s key=%s style=%s structure=%s", bpm, key, style, structure)
        from engines.production_workflow import ProductionWorkflow
        workflow = ProductionWorkflow()
        result = workflow.produce_reggaeton(
            bpm=bpm, key=key, style=style, structure=structure,
            record_arrangement=record_arrangement
        )
        logger.info("produce_reggaeton: workflow returned")
        return _ok({
            "production_type": "reggaeton",
            "bpm": bpm,
            "key": key,
            "style": style,
            "structure": structure,
            "record_arrangement": record_arrangement,
            "tracks_created": result.get("tracks", []),
            "clips_generated": result.get("clips", []),
            "duration_bars": result.get("duration_bars", 64),
        })
    except ImportError:
        logger.exception("produce_reggaeton: import error")
        return _err("Production workflow engine not available.")
    except Exception as e:
        logger.exception("produce_reggaeton: failed")
        return _err(f"Error producing reggaeton: {str(e)}")


@mcp.tool()
def produce_from_reference(ctx: Context, audio_path: str) -> str:
    """Generate production from a reference audio file (T082).

    Analyzes the reference audio and generates a matching production.

    Args:
        audio_path: Path to the reference audio file (.mp3, .wav)

    Returns:
        JSON with production details and similarity analysis.
    """
    if not os.path.isfile(audio_path):
        return _err(f"Reference audio not found: {audio_path}")
    try:
        from engines.production_workflow import ProductionWorkflow
        workflow = ProductionWorkflow()
        result = workflow.produce_from_reference(reference_path=audio_path)
        return _ok({
            "reference": audio_path,
            "production_type": "from_reference",
            **(result if isinstance(result, dict) else {"result": result}),
        })
    except ImportError:
        return _err("Production workflow or reference matcher engine not available.")
    except Exception as e:
        return _err(f"Error producing from reference: {str(e)}")


@mcp.tool()
def produce_arrangement(ctx: Context, bpm: float = 95, key: str = "Am",
                        style: str = "classic") -> str:
    """Generate production directly in Arrangement View (T083).

    Creates a complete song structure in Arrangement View.

    Args:
        bpm: Tempo in BPM (default 95)
        key: Musical key (default Am)
        style: Production style (classic, modern, perreo, moombahton)

    Returns:
        JSON with arrangement details and clip positions.
    """
    try:
        from engines.production_workflow import ProductionWorkflow
        workflow = ProductionWorkflow()
        result = workflow.produce_arrangement(
            bpm=bpm, key=key, style=style
        )
        return _ok({
            "production_type": "arrangement",
            "view": "Arrangement",
            "bpm": bpm,
            "key": key,
            "style": style,
            "tracks_created": result.get("tracks", []),
            "clips_arranged": result.get("clips", []),
            "total_bars": result.get("total_bars", 128),
        })
    except ImportError:
        return _err("Production workflow engine not available.")
    except Exception as e:
        return _err(f"Error producing arrangement: {str(e)}")


@mcp.tool()
def complete_production(ctx: Context, bpm: float = 95, key: str = "Am",
                        style: str = "classic", output_dir: str = "") -> str:
    """Complete production pipeline with render (T084).

    Generates a full production and renders it to audio.

    Args:
        bpm: Tempo in BPM (default 95)
        key: Musical key (default Am)
        style: Production style
        output_dir: Directory for rendered output (optional)

    Returns:
        JSON with production summary and render path.
    """
    try:
        from engines.production_workflow import ProductionWorkflow
        from engines.workflow_engine import WorkflowEngine
        workflow = ProductionWorkflow()
        result = workflow.complete_production(
            bpm=bpm, key=key, style=style
        )
        render_path = ""
        if output_dir and os.path.isdir(output_dir):
            wf_engine = WorkflowEngine()
            render_result = wf_engine.export_project(
                path=os.path.join(output_dir, f"production_{int(time.time())}.wav"),
                format="wav"
            )
            render_path = render_result.get("export_path", "")
        return _ok({
            "production_type": "complete",
            "bpm": bpm,
            "key": key,
            "style": style,
            "tracks_created": result.get("tracks", []),
            "clips_generated": result.get("clips", []),
            "render_path": render_path,
        })
    except ImportError:
        return _err("Production workflow engine not available.")
    except Exception as e:
        return _err(f"Error in complete production: {str(e)}")


@mcp.tool()
def batch_produce(ctx: Context, count: int = 3, style: str = "classic",
                  bpm_range: str = "90-100") -> str:
    """Batch produce multiple songs (T085).

    Generates multiple productions with varying parameters.

    Args:
        count: Number of songs to produce (default 3, max 10)
        style: Production style
        bpm_range: BPM range as "min-max" string

    Returns:
        JSON with batch production summary.
    """
    if not 1 <= count <= 10:
        return _err(f"Invalid count: {count}. Must be 1-10.")
    try:
        from engines.production_workflow import ProductionWorkflow
        workflow = ProductionWorkflow()
        results = []
        bpms = []
        if "-" in bpm_range:
            parts = bpm_range.split("-")
            bpm_min, bpm_max = int(parts[0]), int(parts[1])
            import random
            bpms = [random.randint(bpm_min, bpm_max) for _ in range(count)]
        else:
            bpms = [int(bpm_range)] * count
        keys = ["Am", "Dm", "Em", "Gm", "Cm"]
        for i in range(count):
            result = workflow.produce_reggaeton(
                bpm=bpms[i],
                key=keys[i % len(keys)],
                style=style,
                structure="verse-chorus"
            )
            results.append({
                "index": i + 1,
                "bpm": bpms[i],
                "key": keys[i % len(keys)],
                "tracks": len(result.get("tracks", [])),
            })
        return _ok({
            "batch_size": count,
            "style": style,
            "bpm_range": bpm_range,
            "productions": results,
        })
    except ImportError:
        return _err("Production workflow engine not available.")
    except Exception as e:
        return _err(f"Error in batch production: {str(e)}")


# ------------------------------------------------------------------
# MIDI Clip Generator Tools (T001-T005)
# ------------------------------------------------------------------

@mcp.tool()
def generate_midi_clip(ctx: Context, track_index: int, clip_index: int = 0,
                       notes: list = None) -> str:
    """Create a MIDI clip with specified notes (T001).

    Args:
        track_index: Index of the target track
        clip_index: Index of the clip slot (default 0)
        notes: List of note dicts with pitch, start_time, duration, velocity

    Returns:
        JSON with clip creation status.
    """
    if notes is None:
        notes = []
    try:
        resp = _send_to_ableton(
            "create_clip",
            {"track_index": track_index, "clip_index": clip_index, "length": 4.0},
            timeout=TIMEOUTS["generate_midi_clip"]
        )
        if resp.get("status") == "success" and notes:
            resp2 = _send_to_ableton(
                "add_notes_to_clip",
                {"track_index": track_index, "clip_index": clip_index, "notes": notes},
                timeout=TIMEOUTS["generate_midi_clip"]
            )
            if resp2.get("status") == "success":
                return _ok({
                    "track_index": track_index,
                    "clip_index": clip_index,
                    "notes_added": len(notes),
                })
            return _err(resp2.get("message", "Failed to add notes"))
        return _ok({
            "track_index": track_index,
            "clip_index": clip_index,
            "notes_added": 0,
            "created_empty": True,
        })
    except Exception as e:
        return _err(f"Error generating MIDI clip: {str(e)}")


@mcp.tool()
def generate_dembow_clip(ctx: Context, track_index: int, clip_index: int = 0,
                          bars: int = 4, variation: str = "standard") -> str:
    """Generate a dembow rhythm MIDI clip (T002).

    Creates a classic reggaeton dembow pattern.

    Args:
        track_index: Index of the target track
        clip_index: Index of the clip slot (default 0)
        bars: Number of bars (default 4)
        variation: Pattern variation (standard, minimal, complex, fill)

    Returns:
        JSON with clip generation status.
    """
    try:
        patterns = {
            "standard": [
                {"pitch": 36, "start_time": 0.0, "duration": 0.25, "velocity": 100},
                {"pitch": 42, "start_time": 0.25, "duration": 0.25, "velocity": 80},
                {"pitch": 38, "start_time": 0.5, "duration": 0.25, "velocity": 90},
                {"pitch": 42, "start_time": 0.75, "duration": 0.25, "velocity": 80},
                {"pitch": 36, "start_time": 1.0, "duration": 0.25, "velocity": 100},
                {"pitch": 42, "start_time": 1.25, "duration": 0.25, "velocity": 80},
                {"pitch": 38, "start_time": 1.5, "duration": 0.25, "velocity": 90},
                {"pitch": 42, "start_time": 1.75, "duration": 0.25, "velocity": 80},
            ],
            "minimal": [
                {"pitch": 36, "start_time": 0.0, "duration": 0.5, "velocity": 100},
                {"pitch": 42, "start_time": 0.5, "duration": 0.5, "velocity": 80},
                {"pitch": 36, "start_time": 1.0, "duration": 0.5, "velocity": 100},
                {"pitch": 42, "start_time": 1.5, "duration": 0.5, "velocity": 80},
            ],
            "complex": [
                {"pitch": 36, "start_time": 0.0, "duration": 0.25, "velocity": 100},
                {"pitch": 42, "start_time": 0.125, "duration": 0.125, "velocity": 70},
                {"pitch": 42, "start_time": 0.25, "duration": 0.25, "velocity": 80},
                {"pitch": 38, "start_time": 0.5, "duration": 0.25, "velocity": 90},
                {"pitch": 42, "start_time": 0.625, "duration": 0.125, "velocity": 70},
                {"pitch": 42, "start_time": 0.75, "duration": 0.25, "velocity": 80},
                {"pitch": 36, "start_time": 1.0, "duration": 0.25, "velocity": 100},
                {"pitch": 42, "start_time": 1.125, "duration": 0.125, "velocity": 70},
                {"pitch": 42, "start_time": 1.25, "duration": 0.25, "velocity": 80},
                {"pitch": 38, "start_time": 1.5, "duration": 0.25, "velocity": 90},
                {"pitch": 42, "start_time": 1.75, "duration": 0.25, "velocity": 80},
            ],
            "fill": [
                {"pitch": 36, "start_time": 0.0, "duration": 0.25, "velocity": 100},
                {"pitch": 38, "start_time": 0.25, "duration": 0.25, "velocity": 100},
                {"pitch": 42, "start_time": 0.5, "duration": 0.25, "velocity": 100},
                {"pitch": 38, "start_time": 0.75, "duration": 0.25, "velocity": 100},
            ],
        }
        notes = patterns.get(variation, patterns["standard"])
        full_notes = []
        for bar in range(bars):
            for note in notes:
                full_notes.append({
                    "pitch": note["pitch"],
                    "start_time": note["start_time"] + (bar * 2.0),
                    "duration": note["duration"],
                    "velocity": note["velocity"],
                })
        resp = _send_to_ableton(
            "create_clip",
            {"track_index": track_index, "clip_index": clip_index, "length": float(bars * 2)},
            timeout=TIMEOUTS["generate_dembow_clip"]
        )
        if resp.get("status") == "success":
            resp2 = _send_to_ableton(
                "add_notes_to_clip",
                {"track_index": track_index, "clip_index": clip_index, "notes": full_notes},
                timeout=TIMEOUTS["generate_dembow_clip"]
            )
            if resp2.get("status") == "success":
                return _ok({
                    "track_index": track_index,
                    "clip_index": clip_index,
                    "variation": variation,
                    "bars": bars,
                    "notes_added": len(full_notes),
                })
        return _err(resp.get("message", "Failed to create dembow clip"))
    except Exception as e:
        return _err(f"Error generating dembow clip: {str(e)}")


@mcp.tool()
def generate_bass_clip(ctx: Context, track_index: int, clip_index: int = 0,
                        bars: int = 4, root_notes: list = None, style: str = "standard") -> str:
    """Generate a bassline MIDI clip (T003).

    Creates a reggaeton-style bassline pattern.

    Args:
        track_index: Index of the target track
        clip_index: Index of the clip slot (default 0)
        bars: Number of bars (default 4)
        root_notes: List of root note pitches (default [36, 36, 36, 36])
        style: Bass style (standard, melodic, staccato, slides)

    Returns:
        JSON with clip generation status.
    """
    if root_notes is None:
        root_notes = [36] * 4
    try:
        notes = []
        base_octave = 36
        for bar in range(bars):
            root = root_notes[bar % len(root_notes)] if root_notes else base_octave
            if style == "standard":
                notes.extend([
                    {"pitch": root, "start_time": bar * 2.0, "duration": 0.5, "velocity": 100},
                    {"pitch": root, "start_time": bar * 2.0 + 0.5, "duration": 0.5, "velocity": 90},
                    {"pitch": root, "start_time": bar * 2.0 + 1.0, "duration": 0.5, "velocity": 100},
                    {"pitch": root + 7, "start_time": bar * 2.0 + 1.5, "duration": 0.5, "velocity": 80},
                ])
            elif style == "melodic":
                notes.extend([
                    {"pitch": root, "start_time": bar * 2.0, "duration": 0.75, "velocity": 100},
                    {"pitch": root + 4, "start_time": bar * 2.0 + 0.75, "duration": 0.25, "velocity": 80},
                    {"pitch": root + 7, "start_time": bar * 2.0 + 1.0, "duration": 0.5, "velocity": 90},
                    {"pitch": root, "start_time": bar * 2.0 + 1.5, "duration": 0.5, "velocity": 85},
                ])
            elif style == "staccato":
                notes.extend([
                    {"pitch": root, "start_time": bar * 2.0, "duration": 0.125, "velocity": 110},
                    {"pitch": root, "start_time": bar * 2.0 + 0.5, "duration": 0.125, "velocity": 100},
                    {"pitch": root, "start_time": bar * 2.0 + 1.0, "duration": 0.125, "velocity": 110},
                    {"pitch": root, "start_time": bar * 2.0 + 1.5, "duration": 0.125, "velocity": 100},
                ])
            else:  # slides or default
                notes.extend([
                    {"pitch": root, "start_time": bar * 2.0, "duration": 1.0, "velocity": 100},
                    {"pitch": root + 12, "start_time": bar * 2.0 + 1.0, "duration": 0.25, "velocity": 90},
                    {"pitch": root, "start_time": bar * 2.0 + 1.5, "duration": 0.5, "velocity": 80},
                ])
        resp = _send_to_ableton(
            "create_clip",
            {"track_index": track_index, "clip_index": clip_index, "length": float(bars * 2)},
            timeout=TIMEOUTS["generate_bass_clip"]
        )
        if resp.get("status") == "success":
            resp2 = _send_to_ableton(
                "add_notes_to_clip",
                {"track_index": track_index, "clip_index": clip_index, "notes": notes},
                timeout=TIMEOUTS["generate_bass_clip"]
            )
            if resp2.get("status") == "success":
                return _ok({
                    "track_index": track_index,
                    "clip_index": clip_index,
                    "style": style,
                    "bars": bars,
                    "notes_added": len(notes),
                })
        return _err(resp.get("message", "Failed to create bass clip"))
    except Exception as e:
        return _err(f"Error generating bass clip: {str(e)}")


@mcp.tool()
def generate_chords_clip(ctx: Context, track_index: int, clip_index: int = 0,
                         bars: int = 4, progression: str = "i-v-vi-iv", key: str = "Am") -> str:
    """Generate a chord progression MIDI clip (T004).

    Creates chord patterns for reggaeton progressions.

    Args:
        track_index: Index of the target track
        clip_index: Index of the clip slot (default 0)
        bars: Number of bars (default 4)
        progression: Roman numeral progression (default "i-v-vi-iv")
        key: Musical key (default Am)

    Returns:
        JSON with clip generation status.
    """
    try:
        progressions = {
            "i-v-vi-iv": [0, 7, 9, 5],
            "i-iv-v": [0, 5, 7],
            "i-vi-iv-v": [0, 9, 5, 7],
            "i-v-i-v": [0, 7, 0, 7],
            "i-iv-i-v": [0, 5, 0, 7],
        }
        offsets = progressions.get(progression, progressions["i-v-vi-iv"])
        base_note = 48 if key.endswith("m") else 60
        if key.startswith("C"): base_note = 48 if key.endswith("m") else 60
        elif key.startswith("D"): base_note = 50 if key.endswith("m") else 62
        elif key.startswith("E"): base_note = 52 if key.endswith("m") else 64
        elif key.startswith("F"): base_note = 53 if key.endswith("m") else 65
        elif key.startswith("G"): base_note = 55 if key.endswith("m") else 67
        elif key.startswith("A"): base_note = 45 if key.endswith("m") else 57
        elif key.startswith("B"): base_note = 47 if key.endswith("m") else 59
        notes = []
        chord_length = bars // len(offsets) if bars >= len(offsets) else 1
        for i, offset in enumerate(offsets):
            for bar in range(chord_length):
                root = base_note + offset
                if key.endswith("m"):
                    notes.extend([
                        {"pitch": root, "start_time": i * chord_length * 2.0 + bar * 2.0, "duration": 2.0, "velocity": 70},
                        {"pitch": root + 3, "start_time": i * chord_length * 2.0 + bar * 2.0, "duration": 2.0, "velocity": 70},
                        {"pitch": root + 7, "start_time": i * chord_length * 2.0 + bar * 2.0, "duration": 2.0, "velocity": 70},
                    ])
                else:
                    notes.extend([
                        {"pitch": root, "start_time": i * chord_length * 2.0 + bar * 2.0, "duration": 2.0, "velocity": 70},
                        {"pitch": root + 4, "start_time": i * chord_length * 2.0 + bar * 2.0, "duration": 2.0, "velocity": 70},
                        {"pitch": root + 7, "start_time": i * chord_length * 2.0 + bar * 2.0, "duration": 2.0, "velocity": 70},
                    ])
        resp = _send_to_ableton(
            "create_clip",
            {"track_index": track_index, "clip_index": clip_index, "length": float(bars * 2)},
            timeout=TIMEOUTS["generate_chords_clip"]
        )
        if resp.get("status") == "success":
            resp2 = _send_to_ableton(
                "add_notes_to_clip",
                {"track_index": track_index, "clip_index": clip_index, "notes": notes},
                timeout=TIMEOUTS["generate_chords_clip"]
            )
            if resp2.get("status") == "success":
                return _ok({
                    "track_index": track_index,
                    "clip_index": clip_index,
                    "progression": progression,
                    "key": key,
                    "bars": bars,
                    "notes_added": len(notes),
                })
        return _err(resp.get("message", "Failed to create chords clip"))
    except Exception as e:
        return _err(f"Error generating chords clip: {str(e)}")


@mcp.tool()
def generate_melody_clip(ctx: Context, track_index: int, clip_index: int = 0,
                        bars: int = 4, scale: str = "minor", density: str = "medium") -> str:
    """Generate a melodic line MIDI clip (T005).

    Creates a melody pattern for reggaeton.

    Args:
        track_index: Index of the target track
        clip_index: Index of the clip slot (default 0)
        bars: Number of bars (default 4)
        scale: Scale type (minor, major, harmonic_minor, pentatonic)
        density: Note density (sparse, medium, dense)

    Returns:
        JSON with clip generation status.
    """
    try:
        scales = {
            "minor": [60, 62, 63, 65, 67, 68, 70, 72],
            "major": [60, 62, 64, 65, 67, 69, 71, 72],
            "harmonic_minor": [60, 62, 63, 65, 67, 68, 71, 72],
            "pentatonic": [60, 62, 64, 67, 69, 72],
        }
        scale_notes = scales.get(scale, scales["minor"])
        density_ratios = {"sparse": 0.25, "medium": 0.5, "dense": 0.75}
        ratio = density_ratios.get(density, 0.5)
        import random
        random.seed(42)
        notes = []
        sixteenth = 2.0 / 16
        for bar in range(bars):
            for step in range(16):
                if random.random() < ratio:
                    note_pitch = random.choice(scale_notes)
                    start = bar * 2.0 + step * sixteenth
                    duration = sixteenth * random.choice([1, 2, 4])
                    velocity = random.randint(70, 110)
                    notes.append({
                        "pitch": note_pitch,
                        "start_time": start,
                        "duration": duration,
                        "velocity": velocity,
                    })
        resp = _send_to_ableton(
            "create_clip",
            {"track_index": track_index, "clip_index": clip_index, "length": float(bars * 2)},
            timeout=TIMEOUTS["generate_melody_clip"]
        )
        if resp.get("status") == "success":
            resp2 = _send_to_ableton(
                "add_notes_to_clip",
                {"track_index": track_index, "clip_index": clip_index, "notes": notes},
                timeout=TIMEOUTS["generate_melody_clip"]
            )
            if resp2.get("status") == "success":
                return _ok({
                    "track_index": track_index,
                    "clip_index": clip_index,
                    "scale": scale,
                    "density": density,
                    "bars": bars,
                    "notes_added": len(notes),
                })
        return _err(resp.get("message", "Failed to create melody clip"))
    except Exception as e:
        return _err(f"Error generating melody clip: {str(e)}")


# ------------------------------------------------------------------
# Sample Management Tools (T006-T010)
# ------------------------------------------------------------------

@mcp.tool()
def load_samples_for_genre(ctx: Context, genre: str, key: str = "", bpm: float = 0) -> str:
    """Select and load samples for a genre (T008).

    This is an alias for select_samples_for_genre with additional auto-loading.

    Args:
        genre: Genre to select samples for
        key: Musical key filter (optional)
        bpm: BPM filter (optional)

    Returns:
        JSON with selected samples info.
    """
    try:
        from engines.sample_selector import SampleSelector, get_selector
        selector = get_selector()
        if selector is None:
            return _err("Sample selector not available. Check libreria/reggaeton path.")
        group = selector.select_for_genre(genre, key if key else None, bpm if bpm > 0 else None)
        result = {
            "genre": group.genre,
            "key": group.key,
            "bpm": group.bpm,
            "drums": {},
            "bass": [],
            "synths": [],
            "fx": [],
        }
        kit = group.drums
        if kit.kick:
            result["drums"]["kick"] = kit.kick.name
        if kit.snare:
            result["drums"]["snare"] = kit.snare.name
        if kit.clap:
            result["drums"]["clap"] = kit.clap.name
        if kit.hat_closed:
            result["drums"]["hat_closed"] = kit.hat_closed.name
        if kit.hat_open:
            result["drums"]["hat_open"] = kit.hat_open.name
        result["bass"] = [s.name for s in (group.bass or [])[:5]]
        result["synths"] = [s.name for s in (group.synths or [])[:5]]
        result["fx"] = [s.name for s in (group.fx or [])[:3]]
        return _ok(result)
    except ImportError:
        return _err("Sample selector engine not available.")
    except Exception as e:
        return _err(f"Error loading samples for genre: {str(e)}")


@mcp.tool()
def create_drum_kit(ctx: Context, track_index: int, kick_path: str = "",
                    snare_path: str = "", hat_path: str = "", clap_path: str = "") -> str:
    """Create a drum kit by loading samples into a Drum Rack (T009).

    Args:
        track_index: Index of the track containing the Drum Rack
        kick_path: Path to kick sample (optional)
        snare_path: Path to snare sample (optional)
        hat_path: Path to hi-hat sample (optional)
        clap_path: Path to clap sample (optional)

    Returns:
        JSON with kit creation status.
    """
    try:
        samples = [
            (kick_path, 36),
            (snare_path, 38),
            (hat_path, 42),
            (clap_path, 39),
        ]
        loaded = []
        errors = []
        for path, note in samples:
            if path and os.path.isfile(path):
                resp = _send_to_ableton(
                    "load_sample_to_drum_rack",
                    {"track_index": track_index, "sample_path": path, "pad_note": note},
                    timeout=TIMEOUTS["create_drum_kit"]
                )
                if resp.get("status") == "success":
                    loaded.append({"note": note, "path": path})
                else:
                    errors.append({"note": note, "error": resp.get("message", "unknown")})
            elif path:
                errors.append({"note": note, "error": f"File not found: {path}"})
        return _ok({
            "track_index": track_index,
            "samples_loaded": len(loaded),
            "loaded": loaded,
            "errors": errors,
        })
    except Exception as e:
        return _err(f"Error creating drum kit: {str(e)}")


@mcp.tool()
def build_track_from_samples(ctx: Context, track_type: str = "drums",
                              sample_role: str = "drums") -> str:
    """Build a complete track from library samples (T010).

    Creates a track and loads appropriate samples automatically.

    Args:
        track_type: Type of track (drums, bass, melody, fx)
        sample_role: Sample role to filter by (drums, bass, synths, fx)

    Returns:
        JSON with track creation and sample loading status.
    """
    try:
        from engines.sample_selector import get_selector
        selector = get_selector()
        if selector is None:
            return _err("Sample selector not available.")
        resp = _send_to_ableton(
            "create_audio_track",
            {"index": -1},
            timeout=TIMEOUTS["build_track_from_samples"]
        )
        if resp.get("status") != "success":
            return _err("Failed to create audio track")
        track_index = resp.get("track_index", -1)
        if track_index < 0:
            return _err("Invalid track index returned")
        _send_to_ableton(
            "set_track_name",
            {"track_index": track_index, "name": f"{track_type.title()} Track"},
            timeout=TIMEOUTS["build_track_from_samples"]
        )
        samples = selector.get_samples_by_role(sample_role)[:4]
        loaded = []
        for i, sample in enumerate(samples):
            clip_resp = _send_to_ableton(
                "load_sample_to_clip",
                {"track_index": track_index, "clip_index": i, "sample_path": sample.path},
                timeout=TIMEOUTS["build_track_from_samples"]
            )
            if clip_resp.get("status") == "success":
                loaded.append({"index": i, "sample": sample.name})
        return _ok({
            "track_type": track_type,
            "track_index": track_index,
            "samples_loaded": len(loaded),
            "samples": loaded,
        })
    except ImportError:
        return _err("Sample selector engine not available.")
    except Exception as e:
        return _err(f"Error building track from samples: {str(e)}")


# ------------------------------------------------------------------
# Configuration-Based Generators (T011-T015)
# ------------------------------------------------------------------

@mcp.tool()
def generate_full_song(ctx: Context, bpm: float = 95, key: str = "Am",
                       style: str = "classic", structure: str = "standard") -> str:
    """Generate a complete song with multiple elements (T011).

    This is an enhanced version that creates drums, bass, chords, and melody.

    Args:
        bpm: Tempo in BPM (default 95)
        key: Musical key (default Am)
        style: Song style (classic, modern, perreo, moombahton)
        structure: Song structure (standard, verse-chorus, full)

    Returns:
        JSON with song generation summary.
    """
    try:
        from engines.production_workflow import ProductionWorkflow
        workflow = ProductionWorkflow()
        result = workflow.generate_song(
            genre="reggaeton",
            bpm=bpm,
            key=key,
            style=style,
            structure=structure
        )
        return _ok({
            "song_type": "full",
            "bpm": bpm,
            "key": key,
            "style": style,
            "structure": structure,
            "tracks_created": result.get("tracks", []),
            "clips_generated": result.get("clips", []),
            "duration_bars": result.get("duration_bars", 128),
        })
    except ImportError:
        return _err("Production workflow engine not available.")
    except Exception as e:
        return _err(f"Error generating full song: {str(e)}")


@mcp.tool()
def generate_track_from_config(ctx: Context, track_config_json: str) -> str:
    """Generate a track from a JSON configuration (T012).

    Flexible track generation using a configuration object.

    Args:
        track_config_json: JSON string with track configuration
            Example: '{"type": "drums", "pattern": "dembow", "bars": 8}'

    Returns:
        JSON with track generation status.
    """
    try:
        import json as json_lib
        config = json_lib.loads(track_config_json)
        track_type = config.get("type", "drums")
        resp = _send_to_ableton(
            "create_midi_track",
            {"index": -1},
            timeout=TIMEOUTS["generate_track_from_config"]
        )
        if resp.get("status") != "success":
            return _err("Failed to create MIDI track")
        track_index = resp.get("track_index", -1)
        _send_to_ableton(
            "set_track_name",
            {"track_index": track_index, "name": config.get("name", f"{track_type.title()} Track")},
            timeout=TIMEOUTS["generate_track_from_config"]
        )
        if track_type == "drums":
            pattern = config.get("pattern", "dembow")
            bars = config.get("bars", 4)
            if pattern == "dembow":
                return generate_dembow_clip(ctx, track_index, 0, bars, "standard")
        elif track_type == "bass":
            bars = config.get("bars", 4)
            root_notes = config.get("root_notes", [36])
            style = config.get("style", "standard")
            return generate_bass_clip(ctx, track_index, 0, bars, root_notes, style)
        elif track_type == "chords":
            bars = config.get("bars", 4)
            progression = config.get("progression", "i-v-vi-iv")
            key = config.get("key", "Am")
            return generate_chords_clip(ctx, track_index, 0, bars, progression, key)
        elif track_type == "melody":
            bars = config.get("bars", 4)
            scale = config.get("scale", "minor")
            density = config.get("density", "medium")
            return generate_melody_clip(ctx, track_index, 0, bars, scale, density)
        return _ok({
            "track_type": track_type,
            "track_index": track_index,
            "config": config,
            "status": "created",
        })
    except json_lib.JSONDecodeError:
        return _err("Invalid JSON configuration")
    except Exception as e:
        return _err(f"Error generating track from config: {str(e)}")


@mcp.tool()
def generate_section(ctx: Context, section_config_json: str, start_bar: int = 0) -> str:
    """Generate a song section from JSON config (T013).

    Creates a section (verse, chorus, intro, etc.) at the specified position.

    Args:
        section_config_json: JSON string with section configuration
            Example: '{"type": "verse", "bars": 16, "elements": ["drums", "bass"]}'
        start_bar: Starting bar position in the song

    Returns:
        JSON with section generation status.
    """
    try:
        import json as json_lib
        config = json_lib.loads(section_config_json)
        section_type = config.get("type", "verse")
        bars = config.get("bars", 8)
        elements = config.get("elements", ["drums"])
        tracks_created = []
        for element in elements:
            element_config = {
                "type": element,
                "bars": bars,
                "name": f"{section_type.title()} {element.title()}",
            }
            if element == "drums":
                element_config["pattern"] = "dembow"
            result = generate_track_from_config(ctx, json_lib.dumps(element_config))
            tracks_created.append({"element": element, "result": result})
        return _ok({
            "section_type": section_type,
            "start_bar": start_bar,
            "bars": bars,
            "elements": elements,
            "tracks_created": len(tracks_created),
        })
    except json_lib.JSONDecodeError:
        return _err("Invalid JSON configuration")
    except Exception as e:
        return _err(f"Error generating section: {str(e)}")


@mcp.tool()
def apply_human_feel(ctx: Context, track_index: int, intensity: float = 0.5) -> str:
    """Apply humanization feel to a MIDI track (T014).

    Adds velocity and timing variations for a more natural feel.

    Args:
        track_index: Index of the track to humanize
        intensity: Humanization intensity 0.0-1.0 (default 0.5)

    Returns:
        JSON with humanization status.
    """
    if not 0.0 <= intensity <= 1.0:
        return _err(f"Invalid intensity: {intensity}. Must be 0.0-1.0.")
    try:
        resp = _send_to_ableton(
            "humanize_track",
            {"track_index": track_index, "intensity": intensity},
            timeout=TIMEOUTS["apply_human_feel"]
        )
        if resp.get("status") == "success":
            return _ok({
                "track_index": track_index,
                "intensity": intensity,
                "notes_affected": resp.get("notes_affected", 0),
                "velocity_variation": resp.get("velocity_variation", 0),
                "timing_variation": resp.get("timing_variation", 0),
            })
        return _err(resp.get("message", "Failed to apply human feel"))
    except Exception as e:
        return _err(f"Error applying human feel: {str(e)}")


@mcp.tool()
def add_percussion_fills(ctx: Context, track_index: int, positions: list = None) -> str:
    """Add percussion fills at specified positions (T015).

    Inserts drum fills at specific bars in the arrangement.

    Args:
        track_index: Index of the percussion track
        positions: List of bar positions for fills (default [7, 15, 23, 31])

    Returns:
        JSON with fills addition status.
    """
    if positions is None:
        positions = [7, 15, 23, 31]
    try:
        fill_pattern = [
            {"pitch": 38, "start_time": 0.0, "duration": 0.125, "velocity": 110},
            {"pitch": 42, "start_time": 0.25, "duration": 0.125, "velocity": 100},
            {"pitch": 38, "start_time": 0.5, "duration": 0.125, "velocity": 110},
            {"pitch": 36, "start_time": 0.75, "duration": 0.125, "velocity": 120},
        ]
        fills_added = []
        for pos in positions:
            full_fill = []
            for note in fill_pattern:
                full_fill.append({
                    "pitch": note["pitch"],
                    "start_time": note["start_time"] + pos * 2.0,
                    "duration": note["duration"],
                    "velocity": note["velocity"],
                })
            resp = _send_to_ableton(
                "add_notes_to_clip",
                {"track_index": track_index, "clip_index": 0, "notes": full_fill},
                timeout=TIMEOUTS["add_percussion_fills"]
            )
            if resp.get("status") == "success":
                fills_added.append({"position": pos, "notes": len(full_fill)})
        return _ok({
            "track_index": track_index,
            "fills_added": len(fills_added),
            "positions": positions,
            "details": fills_added,
        })
    except Exception as e:
        return _err(f"Error adding percussion fills: {str(e)}")


# ==================================================================
# FASE 6: PHASE 2 - ARRANGEMENT & AUTOMATION (T021-T026)
# ==================================================================

@mcp.tool()
def build_arrangement_structure(ctx: Context, song_config: str) -> str:
    """Build a complete arrangement structure (T021).

    Creates song sections and arranges them in Arrangement View.

    Args:
        song_config: JSON string with song configuration
            Example: '{"sections": [{"type": "intro", "bars": 8}, {"type": "verse", "bars": 16}]}'

    Returns:
        JSON with arrangement structure status.
    """
    try:
        import json as json_lib
        config = json_lib.loads(song_config)
        sections = config.get("sections", [])
        current_bar = 0
        created_sections = []
        for section in sections:
            section_type = section.get("type", "verse")
            bars = section.get("bars", 8)
            section_config = json_lib.dumps({
                "type": section_type,
                "bars": bars,
                "elements": section.get("elements", ["drums", "bass"]),
            })
            result = generate_section(ctx, section_config, current_bar)
            created_sections.append({
                "type": section_type,
                "start_bar": current_bar,
                "bars": bars,
                "result": result,
            })
            current_bar += bars
        return _ok({
            "total_sections": len(created_sections),
            "total_bars": current_bar,
            "sections": created_sections,
        })
    except json_lib.JSONDecodeError:
        return _err("Invalid JSON configuration")
    except Exception as e:
        return _err(f"Error building arrangement structure: {str(e)}")


@mcp.tool()
def create_arrangement_midi_clip(ctx: Context, track_index: int, start_time: float = 0.0,
                                  length: float = 4.0, notes: list = None) -> str:
    """Create a MIDI clip in Arrangement View (T023).

    Args:
        track_index: Index of the target track
        start_time: Start position in bars
        length: Clip length in bars
        notes: List of MIDI notes to add

    Returns:
        JSON with clip creation status.
    """
    if notes is None:
        notes = []
    try:
        resp = _send_to_ableton(
            "create_arrangement_midi_clip",
            {"track_index": track_index, "start_time": start_time, "length": length, "notes": notes},
            timeout=TIMEOUTS["create_arrangement_midi_clip"]
        )
        if resp.get("status") == "success":
            return _ok({
                "track_index": track_index,
                "start_time": start_time,
                "length": length,
                "notes_added": len(notes),
                "view": "Arrangement",
            })
        return _err(resp.get("message", "Failed to create arrangement MIDI clip"))
    except Exception as e:
        return _err(f"Error creating arrangement MIDI clip: {str(e)}")


@mcp.tool()
def create_arrangement_audio_clip(ctx: Context, track_index: int, sample_path: str,
                                   start_time: float = 0.0, length: float = 4.0) -> str:
    """Create an audio clip in Arrangement View (T024).

    Args:
        track_index: Index of the target audio track
        sample_path: Absolute path to the audio file
        start_time: Start position in bars
        length: Clip length in bars

    Returns:
        JSON with clip creation status.
    """
    if not os.path.isfile(sample_path):
        return _err(f"Sample not found: {sample_path}")
    try:
        resp = _send_to_ableton(
            "create_arrangement_audio_clip",
            {"track_index": track_index, "sample_path": sample_path, "start_time": start_time, "length": length},
            timeout=TIMEOUTS["create_arrangement_audio_clip"]
        )
        if resp.get("status") == "success":
            return _ok({
                "track_index": track_index,
                "sample_path": sample_path,
                "start_time": start_time,
                "length": length,
                "view": "Arrangement",
            })
        return _err(resp.get("message", "Failed to create arrangement audio clip"))
    except Exception as e:
        return _err(f"Error creating arrangement audio clip: {str(e)}")


@mcp.tool()
def fill_arrangement_with_song(ctx: Context, song_config: str) -> str:
    """Fill the entire arrangement with a complete song (T025).

    Populates Arrangement View with all song elements.

    Args:
        song_config: JSON string with complete song configuration
            Example: '{"bpm": 95, "key": "Am", "style": "classic", "duration": 128}'

    Returns:
        JSON with song arrangement status.
    """
    try:
        import json as json_lib
        config = json_lib.loads(song_config)
        bpm = config.get("bpm", 95)
        key = config.get("key", "Am")
        style = config.get("style", "classic")
        duration = config.get("duration", 128)
        resp = _send_to_ableton(
            "set_tempo",
            {"tempo": bpm},
            timeout=10.0
        )
        if resp.get("status") != "success":
            return _err("Failed to set tempo")
        structure_config = json_lib.dumps({
            "sections": [
                {"type": "intro", "bars": 8, "elements": ["drums", "bass"]},
                {"type": "verse", "bars": 16, "elements": ["drums", "bass", "chords"]},
                {"type": "chorus", "bars": 16, "elements": ["drums", "bass", "chords", "melody"]},
                {"type": "verse", "bars": 16, "elements": ["drums", "bass", "chords"]},
                {"type": "chorus", "bars": 16, "elements": ["drums", "bass", "chords", "melody"]},
                {"type": "outro", "bars": 8, "elements": ["drums", "bass"]},
            ]
        })
        result = build_arrangement_structure(ctx, structure_config)
        return _ok({
            "bpm": bpm,
            "key": key,
            "style": style,
            "duration_bars": duration,
            "arrangement_result": result,
        })
    except json_lib.JSONDecodeError:
        return _err("Invalid JSON configuration")
    except Exception as e:
        return _err(f"Error filling arrangement: {str(e)}")


@mcp.tool()
def generate_advanced_chords(ctx: Context, root: str = "C", chord_type: str = "maj9",
                              track_index: int = None, octave: int = 4,
                              voicing: str = "default", progression_roots: list = None,
                              progression_types: list = None, bar_length: float = 4.0,
                              start_bar: float = 0.0) -> str:
    """Generate advanced extended chords with professional voice leading (Agente 13).

    Creates rich harmonic content with extended chords (9ths, 11ths, 13ths),
    suspended chords, and altered dominants. Includes intelligent voice leading
    options like drop-2, drop-3, and minimal movement between chords.

    Args:
        root: Root note for single chord (e.g., 'C', 'F#', 'Bb')
        chord_type: Chord quality - 'maj9', 'min9', 'dom9', 'maj11', 'min11',
                    'maj13', 'min13', 'dom13', 'sus2', 'sus4', '7sus4',
                    '7b5', '7b9', '7#9', '7#11', '7b13', 'alt'
        track_index: Optional track index to create MIDI clip on
        octave: Octave number (4 = middle C, default)
        voicing: Voice leading type - 'default', 'drop2', 'drop3', 'open', 'minimal'
        progression_roots: List of roots for chord progression (e.g., ['C', 'F', 'G', 'C'])
        progression_types: Parallel list of chord types (e.g., ['maj9', 'maj11', 'dom13', 'maj9'])
        bar_length: Length of each chord in bars
        start_bar: Starting bar position for arrangement clip

    Returns:
        JSON with chord notes, MIDI data, and optional clip creation status.
    """
    try:
        from engines.harmony_engine import ExtendedChordsEngine, CHORD_STRUCTURES, CHORD_CATEGORIES
        engine = ExtendedChordsEngine()

        result = {
            "chord_type": chord_type,
            "voicing": voicing,
            "octave": octave,
        }

        # Generate chord or progression
        if progression_roots and progression_types:
            # Generate progression
            chords = engine.generate_chord_progression(
                roots=progression_roots,
                chord_types=progression_types,
                voicing=voicing
            )
            result["progression"] = chords
            result["chord_count"] = len(chords)
            notes = []
            for i, chord in enumerate(chords):
                for midi_note in chord["midi_notes"]:
                    notes.append({
                        "pitch": midi_note,
                        "start_time": start_bar * 2.0 + i * bar_length * 2.0,
                        "duration": bar_length * 2.0,
                        "velocity": 80,
                    })
            result["total_notes"] = len(notes)
        else:
            # Generate single chord
            chord = engine.generate_extended_chord(root, chord_type, octave, voicing)
            result["chord"] = chord
            notes = []
            for midi_note in chord["midi_notes"]:
                notes.append({
                    "pitch": midi_note,
                    "start_time": start_bar * 2.0,
                    "duration": bar_length * 2.0,
                    "velocity": 80,
                })
            result["total_notes"] = len(notes)

        # Add available chord types info
        result["available_categories"] = CHORD_CATEGORIES
        result["available_types"] = engine.get_available_chord_types()

        # Create MIDI clip if track_index provided
        if track_index is not None:
            resp = _send_to_ableton(
                "create_clip",
                {"track_index": track_index, "clip_index": 0, "length": bar_length * 2.0},
                timeout=TIMEOUTS["generate_advanced_chords"]
            )
            if resp.get("status") == "success":
                resp2 = _send_to_ableton(
                    "add_notes_to_clip",
                    {"track_index": track_index, "clip_index": 0, "notes": notes},
                    timeout=TIMEOUTS["generate_advanced_chords"]
                )
                if resp2.get("status") == "success":
                    result["clip_created"] = True
                    result["track_index"] = track_index
                    result["notes_added"] = len(notes)
                else:
                    result["clip_created"] = False
                    result["clip_error"] = resp2.get("message", "Failed to add notes")
            else:
                result["clip_created"] = False
                result["clip_error"] = resp.get("message", "Failed to create clip")

        return _ok(result)
    except Exception as e:
        return _err(f"Error generating advanced chords: {str(e)}")


@mcp.tool()
def automate_filter(ctx: Context, track_index: int, start_bar: float = 0.0,
                    end_bar: float = 8.0, start_freq: float = 200.0,
                    end_freq: float = 20000.0,
                    curve_type: str = "s_curve") -> str:
    """Automate a filter sweep on a track (T026).

    Creates automation for filter frequency from start to end.

    Args:
        track_index: Index of the target track
        start_bar: Start bar for automation
        end_bar: End bar for automation
        start_freq: Starting filter frequency in Hz
        end_freq: Ending filter frequency in Hz
        curve_type: Type of interpolation curve ("linear", "bezier", "s_curve", 
                   "exponential", "stepped"). Default: "s_curve"

    Returns:
        JSON with automation creation status.
    """
    return _proxy_ableton_command(
        "automate_filter",
        {
            "track_index": track_index,
            "start_bar": start_bar,
            "end_bar": end_bar,
            "start_freq": start_freq,
            "end_freq": end_freq,
            "curve_type": curve_type,
        },
        timeout=TIMEOUTS["automate_filter"],
        defaults={
            "track_index": track_index,
            "start_bar": start_bar,
            "end_bar": end_bar,
            "start_freq": start_freq,
            "end_freq": end_freq,
            "curve_type": curve_type,
        },
    )


@mcp.tool()
def generate_curve_automation(ctx: Context, track_index: int, parameter: str,
                               points: list, curve_type: str = "linear",
                               grid_quantization: str = "none") -> str:
    """Generate advanced automation with curve interpolation (Agente 6).

    Creates automation curves using various interpolation methods.

    Args:
        track_index: Index of the target track
        parameter: Parameter name to automate (e.g., "volume", "filter_freq")
        points: List of {time: float, value: float} control points
        curve_type: Interpolation type ("linear", "bezier", "s_curve", 
                   "exponential", "stepped")
        grid_quantization: Grid size ("1/4", "1/8", "1/16", "1/32", "none")

    Returns:
        JSON with automation points and status.
    """
    try:
        # Import curve interpolation engine
        from engines.curve_interpolation import (
            generate_curve, CurveConfig, CurveType,
            GridQuantization, AutomationPoint
        )
        
        # Parse curve type
        curve_type_map = {
            "linear": CurveType.LINEAR,
            "bezier": CurveType.BEZIER,
            "s_curve": CurveType.S_CURVE,
            "s-curve": CurveType.S_CURVE,
            "scurve": CurveType.S_CURVE,
            "exponential": CurveType.EXPONENTIAL,
            "exp": CurveType.EXPONENTIAL,
            "stepped": CurveType.STEPPED,
            "steps": CurveType.STEPPED,
        }
        ct = curve_type_map.get(curve_type.lower(), CurveType.LINEAR)
        
        # Parse grid quantization
        grid_map = {
            "none": None,
            "1/4": GridQuantization.QUARTER,
            "quarter": GridQuantization.QUARTER,
            "1/8": GridQuantization.EIGHTH,
            "eighth": GridQuantization.EIGHTH,
            "1/16": GridQuantization.SIXTEENTH,
            "sixteenth": GridQuantization.SIXTEENTH,
            "1/32": GridQuantization.THIRTYSECOND,
            "thirtysecond": GridQuantization.THIRTYSECOND,
        }
        grid = grid_map.get(grid_quantization.lower(), None)
        
        # Convert points to tuples
        point_tuples = [(p.get("time", 0.0), p.get("value", 0.0)) for p in points]
        
        # Generate curve
        config = CurveConfig(
            curve_type=ct,
            quantize_grid=grid
        )
        automation_points = generate_curve(point_tuples, config)
        
        # Convert to serializable format
        points_data = [p.to_dict() for p in automation_points]
        
        return _ok({
            "track_index": track_index,
            "parameter": parameter,
            "curve_type": curve_type,
            "grid_quantization": grid_quantization,
            "points_generated": len(points_data),
            "automation_points": points_data[:50],  # Limit to first 50 points
            "note": "Automation envelope generated. Apply to Ableton clip to use.",
        })
    except ImportError:
        return _err("Curve interpolation engine not available.")
    except Exception as e:
        return _err(f"Error generating curve automation: {str(e)}")


# ==================================================================
# AGENTE 5: MULTI-PARAMETER AUTOMATION (live_bridge exposure)
# ==================================================================

@mcp.tool()
def add_parameter_automation(ctx: Context, track_index: int, parameter_name: str,
                              points: list, device_name: str = "",
                              clip_index: int = None, send_index: int = None) -> str:
    """Add automation envelope to track parameters (volume, pan, device params, sends).

    Agente 5: Exposes live_bridge.add_automation() for multi-parameter automation.
    Supports track-level automation (volume, pan, sends) and clip/device automation.

    Args:
        track_index: Index of the target track
        parameter_name: Name of parameter to automate ("volume", "pan", "device_param", etc.)
        points: List of [time, value] pairs where time is in beats and value is 0.0-1.0
        device_name: Name of device (only for device_param automation, e.g., "EQ Eight")
        clip_index: Clip index (only for clip automation)
        send_index: Send index (only for send automation, 0-based)

    Returns:
        JSON with automation creation status.

    Examples:
        # Volume automation (track level)
        add_parameter_automation(track_index=0, parameter_name="volume",
                                 points=[[0.0, 0.8], [4.0, 1.0], [8.0, 0.6]])

        # Pan automation (track level)
        add_parameter_automation(track_index=1, parameter_name="pan",
                                 points=[[0.0, 0.0], [8.0, -0.5], [16.0, 0.5]])

        # Send automation
        add_parameter_automation(track_index=0, parameter_name="send",
                                 points=[[0.0, 0.0], [4.0, 0.5]], send_index=0)

        # Device parameter automation
        add_parameter_automation(track_index=0, parameter_name="Frequency",
                                 points=[[0.0, 200.0], [8.0, 20000.0]],
                                 device_name="Auto Filter", clip_index=0)
    """
    params = {
        "track_index": track_index,
        "parameter_name": parameter_name,
        "points": points,
    }
    if device_name:
        params["device_name"] = device_name
    if clip_index is not None:
        params["clip_index"] = clip_index
    if send_index is not None:
        params["send_index"] = send_index

    return _proxy_ableton_command(
        "add_parameter_automation",
        params,
        timeout=TIMEOUTS["add_parameter_automation"],
        defaults={
            "track_index": track_index,
            "parameter_name": parameter_name,
            "points_count": len(points),
        },
    )


# ==================================================================
# FASE 2.5: FX CREATOR TOOLS (T031-T035) - ExposiciÃ³n de arrangement_engine
# ==================================================================

@mcp.tool()
def create_riser(ctx: Context, track_index: int, start_bar: int,
                 duration: int = 8, intensity: float = 0.8,
                 pitch_min: int = 36, pitch_max: int = 84) -> str:
    """Create a riser/buildup effect (T031).
    
    Generates a pre-drop riser with ascending pitch and tension.
    Perfect for build-ups before choruses or drops.
    
    Args:
        track_index: Index of the target track
        start_bar: Start bar for the riser
        duration: Duration in bars (default 8)
        intensity: Intensity 0.0-1.0 (default 0.8)
        pitch_min: Minimum MIDI pitch (default 36 = C2)
        pitch_max: Maximum MIDI pitch (default 84 = C6)
    
    Returns:
        JSON with riser creation status and clip info.
    """
    return _proxy_ableton_command(
        "create_riser",
        {
            "track_index": track_index,
            "start_bar": start_bar,
            "duration": duration,
            "intensity": intensity,
            "pitch_range": [pitch_min, pitch_max],
        },
        timeout=30.0,
        defaults={
            "track_index": track_index,
            "start_bar": start_bar,
            "duration": duration,
            "intensity": intensity,
        },
    )


@mcp.tool()
def create_downlifter(ctx: Context, track_index: int, start_bar: int,
                      duration: int = 4, intensity: float = 0.7,
                      pitch_start: int = 72, pitch_end: int = 36) -> str:
    """Create a downlifter effect (T032).
    
    Generates a post-drop downlifter with descending pitch.
    Perfect for energy release after drops or impacts.
    
    Args:
        track_index: Index of the target track
        start_bar: Start bar for the downlifter
        duration: Duration in bars (default 4)
        intensity: Intensity 0.0-1.0 (default 0.7)
        pitch_start: Starting MIDI pitch (default 72 = C5)
        pitch_end: Ending MIDI pitch (default 36 = C2)
    
    Returns:
        JSON with downlifter creation status and clip info.
    """
    return _proxy_ableton_command(
        "create_downlifter",
        {
            "track_index": track_index,
            "start_bar": start_bar,
            "duration": duration,
            "intensity": intensity,
            "pitch_range": [pitch_start, pitch_end],
        },
        timeout=30.0,
        defaults={
            "track_index": track_index,
            "start_bar": start_bar,
            "duration": duration,
            "intensity": intensity,
        },
    )


@mcp.tool()
def create_impact(ctx: Context, track_index: int, position: float,
                  intensity: float = 1.0, impact_type: str = "hit") -> str:
    """Create an impact FX (T033).
    
    Generates impact effects (hit, crash, sub drop, noise).
    Perfect for emphasizing drops, transitions, or beats.
    
    Args:
        track_index: Index of the target track
        position: Position in bars (int) or beats (float)
        intensity: Intensity 0.0-1.0 (default 1.0)
        impact_type: Type of impact - "hit", "crash", "sub_drop", "noise"
    
    Returns:
        JSON with impact creation status and clip info.
    """
    return _proxy_ableton_command(
        "create_impact",
        {
            "track_index": track_index,
            "position": position,
            "intensity": intensity,
            "impact_type": impact_type,
        },
        timeout=30.0,
        defaults={
            "track_index": track_index,
            "position": position,
            "intensity": intensity,
            "impact_type": impact_type,
        },
    )


@mcp.tool()
def create_silence(ctx: Context, track_index: int, start_bar: int,
                   duration: int = 1) -> str:
    """Create silence/break effect (T034).
    
    Generates a moment of silence for dramatic effect.
    Perfect for creating tension before drops.
    
    Args:
        track_index: Index of the target track (for context)
        start_bar: Start bar for the silence
        duration: Duration in bars (default 1)
    
    Returns:
        JSON with silence creation status.
    """
    return _proxy_ableton_command(
        "create_silence",
        {
            "track_index": track_index,
            "start_bar": start_bar,
            "duration": duration,
        },
        timeout=30.0,
        defaults={
            "track_index": track_index,
            "start_bar": start_bar,
            "duration": duration,
        },
    )


@mcp.tool()
def create_fx_section(ctx: Context, section_type: str, start_bar: int,
                      duration: int = 8, track_indices: list = None) -> str:
    """Create complete FX section (T035).
    
    Generates a complete FX section with risers, impacts, and transitions.
    
    Args:
        section_type: Type - "pre_drop", "post_drop", "transition", "build"
        start_bar: Start bar for the section
        duration: Duration in bars (default 8)
        track_indices: List of track indices to apply FX (optional)
    
    Returns:
        JSON with FX section creation status.
    """
    return _proxy_ableton_command(
        "create_fx_section",
        {
            "section_type": section_type,
            "start_bar": start_bar,
            "duration": duration,
            "track_indices": track_indices or [],
        },
        timeout=30.0,
        defaults={
            "section_type": section_type,
            "start_bar": start_bar,
            "duration": duration,
        },
    )


# ==================================================================
# AGENTE 3: TRANSITIONS & FILLS (ExposiciÃ³n de pattern_library.py)
# ==================================================================

@mcp.tool()
def create_fx_hit(ctx: Context, track_index: int, position: float,
                  fx_type: str = "riser", duration: float = 2.0) -> str:
    """Create an FX hit at a specific position.
    
    Generates single FX hits like risers, downers, impacts, crashes, and sweeps.
    Uses PercussionLibrary.get_fx_hit() from pattern_library.py.
    
    Args:
        track_index: Index of the target track
        position: Position in beats (float) or bars (int)
        fx_type: Type of FX - "riser", "downer", "impact", "crash", "sweep" (default "riser")
        duration: Duration of the FX in beats (default 2.0)
    
    Returns:
        JSON with FX hit creation status and note details.
    """
    valid_types = ["riser", "downer", "impact", "crash", "sweep"]
    if fx_type not in valid_types:
        return _err(f"Invalid fx_type: {fx_type}. Must be one of: {', '.join(valid_types)}")
    
    return _proxy_ableton_command(
        "create_fx_hit",
        {
            "track_index": track_index,
            "position": position,
            "fx_type": fx_type,
            "duration": duration,
        },
        timeout=30.0,
        defaults={
            "track_index": track_index,
            "position": position,
            "fx_type": fx_type,
            "duration": duration,
        },
    )


@mcp.tool()
def create_transition_fill(ctx: Context, track_index: int, position: float,
                           fill_type: str = "break") -> str:
    """Create a transition fill at a specific position.
    
    Generates transition fills for breaks, builds, drops, and impacts.
    Uses PercussionLibrary.get_transition_fill() from pattern_library.py.
    
    Args:
        track_index: Index of the target track
        position: Position in beats (float) or bars (int)
        fill_type: Type of fill - "break", "build", "drop", "impact" (default "break")
    
    Returns:
        JSON with transition fill creation status and note details.
    """
    valid_types = ["break", "build", "drop", "impact"]
    if fill_type not in valid_types:
        return _err(f"Invalid fill_type: {fill_type}. Must be one of: {', '.join(valid_types)}")
    
    return _proxy_ableton_command(
        "create_transition_fill",
        {
            "track_index": track_index,
            "position": position,
            "fill_type": fill_type,
        },
        timeout=30.0,
        defaults={
            "track_index": track_index,
            "position": position,
            "fill_type": fill_type,
        },
    )


@mcp.tool()
def create_intro_buildup(ctx: Context, track_index: int, bars: int = 4) -> str:
    """Create an intro buildup section.
    
    Generates a buildup pattern for intros with increasing density and a final riser.
    Uses PercussionLibrary.get_intro_buildup() from pattern_library.py.
    
    Args:
        track_index: Index of the target track
        bars: Number of bars for the buildup (default 4)
    
    Returns:
        JSON with intro buildup creation status and note details.
    """
    return _proxy_ableton_command(
        "create_intro_buildup",
        {
            "track_index": track_index,
            "bars": bars,
        },
        timeout=30.0,
        defaults={
            "track_index": track_index,
            "bars": bars,
        },
    )


@mcp.tool()
def create_fx_automation(ctx: Context, track_index: int, fx_type: str,
                         section: int = 0) -> str:
    """Create FX automation on a track (T050).

    Uses workflow_engine.create_fx_automation() to create parameter automation
    for various effects types.

    Args:
        track_index: Index of the target track
        fx_type: Type of FX automation - "filter_sweep", "reverb_duck",
                 "delay_wash", or "volume_fade"
        section: Section index to apply automation to (default 0)

    Returns:
        JSON with automation creation status and parameter details.
    """
    valid_fx_types = ["filter_sweep", "reverb_duck", "delay_wash", "volume_fade"]
    if fx_type not in valid_fx_types:
        return _err(
            f"Invalid fx_type: {fx_type}. Must be one of: {', '.join(valid_fx_types)}"
        )
    try:
        from engines.workflow_engine import WorkflowEngine
        engine = WorkflowEngine()
        result = engine.create_fx_automation(
            track_index=track_index,
            fx_type=fx_type,
            section=section
        )
        if result.get("status") == "success":
            return _ok({
                "track_index": track_index,
                "fx_type": fx_type,
                "section": section,
                "automation": result.get("automation", {}),
                "description": result.get("automation", {}).get("description", ""),
                "points_count": len(result.get("automation", {}).get("automation_points", [])),
            })
        return _err(result.get("message", "Failed to create FX automation"))
    except ImportError:
        return _err("Workflow engine not available.")
    except Exception as e:
        return _err(f"Error creating FX automation: {str(e)}")


# ==================================================================
# AGENTE 4: WHITE NOISE GENERATOR
# ==================================================================

@mcp.tool()
def create_white_noise(ctx: Context, duration: float = 4.0, sample_rate: int = 44100,
                       effect_type: str = "basic", start_freq: float = 200.0,
                       end_freq: float = 8000.0) -> str:
    """Genera ruido blanco programÃ¡tico con efectos aplicados (Agente 4).

    Crea ruido blanco programÃ¡ticamente (sin cargar samples externos) y
    aplica efectos como filtros sweep y envolventes de volumen.

    Args:
        duration: DuraciÃ³n en segundos (default 4.0)
        sample_rate: Frecuencia de muestreo (default 44100 Hz)
        effect_type: Tipo de efecto - "basic", "riser", "downlifter", "sweep"
        start_freq: Frecuencia inicial para sweep (default 200 Hz)
        end_freq: Frecuencia final para sweep (default 8000 Hz)

    Returns:
        JSON con informaciÃ³n del archivo generado y parÃ¡metros aplicados.
    """
    try:
        from engines.noise_generator import WhiteNoiseGenerator, get_noise_generator

        generator = get_noise_generator()

        # Generar ruido base
        noise_clip = generator.generate_white_noise(duration, sample_rate)

        # Aplicar efectos segÃºn tipo solicitado
        if effect_type == "basic":
            result = noise_clip
        elif effect_type == "riser":
            result = generator.create_riser_effect(duration, sample_rate, start_freq, end_freq)
        elif effect_type == "downlifter":
            result = generator.create_downlifter_effect(duration, sample_rate, end_freq, start_freq)
        elif effect_type == "sweep":
            # Aplicar sweep personalizado
            result = generator.apply_filter_sweep(noise_clip, start_freq, end_freq)
        else:
            return _err(f"Invalid effect_type: {effect_type}. Must be 'basic', 'riser', 'downlifter', or 'sweep'")

        return _ok({
            "file_path": result.get("file_path"),
            "duration": result.get("duration"),
            "sample_rate": result.get("sample_rate"),
            "effect_type": effect_type,
            "type": result.get("type", "white_noise"),
            "description": result.get("description", "Generated white noise"),
        })
    except ImportError:
        return _err("Noise generator engine not available.")
    except Exception as e:
        return _err(f"Error generating white noise: {str(e)}")


# ==================================================================
# FASE 3: INTELIGENCIA MUSICAL (T041-T060)
# ==================================================================

@mcp.tool()
def analyze_project_key(ctx: Context) -> str:
    """Detecta el key predominante del proyecto actual (T041)."""
    return _proxy_ableton_command("analyze_project_key", timeout=TIMEOUTS["analyze_project_key"])


@mcp.tool()
def harmonize_track(ctx: Context, track_index: int, progression: str = "I-V-vi-IV") -> str:
    """Armoniza un track con una progresion de acordes (T042).

    Args:
        track_index: Indice del track a armonizar
        progression: Progresion de acordes (ej: "I-V-vi-IV", "ii-V-I", "I-IV-V")
    """
    return _proxy_ableton_command(
        "harmonize_track",
        {"track_index": track_index, "progression": progression},
        timeout=TIMEOUTS["harmonize_track"],
        defaults={"track_index": track_index, "progression": progression},
    )


@mcp.tool()
def generate_counter_melody(ctx: Context, main_melody_track: int) -> str:
    """Genera una contra-melodia que complementa la melodia principal (T043).

    Args:
        main_melody_track: Indice del track con la melodia principal
    """
    return _proxy_ableton_command(
        "generate_counter_melody",
        {"main_melody_track": main_melody_track},
        timeout=TIMEOUTS["generate_counter_melody"],
        defaults={"main_melody_track": main_melody_track},
    )


@mcp.tool()
def detect_energy_curve(ctx: Context) -> str:
    """Analiza la curva de energia por seccion del proyecto (T044)."""
    return _proxy_ableton_command("detect_energy_curve", timeout=TIMEOUTS["detect_energy_curve"])


@mcp.tool()
def balance_sections(ctx: Context) -> str:
    """Ajusta automaticamente la energia entre secciones (T045)."""
    return _proxy_ableton_command("balance_sections", timeout=TIMEOUTS["balance_sections"])


@mcp.tool()
def variate_loop(ctx: Context, track_index: int, intensity: float = 0.5) -> str:
    """Crea variaciones de un loop para evitar repetitividad (T046).

    Args:
        track_index: Indice del track con el loop
        intensity: Intensidad de variacion (0.0-1.0)
    """
    if not 0.0 <= intensity <= 1.0:
        return _err(f"Invalid intensity: {intensity}. Must be 0.0-1.0.")
    return _proxy_ableton_command(
        "variate_loop",
        {"track_index": track_index, "intensity": intensity},
        timeout=TIMEOUTS["variate_loop"],
        defaults={"track_index": track_index, "intensity": intensity},
    )


@mcp.tool()
def add_call_and_response(ctx: Context, phrase_track: int, response_length: int = 2) -> str:
    """Genera una respuesta musical a una frase existente (T047).

    Args:
        phrase_track: Indice del track con la frase original
        response_length: Duracion de la respuesta en compases
    """
    return _proxy_ableton_command(
        "add_call_and_response",
        {"phrase_track": phrase_track, "response_length": response_length},
        timeout=TIMEOUTS["add_call_and_response"],
        defaults={"phrase_track": phrase_track, "response_length": response_length},
    )


@mcp.tool()
def generate_breakdown(ctx: Context, start_bar: int, duration: int = 8) -> str:
    """Genera una seccion de breakdown/descanso (T048).

    Args:
        start_bar: Barra donde comienza el breakdown
        duration: Duracion en compases (default 8)
    """
    return _proxy_ableton_command(
        "generate_breakdown",
        {"start_bar": start_bar, "duration": duration},
        timeout=TIMEOUTS["generate_breakdown"],
        defaults={"start_bar": start_bar, "duration": duration},
    )


@mcp.tool()
def generate_drop_variation(ctx: Context, original_drop_bar: int, variation_type: str = "intense") -> str:
    """Genera una variacion de un drop existente (T049).

    Args:
        original_drop_bar: Barra donde esta el drop original
        variation_type: Tipo de variacion ("intense", "minimal", "double", "fill")
    """
    return _proxy_ableton_command(
        "generate_drop_variation",
        {"original_drop_bar": original_drop_bar, "variation_type": variation_type},
        timeout=TIMEOUTS["generate_drop_variation"],
        defaults={"original_drop_bar": original_drop_bar, "variation_type": variation_type},
    )


@mcp.tool()
def create_outro(ctx: Context, fade_duration: int = 8) -> str:
    """Crea un outro con fade out automatico (T050).

    Args:
        fade_duration: Duracion del fade en compases
    """
    return _proxy_ableton_command(
        "create_outro",
        {"fade_duration": fade_duration},
        timeout=TIMEOUTS["create_outro"],
        defaults={"fade_duration": fade_duration},
    )


# ==================================================================
# FASE 4: WORKFLOW Y PRODUCCION (T061-T080)
# ==================================================================

@mcp.tool()
def load_preset(ctx: Context, preset_name: str) -> str:
    """Carga un preset en el proyecto actual (T062).

    Args:
        preset_name: Nombre del preset a cargar
    """
    try:
        from engines.workflow_engine import WorkflowEngine
        engine = WorkflowEngine()
        result = engine.load_preset(preset_name)
        if result.get("success"):
            return _ok({
                "preset_name": preset_name,
                "tracks_loaded": result.get("tracks_loaded", 0),
                "devices_loaded": result.get("devices_loaded", 0),
                "samples_loaded": result.get("samples_loaded", [])
            })
        return _err(result.get("message", "Failed to load preset"))
    except Exception as e:
        return _err(f"Error loading preset: {str(e)}")


@mcp.tool()
def save_as_preset(ctx: Context, name: str, description: str = "") -> str:
    """Guarda el proyecto actual como preset (T063).

    Args:
        name: Nombre del preset
        description: Descripcion opcional
    """
    try:
        from engines.workflow_engine import WorkflowEngine
        engine = WorkflowEngine()
        result = engine.save_as_preset(name, description)
        if result.get("success"):
            return _ok({
                "preset_name": name,
                "description": description,
                "saved_path": result.get("path"),
                "tracks_included": result.get("tracks_included", 0)
            })
        return _err(result.get("message", "Failed to save preset"))
    except Exception as e:
        return _err(f"Error saving preset: {str(e)}")


@mcp.tool()
def list_presets(ctx: Context) -> str:
    """Lista todos los presets disponibles (T064)."""
    try:
        from engines.workflow_engine import WorkflowEngine
        engine = WorkflowEngine()
        result = engine.list_presets()
        return _ok({
            "presets": result.get("presets", []),
            "total_count": result.get("count", 0),
            "categories": result.get("categories", [])
        })
    except Exception as e:
        return _err(f"Error listing presets: {str(e)}")


@mcp.tool()
def create_custom_preset(ctx: Context, name: str, description: str = "") -> str:
    """Crea un preset personalizado desde cero (T065).

    Args:
        name: Nombre del preset
        description: Descripcion del preset
    """
    try:
        from engines.workflow_engine import WorkflowEngine
        engine = WorkflowEngine()
        result = engine.create_custom_preset(name, description)
        if result.get("success"):
            return _ok({
                "preset_name": name,
                "description": description,
                "template_created": True,
                "base_tracks": result.get("base_tracks", [])
            })
        return _err(result.get("message", "Failed to create preset"))
    except Exception as e:
        return _err(f"Error creating custom preset: {str(e)}")


@mcp.tool()
def render_stems(ctx: Context, output_dir: str) -> str:
    """Renderiza stems individuales para mezcla externa (T066).

    Args:
        output_dir: Directorio de salida para los stems
    """
    return _proxy_ableton_command(
        "render_stems",
        {"output_dir": output_dir},
        timeout=TIMEOUTS["render_stems"],
        defaults={"output_dir": output_dir},
    )


@mcp.tool()
def render_full_mix(ctx: Context, output_path: str) -> str:
    """Renderiza el mix completo masterizado (T067).

    Args:
        output_path: Ruta del archivo de salida
    """
    return _proxy_ableton_command(
        "render_full_mix",
        {"output_path": output_path},
        timeout=TIMEOUTS["render_full_mix"],
        defaults={"output_path": output_path},
    )


@mcp.tool()
def render_instrumental(ctx: Context, output_path: str) -> str:
    """Renderiza version instrumental (sin tracks de voz) (T068).

    Args:
        output_path: Ruta del archivo de salida
    """
    return _proxy_ableton_command(
        "render_instrumental",
        {"output_path": output_path},
        timeout=TIMEOUTS["render_instrumental"],
        defaults={"output_path": output_path},
    )


@mcp.tool()
def full_quality_check(ctx: Context) -> str:
    """Quality check completo del proyecto (T071)."""
    return _proxy_ableton_command("full_quality_check", timeout=TIMEOUTS["full_quality_check"])


@mcp.tool()
def fix_quality_issues(ctx: Context, issues: list = None) -> str:
    """Arregla automaticamente los problemas detectados (T072).

    Args:
        issues: Lista de issues especificos a arreglar (default: todos)
    """
    if issues is None:
        issues = []
    return _proxy_ableton_command(
        "fix_quality_issues",
        {"issues": issues},
        timeout=TIMEOUTS["fix_quality_issues"],
        defaults={"issues": issues},
    )


@mcp.tool()
def duplicate_project(ctx: Context, new_name: str) -> str:
    """Duplica el proyecto actual con nuevo nombre (T076).

    Args:
        new_name: Nombre para el proyecto duplicado
    """
    return _proxy_ableton_command(
        "duplicate_project",
        {"new_name": new_name},
        timeout=TIMEOUTS["duplicate_project"],
        defaults={"new_name": new_name},
    )


@mcp.tool()
def create_radio_edit(ctx: Context, output_path: str) -> str:
    """Crea una version radio edit (corta, sin intros largas) (T078).

    Args:
        output_path: Ruta del archivo de salida
    """
    return _proxy_ableton_command(
        "create_radio_edit",
        {"output_path": output_path},
        timeout=TIMEOUTS["create_radio_edit"],
        defaults={"output_path": output_path},
    )


@mcp.tool()
def create_dj_edit(ctx: Context, output_path: str) -> str:
    """Crea una version DJ edit (extended intro/outro, cue points) (T079).

    Args:
        output_path: Ruta del archivo de salida
    """
    return _proxy_ableton_command(
        "create_dj_edit",
        {"output_path": output_path},
        timeout=TIMEOUTS["create_dj_edit"],
        defaults={"output_path": output_path},
    )


# ==================================================================
# FASES 6-9: Session Orchestrator + Warp Automation + Full MIDI Orchestration + MCP Tools
# ==================================================================

@mcp.tool()
def analyze_all_bpm(ctx: Context, force_reanalyze: bool = False) -> str:
    """
    Analyze BPM of all samples in library (800+) using librosa.
    Stores results in SQLite metadata store.
    
    Args:
        force_reanalyze: Reanalyze even if already in database
    """
    try:
        from engines.bpm_analyzer import BPMAnalyzer, analyze_sample
        
        analyzer = BPMAnalyzer()
        result = analyzer.analyze_all_library(force_reanalyze=force_reanalyze)
        
        return _ok({
            "total_samples": result.get("total_samples", 0),
            "analyzed": result.get("analyzed", 0),
            "errors": result.get("errors", 0),
            "metadata_store_updated": True,
            "force_reanalyze": force_reanalyze,
        })
    except ImportError:
        return _err("BPM analyzer engine not available.")
    except Exception as e:
        return _err(f"Error analyzing library BPM: {str(e)}")


@mcp.tool()
def validate_session(ctx: Context) -> str:
    """
    Validate all MIDI tracks in Session View have instruments loaded.
    Reports which tracks need fixing.
    """
    try:
        resp = _send_to_ableton("get_tracks", timeout=TIMEOUTS["get_tracks"])
        if resp.get("status") != "success":
            return _err("Failed to get tracks from Ableton")
        
        tracks = resp.get("result", {}).get("tracks", [])
        midi_tracks_without_instruments = []
        
        for track in tracks:
            if track.get("is_midi"):
                track_idx = track.get("index")
                track_name = track.get("name", f"Track {track_idx}")
                device_count = track.get("device_count", 0)
                
                if device_count == 0:
                    midi_tracks_without_instruments.append({
                        "index": track_idx,
                        "name": track_name,
                        "issue": "No instruments loaded"
                    })
        
        return _ok({
            "valid": len(midi_tracks_without_instruments) == 0,
            "midi_tracks_checked": sum(1 for t in tracks if t.get("is_midi")),
            "tracks_needing_fix": midi_tracks_without_instruments,
            "total_issues": len(midi_tracks_without_instruments),
        })
    except Exception as e:
        return _err(f"Error validating session: {str(e)}")


@mcp.tool()
def fix_session_midi_tracks(ctx: Context) -> str:
    """
    Auto-fix MIDI tracks by loading appropriate instruments.
    Detects track type from name (Piano -> Grand Piano, etc.)
    """
    try:
        resp = _send_to_ableton("fix_session_midi_tracks", timeout=30.0)
        if resp.get("status") == "success":
            result = resp.get("result", {})
            fixed_tracks = result.get("fixed_tracks", [])
            return _ok({
                "fixed_count": len(fixed_tracks),
                "fixed_tracks": fixed_tracks,
                "message": f"Fixed {len(fixed_tracks)} MIDI tracks with instruments",
            })
        return _err(resp.get("message", "Failed to fix session MIDI tracks"))
    except Exception as e:
        return _err(f"Error fixing session MIDI tracks: {str(e)}")


@mcp.tool()
def select_bpm_coherent_pool(ctx: Context, target_bpm: int = 95, 
                              tolerance: int = 5, pool_size: int = 20) -> str:
    """
    Select samples that match target BPM within tolerance.
    Uses librosa-analyzed BPM data from metadata store.
    
    Args:
        target_bpm: Target tempo (default 95)
        tolerance: BPM tolerance (default ±5)
        pool_size: Number of samples to return
    """
    try:
        from engines.bpm_analyzer import BPMAnalyzer
        
        analyzer = BPMAnalyzer()
        pool = analyzer.select_bpm_coherent_pool(
            target_bpm=target_bpm,
            tolerance=tolerance,
            pool_size=pool_size
        )
        
        return _ok({
            "target_bpm": target_bpm,
            "tolerance": tolerance,
            "pool_size": len(pool),
            "samples": [
                {
                    "path": s.get("path"),
                    "name": s.get("name"),
                    "bpm": s.get("bpm"),
                    "role": s.get("role"),
                    "deviation": abs(s.get("bpm", target_bpm) - target_bpm)
                }
                for s in pool
            ],
        })
    except ImportError:
        return _err("BPM analyzer engine not available.")
    except Exception as e:
        return _err(f"Error selecting BPM coherent pool: {str(e)}")


@mcp.tool()
def warp_clip_to_bpm(ctx: Context, track_index: int, clip_index: int,
                     original_bpm: float, target_bpm: float) -> str:
    """
    Warp audio clip from original BPM to target BPM.
    Automatically selects warp mode (Complex Pro/Complex/Beats).
    
    Args:
        track_index: Track containing clip
        clip_index: Clip slot index
        original_bpm: Original sample BPM (from analysis)
        target_bpm: Target project BPM
    """
    try:
        resp = _send_to_ableton(
            "auto_warp_sample",
            {
                "track_index": track_index,
                "clip_index": clip_index,
                "original_bpm": original_bpm,
                "target_bpm": target_bpm,
            },
            timeout=15.0
        )
        if resp.get("status") == "success":
            result = resp.get("result", {})
            return _ok({
                "warped": result.get("warped", False),
                "track_index": track_index,
                "clip_index": clip_index,
                "original_bpm": result.get("original_bpm"),
                "target_bpm": result.get("target_bpm"),
                "warp_factor": result.get("warp_factor"),
                "warp_mode": result.get("warp_mode"),
                "delta_pct": result.get("delta_pct"),
            })
        return _err(resp.get("message", "Failed to warp clip"))
    except Exception as e:
        return _err(f"Error warping clip: {str(e)}")


# ==================================================================
# FASE 5: INTEGRACION FINAL (T081-T100)
# ==================================================================

@mcp.tool()
def help(ctx: Context, tool_name: str = "") -> str:
    """Lista todas las tools disponibles o ayuda detallada de una tool especifica (T096).

    Args:
        tool_name: Nombre de la tool para ayuda detallada (opcional). Si vacio, lista todas.
    """
    tools_db = {
        # Info
        "get_session_info": {"description": "Obtiene informacion completa de la sesion actual de Ableton Live", "category": "Info", "params": [], "example": "get_session_info()"},
        "get_tracks": {"description": "Obtiene la lista de todas las pistas del proyecto", "category": "Info", "params": [], "example": "get_tracks()"},
        "get_scenes": {"description": "Obtiene la lista de todas las escenas en Session View", "category": "Info", "params": [], "example": "get_scenes()"},
        "get_master_info": {"description": "Obtiene informacion de la pista master", "category": "Info", "params": [], "example": "get_master_info()"},
        "health_check": {"description": "Verificacion completa del sistema (5 chequeos, score 0-5). EJECUTAR PRIMERO", "category": "Info", "params": [], "example": "health_check()"},
        # Transport
        "start_playback": {"description": "Inicia la reproduccion", "category": "Transport", "params": [], "example": "start_playback()"},
        "stop_playback": {"description": "Detiene la reproduccion", "category": "Transport", "params": [], "example": "stop_playback()"},
        "toggle_playback": {"description": "Alterna reproduccion/parada", "category": "Transport", "params": [], "example": "toggle_playback()"},
        "stop_all_clips": {"description": "Detiene todos los clips en Session View", "category": "Transport", "params": [], "example": "stop_all_clips()"},
        # Settings
        "set_tempo": {"description": "Establece el tempo del proyecto en BPM", "category": "Settings", "params": [{"name": "tempo", "type": "float", "range": "20-300"}], "example": "set_tempo(tempo=95)"},
        "set_time_signature": {"description": "Establece la firma de tiempo", "category": "Settings", "params": [{"name": "numerator", "type": "int", "default": 4}, {"name": "denominator", "type": "int", "default": 4}], "example": "set_time_signature(numerator=4, denominator=4)"},
        "set_metronome": {"description": "Activa o desactiva el metronomo", "category": "Settings", "params": [{"name": "enabled", "type": "bool"}], "example": "set_metronome(enabled=True)"},
        # Tracks
        "create_midi_track": {"description": "Crea una nueva pista MIDI", "category": "Tracks", "params": [{"name": "index", "type": "int", "default": -1}], "example": "create_midi_track(index=-1)"},
        "create_audio_track": {"description": "Crea una nueva pista de audio", "category": "Tracks", "params": [{"name": "index", "type": "int", "default": -1}], "example": "create_audio_track(index=-1)"},
        "set_track_name": {"description": "Establece el nombre de una pista", "category": "Tracks", "params": [{"name": "track_index", "type": "int"}, {"name": "name", "type": "str"}], "example": "set_track_name(track_index=0, name='Drums')"},
        "set_track_volume": {"description": "Establece el volumen de una pista (0.0-1.0)", "category": "Tracks", "params": [{"name": "track_index", "type": "int"}, {"name": "volume", "type": "float", "range": "0.0-1.0"}], "example": "set_track_volume(track_index=0, volume=0.8)"},
        "set_track_pan": {"description": "Establece el paneo de una pista (-1.0 a 1.0)", "category": "Tracks", "params": [{"name": "track_index", "type": "int"}, {"name": "pan", "type": "float", "range": "-1.0 a 1.0"}], "example": "set_track_pan(track_index=0, pan=0.0)"},
        "set_track_mute": {"description": "Silencia o reactiva una pista", "category": "Tracks", "params": [{"name": "track_index", "type": "int"}, {"name": "mute", "type": "bool"}], "example": "set_track_mute(track_index=0, mute=True)"},
        "set_track_solo": {"description": "Activa o desactiva solo en una pista", "category": "Tracks", "params": [{"name": "track_index", "type": "int"}, {"name": "solo", "type": "bool"}], "example": "set_track_solo(track_index=0, solo=True)"},
        "set_master_volume": {"description": "Establece el volumen master (0.0-1.0)", "category": "Tracks", "params": [{"name": "volume", "type": "float", "range": "0.0-1.0"}], "example": "set_master_volume(volume=0.8)"},
        # Clips
        "create_clip": {"description": "Crea un clip MIDI en Session View", "category": "Clips", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int", "default": 0}, {"name": "length", "type": "float", "default": 4.0}], "example": "create_clip(track_index=0, clip_index=0, length=4.0)"},
        "add_notes_to_clip": {"description": "Aniade notas MIDI a un clip", "category": "Clips", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int"}, {"name": "notes", "type": "list"}], "example": "add_notes_to_clip(track_index=0, clip_index=0, notes=[{'pitch':36,'start_time':0.0,'duration':0.25,'velocity':100}])"},
        "fire_clip": {"description": "Dispara un clip en Session View", "category": "Clips", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int", "default": 0}], "example": "fire_clip(track_index=0, clip_index=0)"},
        "fire_scene": {"description": "Dispara una escena completa", "category": "Clips", "params": [{"name": "scene_index", "type": "int"}], "example": "fire_scene(scene_index=0)"},
        "set_scene_name": {"description": "Establece el nombre de una escena", "category": "Clips", "params": [{"name": "scene_index", "type": "int"}, {"name": "name", "type": "str"}], "example": "set_scene_name(scene_index=0, name='Verse')"},
        "create_scene": {"description": "Crea una nueva escena", "category": "Clips", "params": [{"name": "index", "type": "int", "default": -1}], "example": "create_scene(index=-1)"},
        # Samples
        "analyze_library": {"description": "Analiza todos los samples en la libreria de reggaeton", "category": "Samples", "params": [{"name": "force_reanalyze", "type": "bool", "default": False}], "example": "analyze_library(force_reanalyze=False)"},
        "get_library_stats": {"description": "Obtiene estadisticas de la libreria analizada", "category": "Samples", "params": [], "example": "get_library_stats()"},
        "get_similar_samples": {"description": "Encuentra samples similares usando embeddings", "category": "Samples", "params": [{"name": "sample_path", "type": "str"}, {"name": "top_n", "type": "int", "default": 10}], "example": "get_similar_samples(sample_path='...', top_n=10)"},
        "find_samples_like_audio": {"description": "Encuentra samples similares a un audio externo", "category": "Samples", "params": [{"name": "audio_path", "type": "str"}, {"name": "top_n", "type": "int", "default": 20}, {"name": "role", "type": "str", "optional": True}], "example": "find_samples_like_audio(audio_path='...', top_n=20)"},
        "get_user_sound_profile": {"description": "Obtiene el perfil de sonido del usuario", "category": "Samples", "params": [], "example": "get_user_sound_profile()"},
        "get_recommended_samples": {"description": "Obtiene samples recomendados para un rol", "category": "Samples", "params": [{"name": "role", "type": "str", "optional": True}, {"name": "count", "type": "int", "default": 5}], "example": "get_recommended_samples(role='kick', count=5)"},
        "compare_two_samples": {"description": "Compara dos samples y devuelve similitud", "category": "Samples", "params": [{"name": "path1", "type": "str"}, {"name": "path2", "type": "str"}], "example": "compare_two_samples(path1='...', path2='...')"},
        "browse_library": {"description": "Navega la libreria con filtros", "category": "Samples", "params": [{"name": "pack", "type": "str", "optional": True}, {"name": "role", "type": "str", "optional": True}, {"name": "bpm_min", "type": "float", "default": 0}, {"name": "bpm_max", "type": "float", "default": 0}, {"name": "key", "type": "str", "optional": True}], "example": "browse_library(role='kick', bpm_min=90, bpm_max=100)"},
        # Mixing
        "create_bus_track": {"description": "Crea un grupo (bus) para mezcla", "category": "Mixing", "params": [{"name": "bus_type", "type": "str", "default": "Group"}], "example": "create_bus_track(bus_type='Drums')"},
        "route_track_to_bus": {"description": "Rutea una pista a un bus/grupo", "category": "Mixing", "params": [{"name": "track_index", "type": "int"}, {"name": "bus_name", "type": "str"}], "example": "route_track_to_bus(track_index=0, bus_name='Drums')"},
        "create_return_track": {"description": "Crea una pista de retorno con efecto", "category": "Mixing", "params": [{"name": "effect_type", "type": "str", "default": "Reverb"}], "example": "create_return_track(effect_type='Reverb')"},
        "set_track_send": {"description": "Configura envio a pista de retorno (0.0-1.0)", "category": "Mixing", "params": [{"name": "track_index", "type": "int"}, {"name": "return_index", "type": "int"}, {"name": "amount", "type": "float", "range": "0.0-1.0"}], "example": "set_track_send(track_index=0, return_index=0, amount=0.3)"},
        "insert_device": {"description": "Inserta un dispositivo/plugin en una pista", "category": "Mixing", "params": [{"name": "track_index", "type": "int"}, {"name": "device_name", "type": "str"}], "example": "insert_device(track_index=0, device_name='EQ Eight')"},
        "configure_eq": {"description": "Configura EQ Eight en una pista", "category": "Mixing", "params": [{"name": "track_index", "type": "int"}, {"name": "preset", "type": "str", "default": "default"}], "example": "configure_eq(track_index=0, preset='kick_boost')"},
        "configure_compressor": {"description": "Configura compresor en una pista", "category": "Mixing", "params": [{"name": "track_index", "type": "int"}, {"name": "preset", "type": "str", "default": "default"}, {"name": "threshold", "type": "float", "default": -20.0}, {"name": "ratio", "type": "float", "default": 4.0}], "example": "configure_compressor(track_index=1, threshold=-20.0, ratio=4.0)"},
        "setup_sidechain": {"description": "Configura compresion sidechain", "category": "Mixing", "params": [{"name": "source_track", "type": "int"}, {"name": "target_track", "type": "int"}, {"name": "amount", "type": "float", "range": "0.0-1.0"}], "example": "setup_sidechain(source_track=0, target_track=1, amount=0.5)"},
        "auto_gain_staging": {"description": "Ajusta automaticamente niveles de ganancia", "category": "Mixing", "params": [], "example": "auto_gain_staging()"},
        "apply_master_chain": {"description": "Aplica cadena de mastering al master", "category": "Mixing", "params": [{"name": "preset", "type": "str", "default": "standard"}], "example": "apply_master_chain(preset='reggaeton_streaming')"},
        # Arrangement
        "create_arrangement_audio_pattern": {"description": "Crea clips de audio en Arrangement View", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "file_path", "type": "str"}, {"name": "positions", "type": "list", "default": [0]}, {"name": "name", "type": "str", "optional": True}], "example": "create_arrangement_audio_pattern(track_index=0, file_path='...', positions=[0, 4, 8])"},
        "load_sample_to_clip": {"description": "Carga sample en clip de Session View", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int"}, {"name": "sample_path", "type": "str"}], "example": "load_sample_to_clip(track_index=0, clip_index=0, sample_path='...')"},
        "duplicate_clip": {"description": "Duplica un clip a otro slot de Session View", "category": "Arrangement", "params": [{"name": "source_track", "type": "int"}, {"name": "source_clip", "type": "int"}, {"name": "target_track", "type": "int"}, {"name": "target_clip", "type": "int"}], "example": "duplicate_clip(source_track=0, source_clip=0, target_track=0, target_clip=1)"},
        "load_sample_to_drum_rack": {"description": "Carga sample en pad de Drum Rack", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "sample_path", "type": "str"}, {"name": "pad_note", "type": "int", "default": 36}], "example": "load_sample_to_drum_rack(track_index=0, sample_path='...', pad_note=36)"},
        "set_warp_markers": {"description": "Configura marcadores de warp", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int"}, {"name": "markers", "type": "list"}], "example": "set_warp_markers(track_index=0, clip_index=0, markers=[...])"},
        "reverse_clip": {"description": "Invierte un clip", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int"}], "example": "reverse_clip(track_index=0, clip_index=0)"},
        "pitch_shift_clip": {"description": "Cambia tono de clip (-24 a +24 semitonos)", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int"}, {"name": "semitones", "type": "float", "range": "-24 a +24"}], "example": "pitch_shift_clip(track_index=0, clip_index=0, semitones=-2)"},
        "time_stretch_clip": {"description": "Estira tiempo de clip (0.25x a 4.0x)", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int"}, {"name": "factor", "type": "float", "range": "0.25-4.0"}], "example": "time_stretch_clip(track_index=0, clip_index=0, factor=1.5)"},
        "slice_clip": {"description": "Divide clip en segmentos (2-64)", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int"}, {"name": "num_slices", "type": "int", "default": 8}], "example": "slice_clip(track_index=0, clip_index=0, num_slices=8)"},
        # Production
        "generate_track": {"description": "Genera una pista con IA", "category": "Production", "params": [{"name": "genre", "type": "str"}, {"name": "style", "type": "str", "optional": True}, {"name": "bpm", "type": "float", "default": 0}, {"name": "key", "type": "str", "optional": True}, {"name": "structure", "type": "str", "default": "standard"}], "example": "generate_track(genre='reggaeton', bpm=95, key='Am')"},
        "generate_song": {"description": "Genera cancion completa con IA", "category": "Production", "params": [{"name": "genre", "type": "str"}, {"name": "style", "type": "str", "optional": True}, {"name": "bpm", "type": "float", "default": 0}, {"name": "key", "type": "str", "optional": True}, {"name": "structure", "type": "str", "default": "standard"}], "example": "generate_song(genre='reggaeton', bpm=95, key='Am')"},
        "select_samples_for_genre": {"description": "Selecciona samples para un genero", "category": "Production", "params": [{"name": "genre", "type": "str"}, {"name": "key", "type": "str", "optional": True}, {"name": "bpm", "type": "float", "default": 0}], "example": "select_samples_for_genre(genre='reggaeton', key='Am', bpm=95)"},
        "generate_complete_reggaeton": {"description": "Genera proyecto completo de reggaeton", "category": "Production", "params": [{"name": "bpm", "type": "float", "default": 95}, {"name": "key", "type": "str", "default": "Am"}, {"name": "style", "type": "str", "default": "classic"}, {"name": "structure", "type": "str", "default": "verse-chorus"}, {"name": "use_samples", "type": "bool", "default": True}], "example": "generate_complete_reggaeton(bpm=95, key='Am', style='classic')"},
        "generate_from_reference": {"description": "Genera track desde audio de referencia", "category": "Production", "params": [{"name": "reference_audio_path", "type": "str"}], "example": "generate_from_reference(reference_audio_path='...')"},
        "produce_reggaeton": {"description": "Pipeline completo de produccion reggaeton", "category": "Production", "params": [{"name": "bpm", "type": "float", "default": 95}, {"name": "key", "type": "str", "default": "Am"}, {"name": "style", "type": "str", "default": "classic"}, {"name": "structure", "type": "str", "default": "verse-chorus"}], "example": "produce_reggaeton(bpm=95, key='Am', style='classic', structure='verse-chorus')"},
        "produce_from_reference": {"description": "Genera produccion desde referencia", "category": "Production", "params": [{"name": "audio_path", "type": "str"}], "example": "produce_from_reference(audio_path='...')"},
        "produce_arrangement": {"description": "Genera produccion en Arrangement View", "category": "Production", "params": [{"name": "bpm", "type": "float", "default": 95}, {"name": "key", "type": "str", "default": "Am"}, {"name": "style", "type": "str", "default": "classic"}], "example": "produce_arrangement(bpm=95, key='Am', style='classic')"},
        "complete_production": {"description": "Pipeline completo con renderizado", "category": "Production", "params": [{"name": "bpm", "type": "float", "default": 95}, {"name": "key", "type": "str", "default": "Am"}, {"name": "style", "type": "str", "default": "classic"}, {"name": "output_dir", "type": "str", "optional": True}], "example": "complete_production(bpm=95, key='Am', style='classic')"},
        "batch_produce": {"description": "Produce multiples canciones en lote", "category": "Production", "params": [{"name": "count", "type": "int", "default": 3}, {"name": "style", "type": "str", "default": "classic"}, {"name": "bpm_range", "type": "str", "default": "90-100"}], "example": "batch_produce(count=3, style='classic', bpm_range='90-100')"},
        "generate_midi_clip": {"description": "Crea clip MIDI con notas especificas", "category": "Production", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int", "default": 0}, {"name": "notes", "type": "list", "optional": True}], "example": "generate_midi_clip(track_index=0, clip_index=0, notes=[...])"},
        "generate_dembow_clip": {"description": "Genera clip MIDI con patron dembow", "category": "Production", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int", "default": 0}, {"name": "bars", "type": "int", "default": 4}, {"name": "variation", "type": "str", "default": "standard"}], "example": "generate_dembow_clip(track_index=0, clip_index=0, bars=4, variation='standard')"},
        "generate_bass_clip": {"description": "Genera clip MIDI de bajo reggaeton", "category": "Production", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int", "default": 0}, {"name": "bars", "type": "int", "default": 4}, {"name": "root_notes", "type": "list", "optional": True}, {"name": "style", "type": "str", "default": "standard"}], "example": "generate_bass_clip(track_index=1, clip_index=0, bars=4, style='standard')"},
        "generate_chords_clip": {"description": "Genera clip MIDI de acordes", "category": "Production", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int", "default": 0}, {"name": "bars", "type": "int", "default": 4}, {"name": "progression", "type": "str", "default": "i-v-vi-iv"}, {"name": "key", "type": "str", "default": "Am"}], "example": "generate_chords_clip(track_index=2, clip_index=0, bars=4, progression='i-v-vi-iv', key='Am')"},
        "generate_melody_clip": {"description": "Genera clip MIDI de melodia", "category": "Production", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int", "default": 0}, {"name": "bars", "type": "int", "default": 4}, {"name": "scale", "type": "str", "default": "minor"}, {"name": "density", "type": "str", "default": "medium"}], "example": "generate_melody_clip(track_index=3, clip_index=0, bars=4, scale='minor', density='medium')"},
        "load_samples_for_genre": {"description": "Selecciona y carga samples para genero", "category": "Production", "params": [{"name": "genre", "type": "str"}, {"name": "key", "type": "str", "optional": True}, {"name": "bpm", "type": "float", "default": 0}], "example": "load_samples_for_genre(genre='reggaeton', key='Am', bpm=95)"},
        "create_drum_kit": {"description": "Crea drum kit en Drum Rack", "category": "Production", "params": [{"name": "track_index", "type": "int"}, {"name": "kick_path", "type": "str", "optional": True}, {"name": "snare_path", "type": "str", "optional": True}, {"name": "hat_path", "type": "str", "optional": True}, {"name": "clap_path", "type": "str", "optional": True}], "example": "create_drum_kit(track_index=0, kick_path='...', snare_path='...', hat_path='...', clap_path='...')"},
        "build_track_from_samples": {"description": "Construye pista completa desde samples", "category": "Production", "params": [{"name": "track_type", "type": "str", "default": "drums"}, {"name": "sample_role", "type": "str", "default": "drums"}], "example": "build_track_from_samples(track_type='drums', sample_role='drums')"},
        "generate_full_song": {"description": "Genera cancion completa con drums/bass/chords/melody", "category": "Production", "params": [{"name": "bpm", "type": "float", "default": 95}, {"name": "key", "type": "str", "default": "Am"}, {"name": "style", "type": "str", "default": "classic"}, {"name": "structure", "type": "str", "default": "standard"}], "example": "generate_full_song(bpm=95, key='Am', style='classic')"},
        "generate_track_from_config": {"description": "Genera pista desde JSON config", "category": "Production", "params": [{"name": "track_config_json", "type": "str"}], "example": "generate_track_from_config(track_config_json='{\"type\":\"drums\",\"pattern\":\"dembow\",\"bars\":8}')"},
        "generate_section": {"description": "Genera seccion de cancion desde JSON", "category": "Production", "params": [{"name": "section_config_json", "type": "str"}, {"name": "start_bar", "type": "int", "default": 0}], "example": "generate_section(section_config_json='{\"type\":\"verse\",\"bars\":16,\"elements\":[\"drums\",\"bass\"]}', start_bar=0)"},
        "apply_human_feel": {"description": "Humaniza pista MIDI (0.0-1.0)", "category": "Production", "params": [{"name": "track_index", "type": "int"}, {"name": "intensity", "type": "float", "range": "0.0-1.0"}], "example": "apply_human_feel(track_index=0, intensity=0.3)"},
        "add_percussion_fills": {"description": "Aniade fills de percusion", "category": "Production", "params": [{"name": "track_index", "type": "int"}, {"name": "positions", "type": "list", "default": [7, 15, 23, 31]}], "example": "add_percussion_fills(track_index=0, positions=[7, 15, 23, 31])"},
        # Musical Intelligence
        "analyze_project_key": {"description": "Detecta tonalidad del proyecto", "category": "Musical Intelligence", "params": [], "example": "analyze_project_key()"},
        "harmonize_track": {"description": "Armoniza pista con progresion", "category": "Musical Intelligence", "params": [{"name": "track_index", "type": "int"}, {"name": "progression", "type": "str", "default": "I-V-vi-IV"}], "example": "harmonize_track(track_index=2, progression='I-V-vi-IV')"},
        "generate_counter_melody": {"description": "Genera contra-melodia", "category": "Musical Intelligence", "params": [{"name": "main_melody_track", "type": "int"}], "example": "generate_counter_melody(main_melody_track=3)"},
        "detect_energy_curve": {"description": "Analiza curva de energia por seccion", "category": "Musical Intelligence", "params": [], "example": "detect_energy_curve()"},
        "balance_sections": {"description": "Ajusta energia entre secciones", "category": "Musical Intelligence", "params": [], "example": "balance_sections()"},
        "variate_loop": {"description": "Crea variaciones de loop (0.0-1.0)", "category": "Musical Intelligence", "params": [{"name": "track_index", "type": "int"}, {"name": "intensity", "type": "float", "range": "0.0-1.0"}], "example": "variate_loop(track_index=0, intensity=0.5)"},
        "add_call_and_response": {"description": "Genera respuesta musical a frase", "category": "Musical Intelligence", "params": [{"name": "phrase_track", "type": "int"}, {"name": "response_length", "type": "int", "default": 2}], "example": "add_call_and_response(phrase_track=3, response_length=2)"},
        "generate_breakdown": {"description": "Genera seccion breakdown", "category": "Musical Intelligence", "params": [{"name": "start_bar", "type": "int"}, {"name": "duration", "type": "int", "default": 8}], "example": "generate_breakdown(start_bar=32, duration=8)"},
        "generate_drop_variation": {"description": "Genera variacion de drop", "category": "Musical Intelligence", "params": [{"name": "original_drop_bar", "type": "int"}, {"name": "variation_type", "type": "str", "default": "intense"}], "example": "generate_drop_variation(original_drop_bar=16, variation_type='intense')"},
        "create_outro": {"description": "Crea outro con fade out", "category": "Musical Intelligence", "params": [{"name": "fade_duration", "type": "int", "default": 8}], "example": "create_outro(fade_duration=8)"},
        # Workflow
        "export_project": {"description": "Exporta proyecto a archivo de audio", "category": "Workflow", "params": [{"name": "path", "type": "str"}, {"name": "format", "type": "str", "default": "wav"}], "example": "export_project(path='C:\\\\output.wav', format='wav')"},
        "get_project_summary": {"description": "Obtiene resumen del proyecto", "category": "Workflow", "params": [], "example": "get_project_summary()"},
        "suggest_improvements": {"description": "Sugerencias IA para mejorar proyecto", "category": "Workflow", "params": [], "example": "suggest_improvements()"},
        "validate_project": {"description": "Valida consistencia del proyecto", "category": "Workflow", "params": [], "example": "validate_project()"},
        "humanize_track": {"description": "Humaniza pista MIDI (0.0-1.0)", "category": "Workflow", "params": [{"name": "track_index", "type": "int"}, {"name": "intensity", "type": "float", "range": "0.0-1.0"}], "example": "humanize_track(track_index=0, intensity=0.5)"},
        "load_preset": {"description": "Carga preset en proyecto", "category": "Workflow", "params": [{"name": "preset_name", "type": "str"}], "example": "load_preset(preset_name='reggaeton_basic')"},
        "save_as_preset": {"description": "Guarda proyecto como preset", "category": "Workflow", "params": [{"name": "name", "type": "str"}, {"name": "description", "type": "str", "optional": True}], "example": "save_as_preset(name='mi_preset', description='Mi template de reggaeton')"},
        "list_presets": {"description": "Lista presets disponibles", "category": "Workflow", "params": [], "example": "list_presets()"},
        "create_custom_preset": {"description": "Crea preset personalizado", "category": "Workflow", "params": [{"name": "name", "type": "str"}, {"name": "description", "type": "str", "optional": True}], "example": "create_custom_preset(name='nuevo_preset', description='...')"},
        "render_stems": {"description": "Renderiza stems individuales", "category": "Workflow", "params": [{"name": "output_dir", "type": "str"}], "example": "render_stems(output_dir='C:\\\\stems\\\\')"},
        "render_full_mix": {"description": "Renderiza mix completo masterizado", "category": "Workflow", "params": [{"name": "output_path", "type": "str"}], "example": "render_full_mix(output_path='C:\\\\mix_final.wav')"},
        "render_instrumental": {"description": "Renderiza version instrumental", "category": "Workflow", "params": [{"name": "output_path", "type": "str"}], "example": "render_instrumental(output_path='C:\\\\instrumental.wav')"},
        "full_quality_check": {"description": "Verificacion de calidad completa", "category": "Workflow", "params": [], "example": "full_quality_check()"},
        "fix_quality_issues": {"description": "Arregla problemas de calidad", "category": "Workflow", "params": [{"name": "issues", "type": "list", "optional": True}], "example": "fix_quality_issues(issues=[])"},
        "duplicate_project": {"description": "Duplica proyecto con nuevo nombre", "category": "Workflow", "params": [{"name": "new_name", "type": "str"}], "example": "duplicate_project(new_name='mi_track_v2')"},
        "create_radio_edit": {"description": "Crea version radio edit", "category": "Workflow", "params": [{"name": "output_path", "type": "str"}], "example": "create_radio_edit(output_path='C:\\\\radio_edit.wav')"},
        "create_dj_edit": {"description": "Crea version DJ edit", "category": "Workflow", "params": [{"name": "output_path", "type": "str"}], "example": "create_dj_edit(output_path='C:\\\\dj_edit.wav')"},
        "get_production_report": {"description": "Genera reporte completo de produccion", "category": "Workflow", "params": [], "example": "get_production_report()"},
        # Diagnostics
        "get_memory_usage": {"description": "Uso de memoria del sistema", "category": "Diagnostics", "params": [], "example": "get_memory_usage()"},
        "get_progress_report": {"description": "Reporte de progreso del proyecto", "category": "Diagnostics", "params": [], "example": "get_progress_report()"},
        # System
        "ping": {"description": "Ping simple para verificar conectividad MCP", "category": "System", "params": [], "example": "ping()"},
        "help": {"description": "Lista todas las tools o ayuda detallada de una tool", "category": "System", "params": [{"name": "tool_name", "type": "str", "optional": True}], "example": "help() o help(tool_name='produce_reggaeton')"},
        "get_workflow_status": {"description": "Estado actual del workflow de produccion", "category": "System", "params": [], "example": "get_workflow_status()"},
        "undo": {"description": "Deshace ultima accion", "category": "System", "params": [], "example": "undo()"},
        "redo": {"description": "Rehace ultima accion deshecha", "category": "System", "params": [], "example": "redo()"},
        "save_checkpoint": {"description": "Guarda checkpoint del proyecto", "category": "System", "params": [{"name": "name", "type": "str", "default": "auto"}], "example": "save_checkpoint(name='antes_mejora')"},
        "set_multiple_progressions": {"description": "Configura progresiones para multiples secciones", "category": "System", "params": [{"name": "progressions_config", "type": "list"}], "example": "set_multiple_progressions(progressions_config=[...])"},
        "modulate_key": {"description": "Modula a nueva tonalidad en seccion", "category": "System", "params": [{"name": "section_index", "type": "int"}, {"name": "new_key", "type": "str"}], "example": "modulate_key(section_index=2, new_key='Dm')"},
        "enable_parallel_processing": {"description": "Activa/desactiva procesamiento paralelo", "category": "System", "params": [{"name": "enabled", "type": "bool", "default": True}], "example": "enable_parallel_processing(enabled=True)"},
    }

    # Si se proporciona tool_name, devolver ayuda detallada
    if tool_name:
        tool_name_lower = tool_name.lower()
        matches = {k: v for k, v in tools_db.items() if k.lower() == tool_name_lower}
        if not matches:
            # Fuzzy match
            matches = {k: v for k, v in tools_db.items() if tool_name_lower in k.lower()}
        if not matches:
            return _err(f"Tool '{tool_name}' not found. Use help() without arguments to see all tools.")
        results = []
        for name, info in matches.items():
            params_str = ", ".join(
                p["name"] + (" (optional)" if p.get("optional") else "") + ": " + p["type"]
                for p in info.get("params", [])
            )
            results.append({
                "name": name,
                "description": info["description"],
                "category": info["category"],
                "parameters": params_str if params_str else "None",
                "example": info["example"],
            })
        return _ok({"tool_help": results[0] if len(results) == 1 else results})

    # Sin tool_name: listar todas las tools organizadas por categoria
    by_category = {}
    for name, info in tools_db.items():
        cat = info["category"]
        if cat not in by_category:
            by_category[cat] = []
        by_category[cat].append({"name": name, "description": info["description"]})

    return _ok({
        "total_tools": len(tools_db),
        "categories": sorted(by_category.keys()),
        "tools_by_category": by_category,
        "usage": "Use help(tool_name='toolname') for detailed help on a specific tool.",
    })


@mcp.tool()
def get_workflow_status(ctx: Context) -> str:
    """Obtiene el estado actual del workflow de produccion con proximos pasos accionables (T100).

    Returna:
    - Estado actual del proyecto (tracks, clips, scenes)
    - Configuracion de mezcla
    - Contenido del arrangement
    - Proximos pasos recomendados
    """
    try:
        # Get session info
        session_resp = _send_to_ableton("get_session_info", timeout=TIMEOUTS["get_session_info"])
        session_data = {}
        if session_resp.get("status") == "success":
            r = session_resp.get("result", {})
            session_data = {
                "tempo": r.get("tempo"),
                "num_tracks": r.get("num_tracks", 0),
                "num_scenes": r.get("num_scenes", 0),
                "is_playing": r.get("is_playing", False),
                "current_song_time": r.get("current_song_time", 0),
            }

        # Get tracks detail
        tracks_resp = _send_to_ableton("get_tracks", timeout=TIMEOUTS["get_tracks"])
        tracks_data = {}
        has_mixing_config = False
        has_arrangement_content = False
        if tracks_resp.get("status") == "success":
            tracks = _ableton_result(tracks_resp).get("tracks", [])
            tracks_data = {
                "count": len(tracks),
                "midi_tracks": len([t for t in tracks if t.get("type") == "midi"]),
                "audio_tracks": len([t for t in tracks if t.get("type") == "audio"]),
                "track_names": [t.get("name", "") for t in tracks],
                "muted": [t.get("name", "") for t in tracks if t.get("mute")],
                "soloed": [t.get("name", "") for t in tracks if t.get("solo")],
            }
            # Check if mixing is configured (return tracks, sends, etc.)
            return_tracks = _ableton_result(tracks_resp).get("return_tracks", [])
            has_mixing_config = len(return_tracks) > 0 or any(t.get("devices") for t in tracks)
            # Check arrangement content
            has_arrangement_content = any(t.get("arrangement_clips", 0) > 0 for t in tracks)

        # Determine next steps based on current state
        next_steps = []
        num_tracks = session_data.get("num_tracks", 0)
        if num_tracks == 0:
            next_steps.append("1. Crear pistas: create_midi_track() o create_audio_track()")
            next_steps.append("2. Generar contenido: produce_reggaeton(bpm=95, key='Am', style='classic')")
        elif not has_arrangement_content:
            next_steps.append("1. Generar clips en pistas: generate_dembow_clip(), generate_bass_clip(), etc.")
            next_steps.append("2. O usar pipeline automatico: produce_reggaeton(bpm=95, key='Am')")
            next_steps.append("3. O construir arrangement: produce_arrangement(bpm=95, key='Am')")

        if num_tracks > 0 and not has_mixing_config:
            next_steps.append("Configurar mezcla: create_bus_track(), configure_eq(), configure_compressor(), setup_sidechain()")

        if num_tracks > 0 and has_arrangement_content:
            next_steps.append("Verificar calidad: full_quality_check()")
            next_steps.append("Humanizar: apply_human_feel(track_index=0, intensity=0.3)")
            next_steps.append("Exportar: render_stems(output_dir='...'), render_full_mix(output_path='...')")

        if not next_steps:
            next_steps.append("Ejecutar health_check() para verificar estado del sistema")
            next_steps.append("Usar produce_reggaeton() para iniciar produccion rapida")

        return _ok({
            "project_status": {
                "tempo": session_data.get("tempo"),
                "tracks": tracks_data,
                "num_scenes": session_data.get("num_scenes", 0),
                "is_playing": session_data.get("is_playing", False),
            },
            "mixing_configured": has_mixing_config,
            "arrangement_has_content": has_arrangement_content,
            "next_steps": next_steps,
        })
    except Exception as e:
        return _err(f"Error getting workflow status: {str(e)}")


@mcp.tool()
def undo(ctx: Context) -> str:
    """Deshace la ultima accion (T098)."""
    return _proxy_ableton_command("undo", timeout=TIMEOUTS["undo"])


@mcp.tool()
def redo(ctx: Context) -> str:
    """Rehace la ultima accion deshecha (T098)."""
    return _proxy_ableton_command("redo", timeout=TIMEOUTS["redo"])


@mcp.tool()
def save_checkpoint(ctx: Context, name: str = "auto") -> str:
    """Guarda un checkpoint del proyecto actual (T099).

    Args:
        name: Nombre del checkpoint
    """
    return _proxy_ableton_command(
        "save_checkpoint",
        {"name": name},
        timeout=TIMEOUTS["save_checkpoint"],
        defaults={"name": name},
    )


@mcp.tool()
def get_production_report(ctx: Context) -> str:
    """Genera un reporte completo de produccion (T100)."""
    try:
        from engines.workflow_engine import WorkflowEngine
        engine = WorkflowEngine()
        result = engine.get_production_report()
        return _ok({
            "project_name": result.get("project_name", "Untitled"),
            "duration": result.get("duration", "0:00"),
            "total_tracks": result.get("total_tracks", 0),
            "midi_clips": result.get("midi_clips", 0),
            "audio_clips": result.get("audio_clips", 0),
            "devices_used": result.get("devices", []),
            "samples_used": result.get("samples", []),
            "production_time": result.get("production_time", "unknown"),
            "export_history": result.get("exports", []),
            "quality_score": result.get("quality_score", 0)
        })
    except Exception as e:
        return _err(f"Error getting production report: {str(e)}")


# ==================================================================
# EXTRAS (T086-T095)
# ==================================================================

@mcp.tool()
def set_multiple_progressions(ctx: Context, progressions_config: list) -> str:
    """Configura progresiones de acordes para multiples secciones (T086).

    Args:
        progressions_config: Lista de dicts con {"section": "intro", "progression": "I-V-vi-IV"}
    """
    try:
        from engines.musical_intelligence import MusicalIntelligenceEngine
        engine = MusicalIntelligenceEngine()
        result = engine.set_multiple_progressions(progressions_config)
        return _ok({
            "sections_configured": result.get("sections", []),
            "progressions_applied": result.get("progressions", []),
            "chords_generated": result.get("total_chords", 0)
        })
    except Exception as e:
        return _err(f"Error setting progressions: {str(e)}")


@mcp.tool()
def modulate_key(ctx: Context, section_index: int, new_key: str) -> str:
    """Modula a una nueva key en una seccion especifica (T087).

    Args:
        section_index: Indice de la seccion
        new_key: Nueva tonalidad (ej: "Dm", "F#m", "C")
    """
    try:
        from engines.musical_intelligence import MusicalIntelligenceEngine
        engine = MusicalIntelligenceEngine()
        result = engine.modulate_key(section_index, new_key)
        return _ok({
            "section_index": section_index,
            "original_key": result.get("original_key"),
            "new_key": new_key,
            "modulation_type": result.get("modulation_type", "direct"),
            "tracks_affected": result.get("tracks_affected", [])
        })
    except Exception as e:
        return _err(f"Error modulating key: {str(e)}")


@mcp.tool()
def enable_parallel_processing(ctx: Context, enabled: bool = True) -> str:
    """Activa/desactiva procesamiento paralelo para operaciones pesadas (T092).

    Args:
        enabled: True para activar, False para desactivar
    """
    try:
        from engines.workflow_engine import WorkflowEngine
        engine = WorkflowEngine()
        result = engine.set_parallel_processing(enabled)
        return _ok({
            "parallel_processing": enabled,
            "max_workers": result.get("max_workers", 4),
            "affected_operations": result.get("operations", ["render", "analyze", "generate"])
        })
    except Exception as e:
        return _err(f"Error setting parallel processing: {str(e)}")


@mcp.tool()
def get_memory_usage(ctx: Context) -> str:
    """Obtiene el uso de memoria del sistema y del proyecto (T094)."""
    try:
        import psutil
        process = psutil.Process()
        system_memory = psutil.virtual_memory()
        return _ok({
            "process_memory_mb": process.memory_info().rss / 1024 / 1024,
            "process_memory_percent": process.memory_percent(),
            "system_total_mb": system_memory.total / 1024 / 1024,
            "system_available_mb": system_memory.available / 1024 / 1024,
            "system_percent_used": system_memory.percent,
            "live_processes": len([p for p in psutil.process_iter() if "ableton" in p.name().lower()])
        })
    except ImportError:
        return _err("psutil not available. Install with: pip install psutil")
    except Exception as e:
        return _err(f"Error getting memory usage: {str(e)}")


@mcp.tool()
def get_progress_report(ctx: Context) -> str:
    """Reporte detallado de progreso del proyecto actual (T095)."""
    try:
        from engines.workflow_engine import WorkflowEngine
        engine = WorkflowEngine()
        result = engine.get_progress_report()
        return _ok({
            "project_completion": result.get("completion", 0),
            "phases_completed": result.get("phases_completed", []),
            "current_phase": result.get("current_phase", "unknown"),
            "tasks_done": result.get("tasks_done", 0),
            "tasks_total": result.get("tasks_total", 0),
            "time_invested": result.get("time_invested", "0h 0m"),
            "milestones": result.get("milestones", [])
        })
    except Exception as e:
        return _err(f"Error getting progress report: {str(e)}")




# ==================================================================
# PLAYBACK, ARRANGEMENT & LIBRARY TOOLS (core fixes)
# ==================================================================

@mcp.tool()
def fire_all_clips(ctx: Context, scene_index: int = 0, start_playback: bool = True) -> str:
    """Fire every clip in a Session View scene so you can hear what was created.

    Call this immediately after any produce_* / generate_* command to start playback.
    Without this, clips exist in Live but are silent (they need to be fired).

    Args:
        scene_index: Which scene row to fire (default 0 = first scene)
        start_playback: Also call Start Playing on the transport (default True)
    """
    return _proxy_ableton_command(
        "fire_all_clips",
        {"scene_index": scene_index, "start_playback": start_playback},
        timeout=15.0,
    )


@mcp.tool()
def record_to_arrangement(ctx: Context, duration_bars: int = 8) -> str:
    """Record Session View clips into Arrangement View so you can see and edit them.

    Enables arrangement overdub, fires scene 0, records for `duration_bars` bars,
    then stops and switches Ableton to Arrangement View automatically.

    Args:
        duration_bars: How many bars to record (default 8)
    """
    return _proxy_ableton_command(
        "record_to_arrangement",
        {"duration_bars": duration_bars},
        timeout=duration_bars * 4.0 + 30.0,  # generous timeout
    )


@mcp.tool()
def scan_library(ctx: Context, subfolder: str = "", extensions: list = None) -> str:
    """Scan the libreria/ sample library and return all available samples categorized by folder.

    Use this to discover what samples are available before loading them.
    Returns file paths you can use with load_sample_direct.

    Args:
        subfolder: Sub-folder to scan e.g. "reggaeton/kick" (default = all)
        extensions: File extensions to include e.g. [".wav", ".mp3"] (default all audio)
    """
    params = {"subfolder": subfolder}
    if extensions:
        params["extensions"] = extensions
    return _proxy_ableton_command("scan_library", params, timeout=20.0)


@mcp.tool()
def load_sample_direct(ctx: Context, track_index: int, file_path: str,
                        slot_index: int = 0, warp: bool = True,
                        auto_fire: bool = False) -> str:
    """Load a sample from libreria/ directly onto a track by absolute file path.

    This is the most reliable way to use your sample library â€” bypasses the
    Live browser entirely. Works with any WAV, AIF, or MP3 file.

    Args:
        track_index: Track index in Ableton (0-based)
        file_path: Absolute path OR path relative to libreria/ root
        slot_index: Clip slot index (default 0)
        warp: Enable warping/tempo-sync (default True)
        auto_fire: Fire the clip immediately after loading (default False)
    """
    return _proxy_ableton_command(
        "load_sample_direct",
        {
            "track_index": track_index,
            "file_path": file_path,
            "slot_index": slot_index,
            "warp": warp,
            "auto_fire": auto_fire,
        },
        timeout=20.0,
    )


@mcp.tool()
def produce_with_library(ctx: Context, genre: str = "reggaeton", tempo: int = 95,
                          key: str = "Am", bars: int = 16,
                          auto_play: bool = True,
                          record_arrangement: bool = True) -> str:
    """Complete one-shot music production using your real 511-sample library (Session View).

    DEPRECATED: Consider using build_arrangement_timeline() for direct Arrangement View creation.

    This tool creates content in Session View, which is Ableton's clip-launching paradigm.
    For direct timeline-based composition, use build_arrangement_timeline() instead.

    What it does:
    1. Sets project tempo
    2. Loads real drum samples (kick, snare, clap, hihat) from libreria/
    3. Loads bass samples from libreria/
    4. Generates a MIDI dembow drum pattern
    5. Generates a MIDI bass line
    6. Generates chord progression
    7. Records to Arrangement View (if record_arrangement=True)
    8. Fires all clips so you hear the result immediately

    MIGRATION GUIDE:
    - OLD (Session View): produce_with_library() â†’ Clips in Session View, optionally recorded
    - NEW (Arrangement): build_arrangement_timeline() â†’ Direct timeline placement
    - For timeline-based composition with precise bar positioning, use build_arrangement_timeline()

    Args:
        genre: Genre for sample selection, e.g. "reggaeton" (default "reggaeton")
        tempo: BPM (default 95)
        key: Musical key e.g. "Am", "Cm", "Gm" (default "Am")
        bars: Pattern length in bars (default 16)
        auto_play: Start playback immediately after building (default True)
        record_arrangement: Also record to Arrangement View (default True â€” changed from False)
    """
    return _proxy_ableton_command(
        "produce_with_library",
        {
            "genre": genre,
            "tempo": tempo,
            "key": key,
            "bars": bars,
            "auto_play": auto_play,
            "record_arrangement": record_arrangement,
        },
        timeout=120.0,
    )


@mcp.tool()
def build_song(ctx: Context,
               genre: str = "reggaeton",
               tempo: int = 95,
               key: str = "Am",
               style: str = "standard",
               auto_record: bool = True,
               gap_bars: float = 2.0) -> str:
    """Build a complete, intelligent song arrangement in Ableton Arrangement View.

    *** USE THIS TOOL TO CREATE MUSIC â€” it's the definitive production command. ***

    What it does automatically:
    - Scans your libreria/ sample library (511 samples)
    - Creates Kick, Snare, HiHat, Perc, Bass audio tracks with REAL samples
    - Creates Dembow, Bass MIDI, Chords, Melody MIDI tracks with generated patterns
    - Builds 5 song sections (Intro/Verse/Chorus/Bridge/Outro) each with different
      clip variations (sparse intro, full chorus with melody, etc.)
    - Records all sections to Arrangement View automatically section by section
    - Switches Ableton to Arrangement View when done

    The recording takes approximately:
      4+8+8+4+4 = 28 bars Ã— (60/tempo Ã— 4) seconds per bar

    At 95 BPM: ~70 seconds total recording time.
    Ableton will show clips appearing in the Arrangement as it records.

    Args:
        genre: "reggaeton" (default) â€” which library folder to use for samples
        tempo: Song BPM (default 95)
        key: Musical key e.g. "Am", "Cm", "Gm" (default "Am")
        style: Pattern style â€” "standard", "minimal", or "trap" (default "standard")
        auto_record: Record to Arrangement View automatically (default True)
        gap_bars: Bars of silence between sections (default 2.0, use 0 for no gap)
    """
    return _proxy_ableton_command(
        "build_song",
        {
            "genre": genre,
            "tempo": tempo,
            "key": key,
            "style": style,
            "auto_record": auto_record,
            "gap_bars": gap_bars,
        },
        timeout=300.0,  # 5 min â€” enough for 28-bar recording at any tempo
    )


@mcp.tool()
def build_session_production(ctx: Context,
                              genre: str = "reggaeton",
                              tempo: int = 95,
                              key: str = "Am",
                              style: str = "standard",
                              num_scenes: int = 8) -> str:
    """Build complete Session View production with 8+ scenes.
    
    100% Session View. Each scene has different clip combinations for natural gaps.
    
    Args:
        genre: Genre (default "reggaeton")
        tempo: BPM (default 95)
        key: Musical key (default "Am")
        style: Pattern style (default "standard")
        num_scenes: Number of scenes (default 8)
    """
    return _proxy_ableton_command(
        "build_session_production",
        {
            "genre": genre,
            "tempo": tempo,
            "key": key,
            "style": style,
            "num_scenes": num_scenes,
        },
        timeout=120.0,
    )


@mcp.tool()
def build_song_arrangement(ctx: Context,
                           genre: str = "reggaeton",
                           tempo: int = 95,
                           key: str = "Am",
                           style: str = "standard",
                           gap_bars: float = 2.0) -> str:
    """T014: Build song with direct Arrangement View placement (no Session View).
    
    Places clips DIRECTLY at calculated bar positions with gaps between sections.
    No Session View intermediate, no recording needed.
    
    Args:
        genre: Music genre (default "reggaeton")
        tempo: BPM (default 95)
        key: Musical key (default "Am")
        style: Pattern style (default "standard")
        gap_bars: Bars of silence between sections (default 2.0, use 0 for no gap)
    
    Returns:
        JSON with sections created, clips placed, and timeline positions
    """
    return _proxy_ableton_command(
        "build_song_arrangement",
        {
            "genre": genre,
            "tempo": tempo,
            "key": key,
            "style": style,
            "gap_bars": gap_bars,
        },
        timeout=60.0,
    )


@mcp.tool()
def produce_13_scenes(ctx: Context,
                     genre: str = "reggaeton",
                     tempo: int = 95,
                     key: str = "Am",
                     auto_play: bool = True,
                     record_arrangement: bool = True,
                     gap_bars: float = 2.0) -> str:
    """Sprint 7: Produce complete track with 13 scenes and 100+ unique samples.

    Uses the advanced sample rotation system with:
    - Energy-based sample filtering (soft/medium/hard)
    - Usage tracking to avoid consecutive repetition
    - 658 SentimientoLatino2025 samples (26 kicks, 26 snares, 34 drumloops,
      34 percs, 24 fx, 84 oneshots)
    - 13 complete scenes with specific flags (riser, impact, ambience, etc.)

    Scene Structure:
    1. Intro (4 bars, energy 0.20) - pad + ambience, no drums
    2. Verse A (8 bars, energy 0.50) - full drums + bass
    3. Verse B (8 bars, energy 0.60) - drums + bass + lead
    4. Pre-Chorus (4 bars, energy 0.75) - riser + anticipation
    5. Chorus A (8 bars, energy 0.95) - full arrangement + impact
    6. Chorus B (8 bars, energy 0.90) - alternative progression
    7. Verse C (8 bars, energy 0.55) - variation, sparse drums
    8. Chorus C (8 bars, energy 0.95) - rising intensity
    9. Bridge (4 bars, energy 0.40) - dark, modal borrowing
    10. Build Up (4 bars, energy 0.80) - crescendo + riser
    11. Final Chorus (8 bars, energy 1.00) - all layers, maximum impact
    12. Outro (4 bars, energy 0.30) - fade out elements
    13. End (2 bars, energy 0.00) - silence

    Args:
        genre: Genre for sample selection (default "reggaeton")
        tempo: BPM (default 95)
        key: Musical key e.g. "Am", "Cm", "Gm" (default "Am")
        auto_play: Start playback immediately after building (default True)
        record_arrangement: Also record to Arrangement View (default True)
        gap_bars: Bars of silence between sections (default 2.0, use 0 for no gap)
    """
    return _proxy_ableton_command(
        "produce_13_scenes",
        {
            "genre": genre,
            "tempo": tempo,
            "key": key,
            "auto_play": auto_play,
            "record_arrangement": record_arrangement,
            "gap_bars": gap_bars,
        },
        timeout=300.0,  # 5 min for 13 scenes recording
    )


@mcp.tool()
def get_recording_status(ctx: Context) -> str:
    """Check the progress of an in-progress arrangement recording.

    Use this to poll while build_song or record_to_arrangement is running.
    Returns current section name, phase, and seconds remaining in this section.
    """
    return _proxy_ableton_command("get_recording_status", {}, timeout=5.0)


@mcp.tool()
def stop_recording(ctx: Context) -> str:
    """Stop any in-progress arrangement recording immediately.

    Disables overdub, stops playback, and switches to Arrangement View.
    Use this if you need to abort a build_song recording.
    """
    return _proxy_ableton_command("stop_all_playback", {}, timeout=10.0)


# ==================================================================
# ARRANGEMENT-FIRST TOOLS (Direct timeline composition)
# ==================================================================
# These tools bypass Session View and create content directly in
# Arrangement View for timeline-based music production.

@mcp.tool()
def build_arrangement_timeline(ctx: Context,
                                sections_json: str,
                                genre: str = "reggaeton",
                                tempo: int = 95,
                                key: str = "Am",
                                style: str = "standard") -> str:
    """Build a complete song directly in Arrangement View.

    *** PREFERRED TOOL FOR TIMELINE-BASED COMPOSITION ***

    This is the ARRANGEMENT-FIRST alternative to produce_with_library().
    Instead of creating clips in Session View first, this tool places
    content directly on the Arrangement timeline at specified bar positions.

    MIGRATION GUIDE from Session View workflow:
    - OLD: produce_with_library() â†’ Session View clips â†’ record to arrangement
    - NEW: build_arrangement_timeline() â†’ Direct Arrangement View placement

    sections_json format example:
    [
      {
        "name": "Intro",
        "start_bar": 0,
        "duration_bars": 4,
        "tracks": [
          {"type": "drums", "variation": "minimal"},
          {"type": "bass", "variation": "sparse"}
        ]
      },
      {
        "name": "Verse",
        "start_bar": 4,
        "duration_bars": 16,
        "tracks": [
          {"type": "drums", "variation": "full"},
          {"type": "bass", "variation": "standard"},
          {"type": "chords", "variation": "i-v-vi-iv"}
        ]
      },
      {
        "name": "Chorus",
        "start_bar": 20,
        "duration_bars": 8,
        "tracks": [
          {"type": "drums", "variation": "full"},
          {"type": "bass", "variation": "melodic"},
          {"type": "chords", "variation": "i-v-vi-iv"},
          {"type": "melody", "variation": "lead"}
        ]
      }
    ]

    Track types: drums, bass, chords, melody, fx, perc
    Variations:
      - drums: minimal, standard, full, fill
      - bass: sparse, standard, melodic, staccato
      - chords: i-v-vi-iv, i-iv-v, i-vi-iv-v
      - melody: sparse, medium, dense, lead

    Args:
        sections_json: JSON string defining song sections with bar positions
        genre: Genre for sample selection (default "reggaeton")
        tempo: BPM (default 95)
        key: Musical key e.g. "Am", "Cm", "Gm" (default "Am")
        style: Pattern style â€” "standard", "minimal", "trap" (default "standard")

    Returns:
        JSON with arrangement summary including section positions and tracks created.
    """
    try:
        import json as json_lib
        sections = json_lib.loads(sections_json)

        # Validate sections
        if not isinstance(sections, list) or len(sections) == 0:
            return _err("sections_json must be a non-empty list of section objects")

        created_tracks = []
        created_sections = []

        # Create tracks first
        track_types = set()
        for section in sections:
            for track in section.get("tracks", []):
                track_types.add(track.get("type", "drums"))

        # Create each track in Arrangement View
        for track_type in track_types:
            track_result = _send_to_ableton(
                "create_arrangement_track",
                {"track_type": track_type, "name": f"{track_type.title()} Arr"},
                timeout=15.0
            )
            if track_result.get("status") == "success":
                created_tracks.append({
                    "type": track_type,
                    "index": track_result.get("result", {}).get("track_index", -1)
                })

        # Create sections at their bar positions
        for section in sections:
            section_name = section.get("name", "Section")
            start_bar = section.get("start_bar", 0)
            duration = section.get("duration_bars", 8)

            section_tracks = []
            for track_def in section.get("tracks", []):
                track_type = track_def.get("type", "drums")
                variation = track_def.get("variation", "standard")

                # Find the track index for this type
                track_index = None
                for t in created_tracks:
                    if t["type"] == track_type:
                        track_index = t["index"]
                        break

                if track_index is not None:
                    # Create section content
                    resp = _send_to_ableton(
                        "create_section_at_bar",
                        {
                            "track_index": track_index,
                            "section_type": section_name.lower(),
                            "at_bar": start_bar,
                            "duration_bars": duration,
                            "key": key,
                            "variation": variation
                        },
                        timeout=30.0
                    )
                    if resp.get("status") == "success":
                        section_tracks.append({
                            "type": track_type,
                            "variation": variation,
                            "track_index": track_index
                        })

            created_sections.append({
                "name": section_name,
                "start_bar": start_bar,
                "duration_bars": duration,
                "tracks": section_tracks
            })

        return _ok({
            "arrangement_type": "timeline_direct",
            "genre": genre,
            "tempo": tempo,
            "key": key,
            "style": style,
            "tracks_created": len(created_tracks),
            "sections_created": len(created_sections),
            "section_details": created_sections,
            "view": "Arrangement",
            "note": "Content created directly in Arrangement View (not Session View)"
        })

    except json_lib.JSONDecodeError as e:
        return _err(f"Invalid JSON in sections_json: {str(e)}")
    except Exception as e:
        logger.exception("build_arrangement_timeline: failed")
        return _err(f"Error building arrangement timeline: {str(e)}")


@mcp.tool()
def create_section_at_bar(ctx: Context,
                            track_index: int,
                            section_type: str,
                            at_bar: float,
                            duration_bars: float = 8,
                            key: str = "Am") -> str:
    """Create a song section (intro/verse/chorus/bridge/outro) at specific bar position.

    Creates content directly in Arrangement View at the specified bar position.
    This is a building block for timeline-based composition.

    Section types and their characteristics:
    - intro: Sparse arrangement, minimal drums, building elements
    - verse: Full drums, bass, chords; moderate energy
    - chorus: Full arrangement with melody, highest energy
    - bridge: Different progression, transitional energy
    - outro: Fading elements, breakdown
    - build: Rising energy, preparing for drop
    - drop: Maximum impact, all elements

    Args:
        track_index: Index of the target track
        section_type: Type of section â€” intro, verse, chorus, bridge, outro, build, drop
        at_bar: Starting bar position in the arrangement
        duration_bars: Length of the section in bars (default 8)
        key: Musical key for harmonic content (default "Am")

    Returns:
        JSON with section creation status and clip details.
    """
    # Map section types to content generation parameters
    section_configs = {
        "intro": {"density": "sparse", "variation": "minimal"},
        "verse": {"density": "medium", "variation": "standard"},
        "chorus": {"density": "full", "variation": "full"},
        "bridge": {"density": "medium", "variation": "melodic"},
        "outro": {"density": "sparse", "variation": "fade"},
        "build": {"density": "building", "variation": "rising"},
        "drop": {"density": "maximum", "variation": "impact"},
    }

    config = section_configs.get(section_type.lower(), section_configs["verse"])

    try:
        resp = _send_to_ableton(
            "create_section_at_bar",
            {
                "track_index": track_index,
                "section_type": section_type.lower(),
                "at_bar": at_bar,
                "duration_bars": duration_bars,
                "key": key,
                "density": config["density"],
                "variation": config["variation"]
            },
            timeout=120.0
        )

        if resp.get("status") == "success":
            return _ok({
                "track_index": track_index,
                "section_type": section_type,
                "at_bar": at_bar,
                "duration_bars": duration_bars,
                "key": key,
                "config": config,
                "view": "Arrangement",
                "message": f"Created {section_type} at bar {at_bar} on track {track_index}"
            })
        return _err(resp.get("message", f"Failed to create {section_type} at bar {at_bar}"))

    except Exception as e:
        logger.exception("create_section_at_bar: failed")
        return _err(f"Error creating section: {str(e)}")


@mcp.tool()
def create_arrangement_track(ctx: Context,
                              track_type: str,
                              name: str = None,
                              insert_at_bar: float = 0) -> str:
    """Create a new track directly in Arrangement View.

    Creates a track specifically for timeline-based arrangement composition.
    The track is ready for clips to be placed at specific bar positions.

    Track types and their purposes:
    - drums: Drum patterns, percussive elements
    - bass: Basslines, low-frequency content
    - chords: Harmonic content, pads, rhythmic chords
    - melody: Lead lines, melodic elements
    - fx: Effects, risers, impacts, transitions
    - perc: Additional percussion layers

    Args:
        track_type: Type of track â€” drums, bass, chords, melody, fx, perc
        name: Optional custom name for the track (default: auto-generated from type)
        insert_at_bar: Position hint for initial track focus (default 0)

    Returns:
        JSON with track creation status and track index.
    """
    try:
        # Auto-generate name if not provided
        if name is None:
            name = f"{track_type.title()} Arr"

        resp = _send_to_ableton(
            "create_arrangement_track",
            {
                "track_type": track_type,
                "name": name,
                "insert_at_bar": insert_at_bar
            },
            timeout=15.0
        )

        if resp.get("status") == "success":
            result = resp.get("result", {})
            return _ok({
                "track_index": result.get("track_index", -1),
                "track_type": track_type,
                "name": name,
                "view": "Arrangement",
                "message": f"Created {track_type} track '{name}' at index {result.get('track_index', -1)}"
            })
        return _err(resp.get("message", f"Failed to create {track_type} track"))

    except Exception as e:
        logger.exception("create_arrangement_track: failed")
        return _err(f"Error creating arrangement track: {str(e)}")


@mcp.tool()
def get_arrangement_status(ctx: Context) -> str:
    """Get detailed status of Arrangement View content.

    Returns information about all clips currently in the Arrangement View,
    including their positions, lengths, and track assignments.

    Use this to inspect the current timeline composition state.

    Returns:
        JSON with arrangement details:
        - total_clips: Number of clips in arrangement
        - arrangement_length_beats: Total length in beats
        - unique_start_positions: Sorted clip start points (bar map)
        - clips: List of clip details with track, name, position, length
        - tracks: Summary of tracks with clip counts
    """
    try:
        resp = _send_to_ableton(
            "get_arrangement_clips",
            {},
            timeout=10.0
        )

        if resp.get("status") == "success":
            result = resp.get("result", {})
            return _ok({
                "view": "Arrangement",
                "total_clips": result.get("total_clips", 0),
                "arrangement_length_beats": result.get("arrangement_length_beats", 0),
                "unique_start_positions": result.get("unique_start_positions", []),
                "clips": result.get("clips", []),
                "tracks_summary": result.get("tracks_summary", {}),
                "status": "ready" if result.get("total_clips", 0) > 0 else "empty"
            })
        return _err(resp.get("message", "Failed to get arrangement status"))

    except Exception as e:
        logger.exception("get_arrangement_status: failed")
        return _err(f"Error getting arrangement status: {str(e)}")


# ------------------------------------------------------------------
# SESSION VS ARRANGEMENT MIGRATION NOTES
# ------------------------------------------------------------------
# OLD SESSION-VIEW-FIRST TOOLS (Deprecated patterns):
#   - produce_with_library() â†’ Creates Session clips, optionally records
#   - produce_reggaeton() â†’ Session View based
#   - generate_*_clip() â†’ Creates clips in Session View slots
#
# NEW ARRANGEMENT-FIRST TOOLS (Preferred):
#   - build_arrangement_timeline() â†’ Direct timeline composition
#   - create_section_at_bar() â†’ Place sections at specific bars
#   - create_arrangement_track() â†’ Create timeline-ready tracks
#   - get_arrangement_status() â†’ Inspect timeline state
#   - generate_intelligent_track() â†’ One-prompt professional track creation
#
# RECOMMENDED WORKFLOW:
# 1. Use build_arrangement_timeline() for complete songs
# 2. Use create_section_at_bar() for individual sections
# 3. Use create_arrangement_track() for custom track layouts
# 4. Use get_arrangement_status() to verify timeline content
# 5. Use generate_intelligent_track() for one-prompt music creation
# ------------------------------------------------------------------


# ------------------------------------------------------------------
# INTELLIGENT TRACK GENERATION
# ------------------------------------------------------------------

@mcp.tool()
def generate_intelligent_track(ctx: Context,
                               description: str,
                               structure_type: str = "standard",
                               variation_level: str = "medium",
                               coherence_threshold: float = 0.90,
                               include_vocal_placeholder: bool = True,
                               surprise_mode: bool = False,
                               save_as_preset: bool = True) -> str:
    """Generate complete professional track with intelligent sample selection.
    
    ONE-PROMPT MUSIC CREATION:
    This tool creates a complete, professional-quality track from a single
    description. It handles sample selection, coherence validation, 
    arrangement creation, and mixing automatically.
    
    Args:
        description: Natural language description of desired track.
                    Examples:
                    - "reggaeton perreo intenso 95bpm Am"
                    - "romantico suave 90bpm Gm con piano"
                    - "trap oscuro 140bpm Cm, agresivo"
        
        structure_type: Song structure template.
                       Options: "tiktok" (30s), "short" (1min), 
                       "standard" (3min), "extended" (4-5min)
        
        variation_level: How much samples vary between sections.
                        "low" = same samples throughout
                        "medium" = subtle variations  
                        "high" = distinct but coherent variations
        
        coherence_threshold: Minimum professional coherence (0.0-1.0).
                           Default 0.90 (professional grade).
                           Will iterate until achieved or fail explicitly.
        
        include_vocal_placeholder: Add empty track for vocals.
        
        surprise_mode: If True, introduces controlled randomness
                      for unique but coherent results each time.
        
        save_as_preset: Save the resulting kit as reusable preset.
    
    Returns:
        JSON with complete track info, coherence scores, rationale,
        and preset name if saved.
        
    Example:
        generate_intelligent_track(
            description="reggaeton perreo intenso 95bpm Am",
            structure_type="standard",
            variation_level="high",
            coherence_threshold=0.90
        )
    """
    return _proxy_ableton_command(
        "generate_intelligent_track",
        {
            "description": description,
            "structure_type": structure_type,
            "variation_level": variation_level,
            "coherence_threshold": coherence_threshold,
            "include_vocal_placeholder": include_vocal_placeholder,
            "surprise_mode": surprise_mode,
            "save_as_preset": save_as_preset,
        },
        timeout=300.0,  # 5 minutes for full track generation
        defaults={
            "description": description,
            "structure_type": structure_type,
        }
    )


@mcp.tool()
def generate_expansive_track(
    ctx: Context,
    description: str,
    samples_per_category: int = 12,
    variation_strategy: str = "combined",
    coherence_threshold: float = 0.90,
    structure_type: str = "standard"
) -> str:
    """Generate expansive track production with extensive sample library utilization.

    Creates a complete track production using an expansive selection of samples
    across multiple categories (drums, bass, synths, FX, vocals). This tool enables
    rich, layered productions by pulling more samples per category than standard
    track generation, providing greater variety and depth.

    Args:
        description: Natural language description of desired track.
                    Examples:
                    - "reggaeton perreo intenso 95bpm Am"
                    - "romantico suave 90bpm Gm con piano"
                    - "trap oscuro 140bpm Cm, agresivo"

        samples_per_category: Number of samples to select per category (8-16).
                             Higher values create richer, more layered tracks.
                             Default 12 provides a good balance.

        variation_strategy: How to vary samples across sections.
                          Options:
                          - "combined": Mix of all strategies (default)
                          - "sequential": Progressive sample addition
                          - "random": Random sample selection per section
                          - "coherent": Similar samples with subtle variations

        coherence_threshold: Minimum professional coherence (0.80-0.95).
                            Default 0.90 ensures professional-grade consistency.
                            Lower values allow more experimental combinations.

        structure_type: Song structure template.
                       Options: "tiktok" (30s), "short" (1min),
                       "standard" (3min), "extended" (4-5min)

    Returns:
        JSON with complete production details:
        - status: "success" or "error"
        - tracks_created: List of track names created
        - samples_used: Dict mapping roles to lists of samples used
        - coherence_scores: Dict mapping roles to coherence scores
        - total_clips: Total number of clips created
        - qa_score: Quality assurance score (0.0-1.0)
        - message: Human-readable status message

    Example:
        generate_expansive_track(
            description="reggaeton perreo intenso 95bpm Am",
            samples_per_category=12,
            variation_strategy="combined",
            coherence_threshold=0.90,
            structure_type="standard"
        )

    Expected output format:
        {
            "status": "success",
            "tracks_created": ["Drums Track", "Bass Track", "Synth Track", "FX Track"],
            "samples_used": {
                "drums": ["kick_1.wav", "snare_1.wav", "hat_1.wav"],
                "bass": ["bass_1.wav", "bass_2.wav"],
                "synths": ["synth_1.wav", "synth_2.wav"],
                "fx": ["riser_1.wav", "impact_1.wav"]
            },
            "coherence_scores": {
                "drums": 0.92,
                "bass": 0.88,
                "synths": 0.90,
                "fx": 0.85
            },
            "total_clips": 24,
            "qa_score": 0.91,
            "message": "Expansive track generated successfully with 48 samples across 4 categories"
        }
    """
    # Validate parameters
    if not 8 <= samples_per_category <= 16:
        return _err(f"Invalid samples_per_category: {samples_per_category}. Must be between 8-16.")

    if not 0.80 <= coherence_threshold <= 0.95:
        return _err(f"Invalid coherence_threshold: {coherence_threshold}. Must be between 0.80-0.95.")

    valid_strategies = ["combined", "sequential", "random", "coherent"]
    if variation_strategy not in valid_strategies:
        return _err(f"Invalid variation_strategy: {variation_strategy}. Must be one of: {', '.join(valid_strategies)}")

    try:
        from engines.integration import get_integration_coordinator

        coordinator = get_integration_coordinator()
        result = coordinator.build_expansive_production(
            description=description,
            samples_per_category=samples_per_category,
            variation_strategy=variation_strategy,
            coherence_threshold=coherence_threshold,
            structure_type=structure_type
        )

        # Format result
        return _ok({
            "status": "success" if result.get("success", False) else "error",
            "tracks_created": result.get("tracks_created", []),
            "samples_used": result.get("samples_used", {}),
            "coherence_scores": result.get("coherence_scores", {}),
            "total_clips": result.get("total_clips", 0),
            "qa_score": result.get("qa_score", 0.0),
            "message": result.get("message", "Expansive track generation completed")
        })
    except ImportError:
        return _err("Integration coordinator not available. Ensure engines.integration module is installed.")
    except AttributeError:
        # Method not yet implemented in integration module
        return _err("build_expansive_production method not yet implemented in integration coordinator.")
    except Exception as e:
        return _err(f"Error generating expansive track: {str(e)}")


# ------------------------------------------------------------------
# ARRANGEMENT INJECTION TOOLS
# ------------------------------------------------------------------

@mcp.tool()
def create_arrangement_audio_pattern(ctx: Context, track_index: int, file_path: str, 
                                      positions: str, name: str = "") -> str:
    '''Create audio clips in Arrangement View directly from file.
    
    Args:
        track_index: Target track index
        file_path: Absolute path to audio file
        positions: JSON list of beat positions (e.g., "[0.0, 16.0, 32.0]")
        name: Optional clip name
    
    Returns:
        JSON with created clip info
    '''
    try:
        import json
        pos_list = json.loads(positions)
        if not isinstance(pos_list, list):
            return _err("positions must be a JSON list of beat positions")
        
        resp = _send_to_ableton(
            "create_arrangement_audio_pattern",
            {"track_index": track_index, "file_path": file_path, 
             "positions": pos_list, "name": name},
            timeout=TIMEOUTS["create_arrangement_audio_pattern"]
        )
        
        if resp.get("status") == "success":
            return _ok({
                "track_index": track_index,
                "file_path": file_path,
                "positions": pos_list,
                "clips_created": len(pos_list),
                "name": name,
                "view": "Arrangement",
            })
        return _err(resp.get("message", "Failed to create arrangement audio pattern"))
    except json.JSONDecodeError:
        return _err("Invalid JSON in positions parameter. Expected format: '[0.0, 16.0, 32.0]'")
    except Exception as e:
        return _err(f"Error creating arrangement audio pattern: {str(e)}")


# ------------------------------------------------------------------
# AUDIO ANALYSIS TOOLS
# ------------------------------------------------------------------

@mcp.tool()
def analyze_audio_file(ctx: Context, file_path: str) -> str:
    '''Analyze audio file and extract features (BPM, key, spectral).
    
    Args:
        file_path: Absolute path to audio file
    
    Returns:
        JSON with AudioFeatures (bpm, key, duration, spectral features, etc.)
    '''
    try:
        if not os.path.isfile(file_path):
            return _err(f"Audio file not found: {file_path}")
        
        from engines.audio_analyzer_dual import AudioAnalyzerDual
        
        analyzer = AudioAnalyzerDual(backend="auto")
        features = analyzer.analyze_sample(file_path)
        
        # Convert AudioFeatures dataclass to dict
        result = {
            "file_path": file_path,
            "bpm": features.bpm,
            "key": features.key,
            "duration": features.duration,
            "spectral_centroid": features.spectral_centroid,
            "spectral_rolloff": features.spectral_rolloff,
            "zero_crossing_rate": features.zero_crossing_rate,
            "rms_energy": features.rms_energy,
            "key_confidence": features.key_confidence,
            "sample_type": features.sample_type,
            "is_harmonic": features.is_harmonic,
            "is_percussive": features.is_percussive,
            "suggested_genres": features.suggested_genres,
        }
        
        return _ok(result)
    except ImportError:
        return _err("Audio analyzer engine not available.")
    except Exception as e:
        return _err(f"Error analyzing audio file: {str(e)}")


# ------------------------------------------------------------------
# DIVERSITY & COHERENCE TOOLS
# ------------------------------------------------------------------

@mcp.tool()
def reset_diversity_memory(ctx: Context) -> str:
    '''Reset cross-generation diversity memory for fresh session.
    
    Returns:
        Confirmation message
    '''
    try:
        from engines.coherence_system import reset_all_memory
        
        reset_all_memory()
        
        return _ok({
            "status": "success",
            "message": "Diversity memory reset successfully. All generation history cleared.",
        })
    except ImportError:
        return _err("Coherence system not available.")
    except Exception as e:
        return _err(f"Error resetting diversity memory: {str(e)}")


@mcp.tool()
def get_sample_fatigue_report(ctx: Context) -> str:
    '''Get sample usage fatigue report.
    
    Returns:
        JSON with most used samples by role
    '''
    try:
        from engines.coherence_system import get_coherence_memory_stats
        
        stats = get_coherence_memory_stats()
        
        return _ok({
            "status": "success",
            "report": stats,
        })
    except ImportError:
        return _err("Coherence system not available.")
    except Exception as e:
        return _err(f"Error getting sample fatigue report: {str(e)}")


# ------------------------------------------------------------------
# PROFESSIONAL MIXING TOOLS
# ------------------------------------------------------------------

@mcp.tool()
def apply_professional_mix(ctx: Context, track_assignments: str) -> str:
    '''Apply complete professional mix with buses and returns.
    
    Args:
        track_assignments: JSON dict mapping track indices to roles
                          (e.g., '{"0": "kick", "1": "snare", "2": "bass"}')
    
    Returns:
        JSON with applied mix configuration
    '''
    try:
        import json
        assignments = json.loads(track_assignments)
        if not isinstance(assignments, dict):
            return _err("track_assignments must be a JSON object mapping track indices to roles")
        
        # Convert string keys to integers (JSON keys are always strings)
        parsed_assignments = {}
        for k, v in assignments.items():
            try:
                parsed_assignments[int(k)] = v
            except ValueError:
                return _err(f"Invalid track index: {k}. Must be an integer.")
        
        from engines.bus_architecture import apply_professional_mix
        from engines.tcp_client import get_ableton_connection
        
        ableton_conn = get_ableton_connection()
        if ableton_conn is None:
            return _err("Unable to connect to Ableton Live")
        
        result = apply_professional_mix(ableton_conn, parsed_assignments)
        
        return _ok({
            "status": "success",
            "message": "Professional mix applied successfully",
            "configuration": result,
            "tracks_processed": len(parsed_assignments),
        })
    except json.JSONDecodeError:
        return _err('Invalid JSON in track_assignments. Expected format: \'{"0": "kick", "1": "snare"}\'')
    except ImportError as e:
        return _err(f"Required engine not available: {str(e)}")
    except Exception as e:
        return _err(f"Error applying professional mix: {str(e)}")


# ------------------------------------------------------------------
# AGENTE 18: PROFESSIONAL WORKFLOW ORCHESTRATOR
# ------------------------------------------------------------------

@mcp.tool()
def produce_professional_track(ctx: Context, config_json: str) -> str:
    """Orquestador maestro de workflow profesional (Agente 18).
    
    Ejecuta un pipeline completo de 5 pasos:
    1. Crear tracks y estructura
    2. Generar contenido por secciÃ³n
    3. Aplicar FX y transiciones
    4. Aplicar mezcla profesional
    5. ValidaciÃ³n QA
    
    Args:
        config_json: JSON string con configuraciÃ³n completa. Ejemplo:
            {
                "genre": "reggaeton",
                "style": "perreo",
                "bpm": 95,
                "key": "Am",
                "duration": 128,
                "structure": [
                    {"type": "intro", "bars": 8, "elements": ["drums", "bass"]},
                    {"type": "verse", "bars": 16, "elements": ["drums", "bass", "chords"]},
                    {"type": "chorus", "bars": 16, "elements": ["drums", "bass", "chords", "melody"]},
                    {"type": "bridge", "bars": 8, "elements": ["drums", "bass"]},
                    {"type": "outro", "bars": 8, "elements": ["drums", "bass"]}
                ],
                "elements": ["drums", "bass", "chords", "melody", "fx"],
                "mixing": {
                    "bus_architecture": True,
                    "parallel_comp": True,
                    "master_chain": True
                }
            }
    
    Returns:
        JSON con resultado completo del workflow incluyendo tracks creados,
        secciones generadas, score de QA y duraciÃ³n.
    """
    try:
        from engines.professional_workflow import ProfessionalWorkflow
        
        workflow = ProfessionalWorkflow()
        result = workflow.produce_professional_track(config_json)
        
        return _ok({
            "success": result.get("success", False),
            "step": result.get("step"),
            "config": result.get("config"),
            "tracks_created": result.get("tracks_created", []),
            "tracks_count": result.get("tracks_count", 0),
            "sections_created": result.get("sections_created", 0),
            "buses_created": result.get("buses_created", 0),
            "fx_applied": result.get("fx_applied", 0),
            "qa_score": result.get("qa_score", 0.0),
            "errors": result.get("errors", []),
            "warnings": result.get("warnings", []),
            "duration_seconds": result.get("duration_seconds", 0.0),
        })
    except ImportError:
        return _err("Professional workflow engine not available.")
    except Exception as e:
        return _err(f"Error in professional workflow: {str(e)}")


# ==================================================================
# AGENTE 14: PROFESSIONAL MELODY ENGINE (MOTIVIC)
# ==================================================================

@mcp.tool()
def generate_motivic_melody(ctx: Context,
                            track_index: int,
                            scale: str = "minor",
                            bars: int = 8,
                            density: str = "medium",
                            variation_types: list = None,
                            phrase_structure: str = None,
                            contour: str = None,
                            root_pitch: int = 60,
                            seed: int = None) -> str:
    """Generate professional motivic melody with variations and phrase structures (Agente 14).

    Creates sophisticated melodies using classical composition techniques:
    - Theme/motive generation with scale-based melodic contours
    - Variations: sequence (repetir a intervalo diferente), inversion (invertir intervalos),
      retrograde (reversa), expansion/contraction
    - Phrase structures: antecedent-consequent (pregunta-respuesta), period, sentence
    - Melodic contour application: arch (subir y bajar), wave (mÃºltiples picos),
      step_wise (notas conjuntas)

    Args:
        track_index: Target track index for the melody
        scale: Scale type - "minor", "major", "harmonic_minor", "melodic_minor",
               "pentatonic_minor", "pentatonic_major", "dorian", "phrygian" (default "minor")
        bars: Number of bars for the melody (default 8)
        density: Note density - "sparse", "medium", "dense" (default "medium")
        variation_types: List of variation types to apply - "sequence", "inversion",
                        "retrograde", "expansion", "contraction" (default None)
        phrase_structure: Phrase structure type - "antecedent_consequent", "period",
                         "sentence" (default None)
        contour: Melodic contour - "arch", "wave", "step_wise", "ascending",
                 "descending", "flat" (default None)
        root_pitch: Root note pitch (MIDI note number) for melody center (default 60 = C4)
        seed: Random seed for reproducible melodies (default None)

    Returns:
        JSON with melody generation results and metadata
    """
    try:
        from engines.melody_engine import generate_motivic_melody as engine_generate

        # Generate melody using the engine
        result = engine_generate(
            scale=scale,
            bars=bars,
            variation_types=variation_types or [],
            phrase_structure=phrase_structure,
            contour=contour,
            seed=seed
        )

        # Apply contour if specified (engine may have done it, but re-apply to be sure)
        if contour and result.get("combined_notes"):
            from engines.melody_engine import MelodyEngine, Note
            engine = MelodyEngine()

            # Convert dict notes back to Note objects for contour application
            notes = [
                Note(
                    pitch=n["pitch"],
                    duration=n["duration"],
                    velocity=n["velocity"],
                    start_time=n["start_time"]
                )
                for n in result["combined_notes"]
            ]

            # Apply contour
            contoured_notes = engine.apply_melodic_contour(notes, contour)
            result["combined_notes"] = engine.notes_to_ableton_format(contoured_notes)
            result["metadata"]["contour"] = contour

        # Create clip and add notes to track
        clip_resp = _send_to_ableton(
            "create_clip",
            {"track_index": track_index, "clip_index": 0, "length": float(bars * 4)},
            timeout=TIMEOUTS["generate_motivic_melody"]
        )

        if clip_resp.get("status") != "success":
            return _err(f"Failed to create clip: {clip_resp.get('message', 'Unknown error')}")

        # Add notes to clip
        notes_resp = _send_to_ableton(
            "add_notes_to_clip",
            {
                "track_index": track_index,
                "clip_index": 0,
                "notes": result["combined_notes"]
            },
            timeout=TIMEOUTS["generate_motivic_melody"]
        )

        if notes_resp.get("status") != "success":
            return _err(f"Failed to add notes: {notes_resp.get('message', 'Unknown error')}")

        return _ok({
            "track_index": track_index,
            "scale": scale,
            "bars": bars,
            "density": density,
            "theme_notes_count": len(result.get("theme", [])),
            "variations_count": len(result.get("variations", [])),
            "total_notes_added": len(result.get("combined_notes", [])),
            "phrase_structure": phrase_structure,
            "contour": contour,
            "metadata": result.get("metadata", {}),
            "variations": [{"type": v["type"], "note_count": len(v["notes"])}
                           for v in result.get("variations", [])],
        })

    except ImportError:
        return _err("Melody engine not available. Ensure melody_engine.py is present.")
    except ValueError as e:
        return _err(f"Invalid parameter: {str(e)}")
    except Exception as e:
        return _err(f"Error generating motivic melody: {str(e)}")


# ==================================================================
# AGENTE 12: VST/AU PLUGIN SUPPORT
# ==================================================================

@mcp.tool()
def scan_vst_plugins(ctx: Context, force_rescan: bool = False) -> str:
    """Scan for installed VST/AU plugins.
    
    Detects popular plugins like Serum, Massive, Sylenth1, FabFilter,
    and ValhallaDSP plugins in the system.
    
    Args:
        force_rescan: Force a fresh scan even if cache exists
    
    Returns:
        JSON with scan results including installed plugin list and paths
    """
    try:
        from engines.vst_manager import get_vst_manager
        
        manager = get_vst_manager()
        result = manager.scan_vst_plugins(force_rescan=force_rescan)
        
        return _ok(result)
    except Exception as e:
        return _err(f"Error scanning VST plugins: {str(e)}")


@mcp.tool()
def load_vst_plugin(ctx: Context, track_index: int, plugin_name: str, preset_name: str = "") -> str:
    """Load a VST/AU plugin on a track.
    
    Supports popular plugins:
    - Synths: Serum, Massive, Sylenth1
    - Effects: FabFilter Pro-Q, Pro-C, Pro-R
    - Reverb/Delay: ValhallaRoom, ValhallaVintageVerb, ValhallaDelay
    
    Args:
        track_index: Index of the target track
        plugin_name: Name of the plugin (e.g., "Serum", "Massive", "Pro-Q")
        preset_name: Optional preset to load after inserting plugin
    
    Returns:
        JSON with plugin load status and information
    """
    try:
        from engines.vst_manager import validate_plugin
        
        # Validate plugin installation first
        is_installed, message = validate_plugin(plugin_name)
        
        if not is_installed:
            return _err(f"Plugin '{plugin_name}' not found: {message}")
        
        # Send command to Ableton to load the plugin
        resp = _send_to_ableton(
            "load_vst_plugin",
            {
                "track_index": track_index,
                "plugin_name": plugin_name,
                "preset_name": preset_name,
            },
            timeout=TIMEOUTS["load_vst_plugin"]
        )
        
        if resp.get("status") != "success":
            return _err(resp.get("message", "Failed to load plugin"))
        
        return _ok({
            "plugin_loaded": True,
            "plugin_name": plugin_name,
            "track_index": track_index,
            "preset_name": preset_name if preset_name else None,
            "validation": message,
        })
    except Exception as e:
        return _err(f"Error loading VST plugin: {str(e)}")


@mcp.tool()
def configure_vst_parameter(ctx: Context, track_index: int, plugin_index: int, 
                             param_name: str, value: float) -> str:
    """Configure a parameter on a VST/AU plugin.
    
    Common parameters by plugin:
    - Serum: osc_a_wave, osc_a_level, filter_cutoff, filter_resonance, attack, decay, sustain, release
    - Massive: osc1_pitch, osc1_wtpos, filter_cutoff, filter_resonance, attack, decay, sustain, release
    - Sylenth1: osc_a1_wave, osc_a1_pitch, cutoff_a, resonance_a, attack, decay, sustain, release
    - Pro-Q: gain, mix, band1_gain, band1_freq, band1_q, band2_gain, band2_freq
    - Pro-C: threshold, ratio, attack, release, makeup
    - ValhallaRoom: mix, decay, size, predelay
    
    Args:
        track_index: Index of the track containing the plugin
        plugin_index: Index of the plugin in the device chain (0-based)
        param_name: Name of the parameter to configure
        value: New value for the parameter (normalized 0.0-1.0 or actual value)
    
    Returns:
        JSON with parameter configuration status
    """
    try:
        resp = _send_to_ableton(
            "configure_vst_parameter",
            {
                "track_index": track_index,
                "plugin_index": plugin_index,
                "param_name": param_name,
                "value": value,
            },
            timeout=TIMEOUTS["configure_vst_parameter"]
        )
        
        if resp.get("status") != "success":
            return _err(resp.get("message", "Failed to configure parameter"))
        
        return _ok({
            "parameter_configured": True,
            "track_index": track_index,
            "plugin_index": plugin_index,
            "param_name": param_name,
            "value": value,
        })
    except Exception as e:
        return _err(f"Error configuring VST parameter: {str(e)}")


@mcp.tool()
def get_vst_presets(ctx: Context, plugin_name: str) -> str:
    """Get available presets for a VST/AU plugin.
    
    Args:
        plugin_name: Name of the plugin (e.g., "Serum", "Pro-Q")
    
    Returns:
        JSON with preset list and plugin information
    """
    try:
        from engines.vst_manager import get_vst_presets as _get_presets
        
        result = _get_presets(plugin_name)
        
        if result.get("status") == "error":
            return _err(result.get("message", "Unknown error"))
        
        return _ok(result)
    except Exception as e:
        return _err(f"Error getting VST presets: {str(e)}")


# ------------------------------------------------------------------


# ==================================================================
# AGENTE 17: SECTION GENERATOR (Section-Based Composition)
# ==================================================================

@mcp.tool()
def generate_section_by_type(
    ctx: Context,
    section_type: str,
    at_bar: int = 0,
    duration_bars: int = 8,
    key: str = "Am",
    bpm: float = 95.0,
    build_method: str = "gradual",
    riser_type: str = "standard",
    drum_fill_intensity: float = 0.8,
    melodic_focus: bool = True,
    drum_reduction: float = 0.7,
    max_energy: bool = True,
    all_elements: bool = True,
    variation_type: str = "standard",
    recap_type: str = "melody_only",
    ending_style: str = "fade"
) -> str:
    """Genera una secciÃ³n musical completa por tipo (Agente 17).
    
    Este tool crea configuraciones completas para diferentes tipos de secciones
    musicales: intro, build, breakdown, chorus, outro, y verse.
    
    Args:
        section_type: Tipo de secciÃ³n - "intro", "build", "breakdown", "chorus", "outro", "verse"
        at_bar: PosiciÃ³n inicial en compases (default 0)
        duration_bars: DuraciÃ³n en compases (default 8)
        key: Tonalidad musical (default "Am")
        bpm: Tempo en BPM (default 95.0)
        build_method: Para intro - "gradual", "sudden", "ambient", "rhythmic"
        riser_type: Para build - "standard", "noise", "synth", "vocal", "minimal"
        drum_fill_intensity: Para build - intensidad 0.0-1.0 (default 0.8)
        melodic_focus: Para breakdown - enfocar en melodÃ­a (default True)
        drum_reduction: Para breakdown - reducciÃ³n 0.0-1.0 (default 0.7)
        max_energy: Para chorus - mÃ¡xima energÃ­a (default True)
        all_elements: Para chorus - incluir todos los elementos (default True)
        variation_type: Para chorus - "standard", "minimal", "double", "bouncy"
        recap_type: Para outro - "full", "melody_only", "drums_only", "chords_only"
        ending_style: Para outro - "fade", "cut", "breakdown", "loop"
    
    Returns:
        JSON con configuraciÃ³n completa de la secciÃ³n generada.
        
    Examples:
        # Generar intro gradual de 8 compases
        generate_section_by_type(section_type="intro", at_bar=0, duration_bars=8, build_method="gradual")
        
        # Generar build con riser synth de 8 compases
        generate_section_by_type(section_type="build", at_bar=8, duration_bars=8, riser_type="synth", drum_fill_intensity=0.9)
        
        # Generar breakdown melÃ³dico de 8 compases
        generate_section_by_type(section_type="breakdown", at_bar=16, duration_bars=8, melodic_focus=True)
        
        # Generar chorus de 16 compases con mÃ¡xima energÃ­a
        generate_section_by_type(section_type="chorus", at_bar=24, duration_bars=16, max_energy=True, variation_type="standard")
        
        # Generar outro con fade de 8 compases
        generate_section_by_type(section_type="outro", at_bar=40, duration_bars=8, recap_type="melody_only", ending_style="fade")
    """
    try:
        # Importar SectionGenerator
        from engines.section_generator import SectionGenerator, get_section_generator
        
        # Obtener o crear generador
        generator = get_section_generator(key=key, bpm=bpm)
        
        # Generar configuraciÃ³n segÃºn tipo
        config = None
        
        if section_type.lower() == "intro":
            config = generator.generate_intro(
                build_method=build_method,
                duration=duration_bars,
                start_with_drums=False,
                include_fx_riser=True
            )
        elif section_type.lower() == "build":
            config = generator.generate_build(
                riser_type=riser_type,
                drum_fill_intensity=drum_fill_intensity,
                duration=duration_bars,
                filter_sweep=True
            )
        elif section_type.lower() == "breakdown":
            config = generator.generate_breakdown(
                melodic_focus=melodic_focus,
                drum_reduction=drum_reduction,
                duration=duration_bars,
                include_buildup=True
            )
        elif section_type.lower() == "chorus":
            config = generator.generate_chorus(
                max_energy=max_energy,
                all_elements=all_elements,
                duration=duration_bars,
                variation_type=variation_type
            )
        elif section_type.lower() == "outro":
            config = generator.generate_outro(
                recap_type=recap_type,
                ending_style=ending_style,
                duration=duration_bars,
                include_melody=True
            )
        elif section_type.lower() == "verse":
            config = generator.generate_verse(
                variation="standard",
                duration=duration_bars,
                include_melody=False
            )
        else:
            return _err(f"Unknown section type: {section_type}. Valid types: intro, build, breakdown, chorus, outro, verse")
        
        # Ajustar posiciones para at_bar
        adjusted_tracks = []
        for track in config.tracks:
            adjusted_track = track.copy()
            adjusted_track["start_bar"] = at_bar + track.get("start_bar", 0)
            adjusted_tracks.append(adjusted_track)
        
        adjusted_fx = []
        for fx in config.fx:
            adjusted_fx_item = fx.copy()
            adjusted_fx_item["start_bar"] = at_bar + fx.get("start_bar", 0)
            adjusted_fx.append(adjusted_fx_item)
        
        adjusted_automations = []
        for auto in config.automations:
            adjusted_auto = auto.copy()
            adjusted_auto["start_bar"] = at_bar + auto.get("start_bar", 0)
            adjusted_auto["end_bar"] = at_bar + auto.get("end_bar", duration_bars)
            adjusted_automations.append(adjusted_auto)
        
        return _ok({
            "section_type": section_type,
            "start_bar": at_bar,
            "duration_bars": duration_bars,
            "key": key,
            "bpm": bpm,
            "energy_level": config.energy_level,
            "tracks": adjusted_tracks,
            "fx": adjusted_fx,
            "automations": adjusted_automations,
            "status": "generated",
            "note": f"Section '{section_type}' generated at bar {at_bar}. Use create_section_at_bar() to place in Arrangement View."
        })
        
    except ImportError:
        return _err("SectionGenerator engine not available. Check that section_generator.py is properly installed.")
    except Exception as e:
        return _err(f"Error generating section: {str(e)}")


@mcp.tool()
def create_full_song_sections(
    ctx: Context,
    structure_type: str = "standard",
    key: str = "Am",
    bpm: float = 95.0,
    start_bar: int = 0
) -> str:
    """Crea una estructura completa de canciÃ³n con secciones (Agente 17).
    
    Genera una secuencia completa de secciones: intro, verse, chorus, etc.
    segÃºn el tipo de estructura solicitado.
    
    Args:
        structure_type: Tipo de estructura - "standard", "extended", "minimal"
        key: Tonalidad musical (default "Am")
        bpm: Tempo en BPM (default 95.0)
        start_bar: Barra inicial (default 0)
    
    Returns:
        JSON con lista de secciones generadas y sus configuraciones.
        
    Examples:
        # Estructura estÃ¡ndar
        create_full_song_sections(structure_type="standard", key="Am", bpm=95)
        
        # Estructura extendida
        create_full_song_sections(structure_type="extended", key="Dm", bpm=100)
    """
    try:
        from engines.section_generator import SectionGenerator, get_section_generator
        
        generator = get_section_generator(key=key, bpm=bpm)
        
        # Generar estructura completa
        sections = generator.create_full_song_structure(
            structure_type=structure_type,
            total_duration=64
        )
        
        # Convertir a diccionarios y ajustar posiciones
        results = []
        current_bar = start_bar
        
        for section in sections:
            result = {
                "section_type": section.section_type,
                "start_bar": current_bar,
                "duration_bars": section.duration_bars,
                "energy_level": section.energy_level,
                "key": section.key,
                "tracks_count": len(section.tracks),
                "fx_count": len(section.fx),
                "automations_count": len(section.automations)
            }
            results.append(result)
            current_bar += section.duration_bars
        
        return _ok({
            "structure_type": structure_type,
            "key": key,
            "bpm": bpm,
            "total_sections": len(results),
            "total_bars": current_bar - start_bar,
            "start_bar": start_bar,
            "sections": results,
            "status": "generated",
            "note": f"Generated {len(results)} sections totaling {current_bar - start_bar} bars. Use these configs with create_section_at_bar()."
        })
        
    except ImportError:
        return _err("SectionGenerator engine not available.")
    except Exception as e:
        return _err(f"Error creating song sections: {str(e)}")



# ------------------------------------------------------------------

# ------------------------------------------------------------------
# AGENTE 16: PAD AND TEXTURE LAYER SYSTEM
# ------------------------------------------------------------------

@mcp.tool()
def generate_texture_layers(ctx: Context,
                            track_index: int,
                            key: str = "Am",
                            bars: int = 16,
                            style: str = "ambient",
                            progression: str = "i_v_vi_iv",
                            density: float = 0.5,
                            apply_automation: bool = True) -> str:
    """Generate pad and texture layers for harmonic enrichment.
    
    Creates multiple layers of pads with different characteristics:
    - Ambient: Long, evolving pads
    - Rhythmic: Syncopated chord patterns
    - Arpeggiated: Rhythmic arpeggio patterns
    - Full: Complete stack with all layers
    
    Args:
        track_index: Target track index for the pads
        key: Musical key (default Am)
        bars: Duration in bars (default 16)
        style: Pad style - "ambient", "rhythmic", "arpeggiated", "full"
        progression: Chord progression name ("i_v_vi_iv", "i_vi_iv_v", etc.)
        density: Note density 0.0-1.0 (for rhythmic/arpeggiated)
        apply_automation: Add filter/volume automation
    
    Returns:
        JSON with layer details and note counts
    """
    try:
        from engines import get_texture_engine
        
        engine = get_texture_engine()
        
        # Get chord progression
        progressions = engine.get_available_progressions()
        if progression not in progressions:
            return _err(f"Unknown progression: {progression}. Available: {list(progressions.keys())}")
        
        chord_prog = progressions[progression]
        duration = bars * 4  # Convert bars to beats
        
        # Generate texture based on style
        if style == "ambient":
            layer = engine.generate_ambient_pad(
                chord_progression=chord_prog,
                duration=duration,
                key=key,
                quality="add9",
                voicing="spread"
            )
            if apply_automation:
                layer = engine.apply_pad_automation(
                    layer,
                    volume_swells={"swells": [(0, 0.2), (bars//2, 0.7), (bars, 0.5)]}
                )
            layers = [layer]
            
        elif style == "rhythmic":
            ambient = engine.generate_ambient_pad(
                chord_progression=chord_prog,
                duration=duration,
                key=key,
                quality="sus2",
                voicing="open"
            )
            rhythmic = engine.generate_rhythmic_pad(
                chord_progression=chord_prog,
                syncopation_pattern="latin",
                duration=duration,
                key=key,
                density=density
            )
            layers = engine.layer_by_frequency_range(None, ambient, rhythmic)
            
        elif style == "arpeggiated":
            low_arp = engine.generate_arpeggiated_pad(
                chord_progression=chord_prog,
                arp_pattern="up",
                duration=duration,
                key=key,
                rate="8th",
                octave_range=1
            )
            high_arp = engine.generate_arpeggiated_pad(
                chord_progression=chord_prog,
                arp_pattern="up_down",
                duration=duration,
                key=key,
                rate="16th",
                octave_range=2
            )
            # Adjust octaves
            for note in low_arp.notes:
                note["pitch"] -= 12
            for note in high_arp.notes:
                note["pitch"] += 12
            layers = [low_arp, high_arp]
            
        elif style == "full":
            config = engine.create_full_texture_stack(
                key=key,
                duration=duration,
                style="full",
                progression_name=progression
            )
            layers = config.layers
            
        else:
            return _err(f"Unknown style: {style}. Use: ambient, rhythmic, arpeggiated, full")
        
        # Create MIDI clip and add notes
        all_notes = []
        for i, layer in enumerate(layers):
            # Offset notes for each layer slightly
            for note in layer.notes:
                note_with_offset = note.copy()
                note_with_offset["start_time"] += i * 0.02  # Micro-offset for phase
                all_notes.append(note_with_offset)
        
        # Send to Ableton
        resp = _send_to_ableton(
            "generate_texture_layers",
            {
                "track_index": track_index,
                "notes": all_notes,
                "duration": duration,
                "style": style,
                "layers": len(layers)
            },
            timeout=30.0
        )
        
        if resp.get("status") == "success":
            result = resp.get("result", {})
            return _ok({
                "track_index": track_index,
                "style": style,
                "key": key,
                "progression": progression,
                "bars": bars,
                "layers_created": len(layers),
                "total_notes": len(all_notes),
                "layer_details": [layer.to_dict() for layer in layers],
                "clip_created": result.get("clip_created", False),
                "notes_added": result.get("notes_added", 0),
            })
        
        return _err(resp.get("message", "Failed to generate texture layers"))
        
    except ImportError as e:
        return _err(f"Texture engine not available: {str(e)}")
    except Exception as e:
        return _err(f"Error generating texture layers: {str(e)}")


# ==================================================================
# AGENTE 15: REGGAETON RHYTHM PATTERNS LIBRARY
# ==================================================================

@mcp.tool()
def get_rhythmic_pattern(ctx: Context,
                       pattern_type: str = "dembow_classic",
                       bars: int = 4,
                       intensity: str = "standard",
                       heaviness: str = "medium",
                       fill_density: str = "medium",
                       hat_speed: str = "32nd",
                       complexity: str = "medium",
                       ghost_density: str = "medium",
                       style: str = "dembow") -> str:
    """Get a rhythmic pattern from the Reggaeton Patterns Library (Agente 15).
    
    Returns detailed rhythmic patterns for reggaeton production including:
    - dembow_classic: Patron dembow clasico (el ritmo caracteristico del reggaeton)
    - moombahton: Mas lento y pesado (100-110 BPM feel)
    - perreo_acelerado: Rapido e intenso (160-180 BPM feel)
    - trapeton: Mezcla de reggaeton con trap, hi-hats en 32avos
    - syncopated_kick: Kicks en off-beats para groove avanzado
    - ghost_snare: Ghost notes en snare para feel humano
    - open_hat: Posiciones optimas para open hi-hats
    
    Each pattern returns a list of events with position (in beats), velocity (0-127),
    and sample_type (kick, snare, hihat_closed, hihat_open, clap, etc.).
    
    Args:
        pattern_type: Type of pattern to generate
            - "dembow_classic": Classic dembow pattern (kicks en 1,3 + snare en 2.25,4)
            - "moombahton": Slower, heavier pattern with house-style snares
            - "perreo_acelerado": Fast, intense pattern with double-time feel
            - "trapeton": Trap-reggaeton fusion with 32nd note hi-hats
            - "syncopated_kick": Off-beat kicks for advanced groove
            - "ghost_snare": Ghost notes on snare for human feel
            - "open_hat": Strategic open hi-hat placements
        bars: Number of bars for the pattern (default 4)
        intensity: For dembow_classic: "minimal", "standard", "intense"
        heaviness: For moombahton: "light", "medium", "heavy"
        fill_density: For perreo_acelerado: "low", "medium", "high"
        hat_speed: For trapeton: "16th", "32nd", "64th_triplet"
        complexity: For syncopated_kick: "simple", "medium", "complex"
        ghost_density: For ghost_snare: "low", "medium", "high"
        style: For open_hat: "dembow", "moombahton", "trap", "minimal"
        
    Returns:
        JSON with pattern_type, bars, event_count, events list, and available patterns.
        Each event has: position (beats), velocity (0-127), sample_type (string).
        
    Examples:
        # Get classic dembow pattern
        get_rhythmic_pattern(pattern_type="dembow_classic", bars=4, intensity="standard")
        
        # Get heavy moombahton pattern
        get_rhythmic_pattern(pattern_type="moombahton", bars=4, heaviness="heavy")
        
        # Get fast perreo with high fill density
        get_rhythmic_pattern(pattern_type="perreo_acelerado", bars=8, fill_density="high")
        
        # Get trapeton with fast hi-hats
        get_rhythmic_pattern(pattern_type="trapeton", bars=4, hat_speed="32nd")
        
        # Get syncopated kicks for layering
        get_rhythmic_pattern(pattern_type="syncopated_kick", bars=4, complexity="complex")
        
        # Get ghost snare pattern
        get_rhythmic_pattern(pattern_type="ghost_snare", bars=4, ghost_density="medium")
        
        # Get open hat placements
        get_rhythmic_pattern(pattern_type="open_hat", bars=4, style="dembow")
    """
    try:
        from engines.reggaeton_patterns import ReggaetonPatterns
        
        # Map pattern type to method and parameters
        pattern_methods = {
            "dembow_classic": ("get_dembow_classic", {"intensity": intensity}),
            "moombahton": ("get_moombahton_pattern", {"heaviness": heaviness}),
            "perreo_acelerado": ("get_perreo_acelerado", {"fill_density": fill_density}),
            "trapeton": ("get_trapeton_pattern", {"hat_speed": hat_speed}),
            "syncopated_kick": ("get_syncopated_kick", {"complexity": complexity}),
            "ghost_snare": ("get_ghost_snare_pattern", {"ghost_density": ghost_density}),
            "open_hat": ("get_open_hat_placement", {"style": style}),
        }
        
        if pattern_type not in pattern_methods:
            return _err(f"Invalid pattern_type: {pattern_type}. "
                       f"Available: {list(pattern_methods.keys())}")
        
        method_name, method_kwargs = pattern_methods[pattern_type]
        method = getattr(ReggaetonPatterns, method_name)
        
        # Get the pattern events
        events = method(bars=bars, **method_kwargs)
        
        # Convert to dict format
        events_dict = [e.to_dict() for e in events]
        
        return _ok({
            "pattern_type": pattern_type,
            "bars": bars,
            "event_count": len(events_dict),
            "events": events_dict,
            "available_patterns": list(pattern_methods.keys()),
            "method_used": method_name,
            "parameters_used": method_kwargs
        })
        
    except ImportError:
        return _err("Reggaeton patterns engine not available. "
                   "Ensure reggaeton_patterns.py is present in engines/")
    except ValueError as e:
        return _err(f"Invalid parameter: {str(e)}")
    except Exception as e:
        return _err(f"Error generating rhythmic pattern: {str(e)}")


# ------------------------------------------------------------------
# SPRINT 5: DJ PROFESSIONAL TRACK GENERATION
# ------------------------------------------------------------------

@mcp.tool()
def generate_dj_professional_track(
    ctx: Context,
    description: str,
    tempo: int = 95,
    key: str = "Am",
    include_dj_extended: bool = True,
    include_radio_edit: bool = True,
    sample_count_target: int = 330
) -> str:
    """Generate a professional DJ track with extended and radio edit versions.

    Creates a complete professional track production including both DJ Extended
    version (for mixing, with extended intro/outro) and Radio Edit version 
    (shorter, direct for broadcast). Uses extensive sample library utilization
    for maximum sonic richness.

    Args:
        description: Natural language description of desired track.
                    Examples:
                    - "reggaeton perreo intenso 95bpm Am"
                    - "romantico suave 90bpm Gm con piano"
                    - "trap oscuro 140bpm Cm, agresivo"
        tempo: Tempo in BPM (default 95)
        key: Musical key e.g. "Am", "Cm", "Gm" (default "Am")
        include_dj_extended: Create DJ Extended version with extended intro/outro
        include_radio_edit: Create Radio Edit version (shorter, direct)
        sample_count_target: Target number of samples to use per version (default 330)

    Returns:
        JSON with complete production details:
        - status: "success" or "error"
        - dj_extended: Dict with tracks, samples used, duration for DJ version
        - radio_edit: Dict with tracks, samples used, duration for Radio version
        - total_samples_used: Total samples across both versions (660 if both)
        - total_tracks: Total number of tracks created (21)
        - qa_scores: Quality assurance scores for each version
        - message: Human-readable status message

    Example:
        generate_dj_professional_track(
            description="reggaeton perreo intenso 95bpm Am",
            tempo=95,
            key="Am",
            include_dj_extended=True,
            include_radio_edit=True,
            sample_count_target=330
        )

    Expected output format:
        {
            "status": "success",
            "dj_extended": {
                "tracks": ["Kick", "Snare", "HiHat", "Bass", ...],
                "samples_used": ["kick_1.wav", "snare_1.wav", ...],
                "duration_bars": 128,
                "version": "DJ Extended"
            },
            "radio_edit": {
                "tracks": ["Kick", "Snare", "HiHat", "Bass", ...],
                "samples_used": ["kick_1.wav", "snare_1.wav", ...],
                "duration_bars": 64,
                "version": "Radio Edit"
            },
            "total_samples_used": 660,
            "total_tracks": 21,
            "qa_scores": {
                "dj_extended": 0.92,
                "radio_edit": 0.90
            },
            "message": "DJ Professional track generated successfully with 660 samples across 2 versions"
        }
    """
    try:
        from engines.integration import get_integration_coordinator

        coordinator = get_integration_coordinator()
        result = coordinator.build_dj_professional_production(
            description=description,
            tempo=tempo,
            key=key,
            include_dj_extended=include_dj_extended,
            include_radio_edit=include_radio_edit,
            sample_count_target=sample_count_target
        )

        # Calculate totals
        total_samples = 0
        if include_dj_extended:
            total_samples += sample_count_target
        if include_radio_edit:
            total_samples += sample_count_target

        # Format response
        return _ok({
            "status": "success" if result.get("success", False) else "error",
            "dj_extended": {
                "tracks": result.get("dj_extended_tracks", []),
                "samples_used": result.get("dj_extended_samples", []),
                "duration_bars": result.get("dj_extended_duration", 128),
                "version": "DJ Extended"
            } if include_dj_extended else None,
            "radio_edit": {
                "tracks": result.get("radio_edit_tracks", []),
                "samples_used": result.get("radio_edit_samples", []),
                "duration_bars": result.get("radio_edit_duration", 64),
                "version": "Radio Edit"
            } if include_radio_edit else None,
            "total_samples_used": total_samples,
            "total_tracks": result.get("total_tracks", 21),
            "qa_scores": {
                "dj_extended": result.get("dj_extended_qa_score", 0.0),
                "radio_edit": result.get("radio_edit_qa_score", 0.0)
            },
            "message": result.get("message", 
                f"DJ Professional track generated with {total_samples} samples across "
                f"{(1 if include_dj_extended else 0) + (1 if include_radio_edit else 0)} versions")
        })
    except ImportError:
        return _err("Integration coordinator not available. Ensure engines.integration module is installed.")
    except AttributeError:
        return _err("build_dj_professional_production method not yet implemented in integration coordinator.")
    except Exception as e:
        return _err(f"Error generating DJ professional track: {str(e)}")


# ==================================================================
# SPRINT 5.5: ADVANCED PRODUCTION TOOLS
# ==================================================================

@mcp.tool()
def inject_sample_batch(ctx: Context, samples: list, target_track: int = None) -> str:
    """Inject up to 50 samples into the project with 10s timeout.

    Injects a batch of samples into the Arrangement View for rapid
    track building. Samples are placed sequentially or on specified tracks.

    Args:
        samples: List of sample dicts with keys: path, position (bars), 
                track_index (optional), warp (optional)
        target_track: Default track index to use if not specified in samples

    Returns:
        JSON with injection status, samples loaded, and any errors.

    Example:
        inject_sample_batch(
            samples=[
                {"path": "kick.wav", "position": 0, "track_index": 0},
                {"path": "snare.wav", "position": 0, "track_index": 1},
            ],
            target_track=0
        )
    """
    try:
        if len(samples) > 50:
            return _err(f"Too many samples: {len(samples)}. Maximum is 50 per batch.")
        
        if not samples:
            return _err("No samples provided. Provide at least one sample.")
        
        loaded = []
        errors = []
        
        for i, sample in enumerate(samples):
            try:
                path = sample.get("path", "")
                position = sample.get("position", 0)
                track_idx = sample.get("track_index", target_track)
                warp = sample.get("warp", True)
                
                if not path or not os.path.isfile(path):
                    errors.append({"index": i, "error": f"File not found: {path}"})
                    continue
                
                if track_idx is None:
                    errors.append({"index": i, "error": "No track index specified"})
                    continue
                
                # Create audio clip in Arrangement
                resp = _send_to_ableton(
                    "create_arrangement_audio_clip",
                    {
                        "track_index": track_idx,
                        "sample_path": path,
                        "start_time": position,
                        "length": 4.0,
                        "warp": warp
                    },
                    timeout=5.0
                )
                
                if resp.get("status") == "success":
                    loaded.append({
                        "index": i,
                        "path": path,
                        "track_index": track_idx,
                        "position": position
                    })
                else:
                    errors.append({
                        "index": i,
                        "error": resp.get("message", "Failed to load sample")
                    })
            except Exception as e:
                errors.append({"index": i, "error": str(e)})
        
        return _ok({
            "samples_loaded": len(loaded),
            "samples_total": len(samples),
            "loaded": loaded,
            "errors": errors,
            "error_count": len(errors)
        })
    except Exception as e:
        return _err(f"Error injecting sample batch: {str(e)}")


@mcp.tool()
def validate_coherence(ctx: Context, sample_paths: list, threshold: float = 0.85) -> str:
    """Validate sample compatibility with 15s timeout.

    Analyzes spectral and rhythmic compatibility between samples
    to ensure they work well together in a mix.

    Args:
        sample_paths: List of sample file paths to validate
        threshold: Minimum coherence score (0.0-1.0) for compatibility

    Returns:
        JSON with coherence scores, pairwise comparisons, and recommendations.

    Example:
        validate_coherence(
            sample_paths=["kick.wav", "snare.wav", "bass.wav"],
            threshold=0.85
        )
    """
    try:
        from engines.real_coherence_validator import RealCoherenceValidator
        
        if len(sample_paths) < 2:
            return _err("Need at least 2 samples to validate coherence.")
        
        if not 0.0 <= threshold <= 1.0:
            return _err(f"Invalid threshold: {threshold}. Must be 0.0-1.0.")
        
        validator = RealCoherenceValidator()
        results = validator.validate_batch(sample_paths)
        
        # Calculate overall coherence
        scores = [r.get("coherence_score", 0) for r in results]
        avg_score = sum(scores) / len(scores) if scores else 0
        
        # Identify incompatible pairs
        incompatible = [
            r for r in results 
            if r.get("coherence_score", 0) < threshold
        ]
        
        return _ok({
            "average_coherence": round(avg_score, 3),
            "threshold": threshold,
            "samples_validated": len(sample_paths),
            "pairwise_results": results,
            "incompatible_pairs": incompatible,
            "is_compatible": len(incompatible) == 0,
            "recommendation": "All samples are compatible" if len(incompatible) == 0 
                            else f"{len(incompatible)} pairs below threshold"
        })
    except ImportError:
        return _err("Coherence validator engine not available.")
    except Exception as e:
        return _err(f"Error validating coherence: {str(e)}")


@mcp.tool()
def build_section_real(ctx: Context, section_type: str, at_bar: int,
                       duration_bars: int = 8, key: str = "Am",
                       bpm: float = 95, include_automation: bool = True) -> str:
    """Build section with automation (15s timeout).

    Creates a complete song section directly in Arrangement View with
    optional filter sweeps, volume automation, and FX.

    Args:
        section_type: Type of section - "intro", "verse", "chorus", "bridge", "outro"
        at_bar: Starting bar position in the arrangement
        duration_bars: Length of the section in bars (default 8)
        key: Musical key (default "Am")
        bpm: Tempo in BPM (default 95)
        include_automation: Add filter sweeps and volume automation

    Returns:
        JSON with section creation status, tracks affected, and automation details.

    Example:
        build_section_real(
            section_type="chorus",
            at_bar=16,
            duration_bars=16,
            key="Am",
            include_automation=True
        )
    """
    try:
        # Validate section type
        valid_types = ["intro", "verse", "chorus", "bridge", "outro", "build", "drop"]
        if section_type.lower() not in valid_types:
            return _err(f"Invalid section_type: {section_type}. Must be one of: {', '.join(valid_types)}")
        
        # Create the section via Ableton
        resp = _send_to_ableton(
            "create_section_at_bar",
            {
                "section_type": section_type.lower(),
                "at_bar": at_bar,
                "duration_bars": duration_bars,
                "key": key,
                "bpm": bpm
            },
            timeout=TIMEOUTS["build_section_real"]
        )
        
        if resp.get("status") != "success":
            return _err(resp.get("message", "Failed to create section"))
        
        result = resp.get("result", {})
        
        # Apply automation if requested
        automation_applied = []
        if include_automation:
            tracks_affected = result.get("tracks_affected", [])
            for track_idx in tracks_affected[:3]:  # Limit to first 3 tracks
                try:
                    auto_resp = _send_to_ableton(
                        "automate_filter",
                        {
                            "track_index": track_idx,
                            "start_bar": at_bar,
                            "end_bar": at_bar + duration_bars,
                            "start_freq": 200 if section_type == "intro" else 800,
                            "end_freq": 20000 if section_type in ["chorus", "drop"] else 8000,
                            "curve_type": "s_curve"
                        },
                        timeout=5.0
                    )
                    if auto_resp.get("status") == "success":
                        automation_applied.append({
                            "track_index": track_idx,
                            "type": "filter_sweep"
                        })
                except Exception:
                    pass  # Continue even if automation fails
        
        return _ok({
            "section_type": section_type,
            "at_bar": at_bar,
            "duration_bars": duration_bars,
            "key": key,
            "bpm": bpm,
            "tracks_affected": result.get("tracks_affected", []),
            "automation_applied": automation_applied,
            "automation_count": len(automation_applied),
            "status": "created"
        })
    except Exception as e:
        return _err(f"Error building section: {str(e)}")


@mcp.tool()
def select_coherent_kit(ctx: Context, genre: str = "reggaeton",
                       sample_count: int = 12, coherence_threshold: float = 0.90,
                       key: str = "", bpm: float = 0) -> str:
    """Selects 12 coherent samples (20s timeout).

    Intelligently selects a kit of samples that work well together
    based on spectral analysis and genre matching.

    Args:
        genre: Genre for sample selection (default "reggaeton")
        sample_count: Number of samples to select (default 12, max 20)
        coherence_threshold: Minimum coherence between samples (0.0-1.0)
        key: Musical key filter (optional)
        bpm: BPM filter (optional)

    Returns:
        JSON with selected samples, coherence scores, and kit configuration.

    Example:
        select_coherent_kit(
            genre="reggaeton",
            sample_count=12,
            coherence_threshold=0.90,
            key="Am"
        )
    """
    try:
        from engines.sample_selector import SampleSelector, get_selector
        from engines.real_coherence_validator import RealCoherenceValidator
        
        if not 1 <= sample_count <= 20:
            return _err(f"Invalid sample_count: {sample_count}. Must be 1-20.")
        
        if not 0.0 <= coherence_threshold <= 1.0:
            return _err(f"Invalid coherence_threshold: {coherence_threshold}. Must be 0.0-1.0.")
        
        selector = get_selector()
        if selector is None:
            return _err("Sample selector not available. Check libreria path.")
        
        # Select samples for genre
        group = selector.select_for_genre(
            genre, 
            key if key else None, 
            bpm if bpm > 0 else None
        )
        
        # Collect all available samples
        all_samples = []
        if group.drums:
            if group.drums.kick:
                all_samples.append(("kick", group.drums.kick))
            if group.drums.snare:
                all_samples.append(("snare", group.drums.snare))
            if group.drums.hat_closed:
                all_samples.append(("hat", group.drums.hat_closed))
            if group.drums.clap:
                all_samples.append(("clap", group.drums.clap))
        
        for bass in group.bass[:3]:
            all_samples.append(("bass", bass))
        for synth in group.synths[:3]:
            all_samples.append(("synth", synth))
        for fx in group.fx[:2]:
            all_samples.append(("fx", fx))
        
        # Limit to requested count
        selected = all_samples[:sample_count]
        
        # Validate coherence
        validator = CoherenceValidator()
        sample_paths = [s[1].path for s in selected if hasattr(s[1], 'path')]
        
        coherence_result = None
        if len(sample_paths) >= 2:
            coherence_result = validator.validate_batch(sample_paths)
        
        # Format result
        kit = {
            "genre": genre,
            "key": key or group.key,
            "bpm": bpm or group.bpm,
            "sample_count": len(selected),
            "samples": [
                {
                    "role": role,
                    "name": getattr(sample, 'name', str(sample)),
                    "path": getattr(sample, 'path', ''),
                    "bpm": getattr(sample, 'bpm', 0),
                    "key": getattr(sample, 'key', '')
                }
                for role, sample in selected
            ],
            "coherence_validation": coherence_result,
            "coherence_threshold": coherence_threshold
        }
        
        return _ok(kit)
    except ImportError:
        return _err("Sample selector or coherence engine not available.")
    except Exception as e:
        return _err(f"Error selecting coherent kit: {str(e)}")


@mcp.tool()
def produce_radio_edit_4min(ctx: Context, description: str,
                            tempo: int = 95, key: str = "Am",
                            target_duration_seconds: int = 240) -> str:
    """Full 4-min radio edit production (600s timeout).

    Generates a complete radio-ready 4-minute track optimized for
    broadcast with proper intro length, verse-chorus structure,
    and clean outro.

    Args:
        description: Natural language description of desired track
        tempo: Tempo in BPM (default 95)
        key: Musical key (default "Am")
        target_duration_seconds: Target duration in seconds (default 240 = 4 min)

    Returns:
        JSON with production summary, tracks created, and render info.

    Example:
        produce_radio_edit_4min(
            description="reggaeton perreo intenso",
            tempo=95,
            key="Am"
        )
    """
    try:
        from engines.integration import get_integration_coordinator
        
        # Calculate bars from seconds
        bars = int((target_duration_seconds / 60) * (tempo / 4))
        
        coordinator = get_integration_coordinator()
        
        # Build the production
        result = coordinator.build_expansive_production(
            description=description,
            samples_per_category=8,
            variation_strategy="coherent",
            coherence_threshold=0.90,
            structure_type="short"  # Optimized for radio
        )
        
        return _ok({
            "status": "success" if result.get("success") else "error",
            "description": description,
            "tempo": tempo,
            "key": key,
            "target_duration_seconds": target_duration_seconds,
            "estimated_bars": bars,
            "tracks_created": result.get("tracks_created", []),
            "samples_used": result.get("samples_used", {}),
            "coherence_scores": result.get("coherence_scores", {}),
            "qa_score": result.get("qa_score", 0.0),
            "message": f"Radio edit production completed: {target_duration_seconds}s track"
        })
    except ImportError:
        return _err("Integration coordinator not available.")
    except Exception as e:
        return _err(f"Error producing radio edit: {str(e)}")


@mcp.tool()
def get_production_progress(ctx: Context) -> str:
    """Gets production status (5s timeout).

    Returns the current state of any in-progress production,
    including tracks created, samples loaded, and estimated completion.

    Returns:
        JSON with production status, progress percentage, and current phase.

    Example:
        get_production_progress()
    """
    try:
        from engines.workflow_engine import WorkflowEngine
        
        engine = WorkflowEngine()
        
        # Get current progress
        progress_data = engine.get_progress_report()
        
        # Also get arrangement status
        arr_resp = _send_to_ableton("get_arrangement_clips", timeout=3.0)
        arrangement_data = {}
        if arr_resp.get("status") == "success":
            r = arr_resp.get("result", {})
            arrangement_data = {
                "total_clips": r.get("total_clips", 0),
                "arrangement_length_beats": r.get("arrangement_length_beats", 0)
            }
        
        return _ok({
            "production_phase": progress_data.get("current_phase", "unknown"),
            "completion_percent": progress_data.get("completion", 0),
            "tasks_done": progress_data.get("tasks_done", 0),
            "tasks_total": progress_data.get("tasks_total", 0),
            "time_invested": progress_data.get("time_invested", "0h 0m"),
            "milestones": progress_data.get("milestones", []),
            "arrangement": arrangement_data,
            "status": "in_progress" if progress_data.get("completion", 0) < 100 else "complete"
        })
    except ImportError:
        return _err("Workflow engine not available.")
    except Exception as e:
        return _err(f"Error getting production progress: {str(e)}")


@mcp.tool()
def produce_with_spectral_coherence(ctx: Context, 
                                     bpm: int = 100,
                                     key: str = "Am",
                                     style: str = "standard",
                                     coherence_threshold: float = 0.90,
                                     max_samples_per_role: int = 12,
                                     auto_record: bool = True) -> str:
    """
    Genera una cancion profesional con seleccion espectral coherente.
    
    Usa los 511 samples analizados para crear una produccion donde TODOS
    los samples son espectralmente coherentes (mismo timbre, energia compatible).
    
    Args:
        bpm: Tempo del proyecto (default 100)
        key: Tonalidad (default Am)
        style: Estilo de produccion (standard, minimal, trap, perreo)
        coherence_threshold: Minimo score de coherencia (0.0-1.0, default 0.90 profesional)
        max_samples_per_role: Cuantos samples usar por rol (default 12)
        auto_record: Grabar a Arrangement View automaticamente
    
    Returns:
        JSON con detalles de la produccion, coherencia por rol, y samples usados.
    """
    import sqlite3 as _sqlite3
    DB_PATH = os.path.join(REGGAETON_LIB, "sample_metadata.db")
    LIBRARY_PATH = REGGAETON_LIB

    def _cosine_sim(v1, v2):
        try:
            dot = sum(a * b for a, b in zip(v1, v2))
            n1 = sum(a * a for a in v1) ** 0.5
            n2 = sum(b * b for b in v2) ** 0.5
            return dot / (n1 * n2) if n1 * n2 > 0 else 0.0
        except Exception:
            return 0.0

    def _calc_coherence(s1, s2):
        mfcc_sim = _cosine_sim(s1['mfccs'], s2['mfccs'])
        centroid_diff = abs(s1['spectral_centroid'] - s2['spectral_centroid']) / max(s1['spectral_centroid'], 1)
        centroid_sim = max(0, 1 - centroid_diff)
        rms_diff = abs(s1['rms'] - s2['rms']) / 60
        rms_sim = max(0, 1 - rms_diff)
        zcr_sim = 1 - min(1, abs(s1['zcr'] - s2['zcr']) * 10)
        return mfcc_sim * 0.40 + centroid_sim * 0.30 + rms_sim * 0.20 + zcr_sim * 0.10

    def _extract_track_index(resp):
        r = _ableton_result(resp)
        if isinstance(r, dict):
            r2 = _ableton_result(r)
            if isinstance(r2, dict) and "index" in r2:
                return r2["index"]
            if isinstance(r, dict) and "index" in r:
                return r["index"]
        return None

    ROLE_CATEGORIES = {
        "kick": ["kick", "8. KICKS"],
        "snare": ["snare", "9. SNARE"],
        "hihat": ["hi-hat", "hi_hat", "hihats"],
        "perc": ["perc loop", "10. PERCS"],
        "bass": ["bass"],
        "drumloop": ["drumloops", "drumloop", "4. DRUM LOOPS", "LATINOS - DRUM LOOPS", "23 Drum Loops"],
        "oneshot": ["oneshots", "oneshot", "3. ONE SHOTS", "LATINOS  -  ONE SHOTS", "20 One Shots"],
        "fx": ["fx", "5. FX"],
    }

    try:
        logger.info("[SPECTRAL] Step 1: Opening DB...")
        conn = _sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        logger.info("[SPECTRAL] Step 2: Counting samples...")
        cursor.execute("SELECT COUNT(*) FROM samples")
        total_samples = cursor.fetchone()[0]
        if total_samples == 0:
            conn.close()
            return _err("Database vacia. Ejecutar analisis de libreria primero.")
        logger.info(f"[SPECTRAL] Step 3: {total_samples} samples in DB")

        def _get_samples_for_role(role):
            categories = ROLE_CATEGORIES.get(role, [role])
            samples = []
            for cat in categories:
                cursor.execute(
                    "SELECT s.path, s.bpm, s.key, s.duration, s.rms, "
                    "s.spectral_centroid, s.spectral_rolloff, s.zero_crossing_rate, "
                    "s.mfcc_1,s.mfcc_2,s.mfcc_3,s.mfcc_4,s.mfcc_5,"
                    "s.mfcc_6,s.mfcc_7,s.mfcc_8,s.mfcc_9,s.mfcc_10,"
                    "s.mfcc_11,s.mfcc_12,s.mfcc_13, sc.category "
                    "FROM samples s JOIN sample_categories sc ON s.path=sc.path "
                    "WHERE sc.category LIKE ? AND s.duration > 0 "
                    "ORDER BY s.duration DESC",
                    (f"%{cat}%",),
                )
                for row in cursor.fetchall():
                    mfccs = [x for x in list(row[8:21]) if x is not None]
                    if len(mfccs) < 5:
                        mfccs = [0.0] * 13
                    samples.append({
                        'path': row[0],
                        'bpm': row[1] or bpm,
                        'key': row[2] or key,
                        'duration': row[3],
                        'rms': row[4] if row[4] is not None else -20,
                        'spectral_centroid': row[5] if row[5] is not None else 2000,
                        'spectral_rolloff': row[6] if row[6] is not None else 4000,
                        'zcr': row[7] if row[7] is not None else 0.1,
                        'mfccs': mfccs,
                    })
            seen = set()
            unique = []
            for s in samples:
                if s['path'] not in seen:
                    seen.add(s['path'])
                    unique.append(s)
            return unique

        selected_kits = {}
        coherence_by_role = {}
        for role in ["kick", "snare", "hihat", "perc", "bass", "drumloop", "oneshot", "fx"]:
            all_role = _get_samples_for_role(role)
            if len(all_role) < 2:
                selected_kits[role] = all_role[:max_samples_per_role]
                coherence_by_role[role] = 0.85 if all_role else 0.0
                continue
            selected = [all_role[0]]
            for candidate in all_role[1:]:
                if len(selected) >= max_samples_per_role:
                    break
                scores = [_calc_coherence(candidate, s) for s in selected]
                avg = sum(scores) / len(scores) if scores else 0
                if avg >= coherence_threshold:
                    selected.append(candidate)
            if len(selected) >= 2:
                pairwise = []
                for i in range(len(selected)):
                    for j in range(i + 1, len(selected)):
                        pairwise.append(_calc_coherence(selected[i], selected[j]))
                coherence_by_role[role] = round(sum(pairwise) / len(pairwise), 3) if pairwise else 0
            else:
                coherence_by_role[role] = 0.85
            selected_kits[role] = selected
            logger.info(f"[SPECTRAL] {role}: {len(selected)} samples, coherence={coherence_by_role[role]}")

        overall_coherence = round(
            sum(coherence_by_role.values()) / len(coherence_by_role), 3
        ) if coherence_by_role else 0
        conn.close()
        logger.info("[SPECTRAL] Step 4: Setting tempo...")
        try:
            tempo_result = _send_to_ableton("set_tempo", {"tempo": bpm}, timeout=10.0)
            logger.info(f"[SPECTRAL] set_tempo result: {tempo_result}")
        except Exception as e:
            logger.error(f"[SPECTRAL] set_tempo failed: {e}")
            return _err(f"set_tempo failed: {e}")
        logger.info("[SPECTRAL] Step 5: Creating tracks...")
        tracks_created = []
        samples_loaded = []
        for role, samples in selected_kits.items():
            if not samples:
                continue
            try:
                logger.info(f"[SPECTRAL] Role {role}: Creating audio track...")
                tr = _send_to_ableton("create_audio_track", {"index": -1}, timeout=30.0)
                logger.info(f"[SPECTRAL] create_audio_track response: {tr}")
                if tr.get("status") != "success":
                    logger.warning(f"[SPECTRAL] Fallo crear track para {role}")
                    continue
                ti = _extract_track_index(tr)
                logger.info(f"[SPECTRAL] Extracted track_index: {ti}")
                if ti is None:
                    logger.warning(f"[SPECTRAL] Sin track_index para {role}, resp={tr}")
                    continue
                logger.info(f"[SPECTRAL] Setting track name for {role}...")
                name_result = _send_to_ableton("set_track_name", {"track_index": ti, "name": f"{role.title()} Spectral"}, timeout=10.0)
                logger.info(f"[SPECTRAL] set_track_name result: {name_result}")
                logger.info(f"[SPECTRAL] Loading samples for {role}...")
                for slot_idx, sample in enumerate(samples[:8]):
                    sp = os.path.join(LIBRARY_PATH, sample['path'])
                    logger.info(f"[SPECTRAL] Sample {slot_idx}: {sp}")
                    if os.path.exists(sp):
                        logger.info(f"[SPECTRAL] Loading sample to clip: track={ti}, slot={slot_idx}")
                        lr = _send_to_ableton("load_sample_to_clip", {"track_index": ti, "clip_index": slot_idx, "sample_path": sp}, timeout=15.0)
                        logger.info(f"[SPECTRAL] load_sample_to_clip result: {lr}")
                        if lr.get("status") == "success":
                            samples_loaded.append({"role": role, "track": ti, "slot": slot_idx, "path": sample['path'], "bpm": sample['bpm'], "duration": sample['duration']})
                    else:
                        logger.warning(f"[SPECTRAL] Sample not found: {sp}")
                cnt = len([s for s in samples_loaded if s['role'] == role])
                tracks_created.append({"role": role, "track_index": ti, "samples_count": cnt})
                logger.info(f"[SPECTRAL] Track {role}: index={ti}, {cnt} samples")
            except Exception as role_err:
                import traceback as _tb
                logger.error(f"[SPECTRAL] Error rol {role}: {role_err}\n{_tb.format_exc()}")
                return _err(f"Error en rol {role}: {role_err}\n{_tb.format_exc()[-800:]}")

        for t in tracks_created:
            if t.get('samples_count', 0) > 0:
                _send_to_ableton("fire_clip", {"track_index": t['track_index'], "clip_index": 0}, timeout=10.0)
        _send_to_ableton("start_playback", {}, timeout=10.0)

        return _ok({
            "message": "Produccion con coherencia espectral creada",
            "total_samples_analyzed": total_samples,
            "samples_used": len(samples_loaded),
            "tracks_created": len(tracks_created),
            "coherence_threshold": coherence_threshold,
            "coherence_scores_by_role": coherence_by_role,
            "overall_coherence": overall_coherence,
            "is_professional": overall_coherence >= 0.90,
            "tracks": tracks_created,
            "samples_preview": samples_loaded[:20],
            "project_bpm": bpm,
            "project_key": key,
            "style": style,
        })

    except Exception as e:
        import traceback
        tb = traceback.format_exc()
        logger.error(f"[SPECTRAL] OUTER Error: {tb}")
        return _err(f"SPECTRAL OUTER: type={type(e).__name__} msg={str(e)!r}\n{tb[:1500]}")


# ==================================================================
# SPRINT 9 — SCORE → RENDER PIPELINE
# Compose a SongScore JSON incrementally, then inject it into Ableton
# in one atomic render_score() call.
# ==================================================================

# Lazy imports so server still starts if score_engine is missing
def _import_score_engine():
    try:
        import score_engine as _se
        return _se
    except ImportError:
        return None

def _import_score_renderer():
    try:
        import score_renderer as _sr
        return _sr
    except ImportError:
        return None

_REGGAETON_LIB = str(PROJECT_DIR.parent / "libreria" / "reggaeton")


@mcp.tool()
def new_score(
    ctx: Context,
    title: str = "Untitled",
    tempo: float = 95.0,
    key: str = "Am",
    genre: str = "reggaeton",
    time_signature: str = "4/4",
    gap_bars: float = 2.0,
) -> str:
    """Create a fresh SongScore in memory and make it the active score.

    This clears any previous in-memory score.

    Args:
        title:          Song title
        tempo:          BPM (80-160)
        key:            Musical key (Am, C, Dm, F, G, etc.)
        genre:          Genre tag (for documentation)
        time_signature: e.g. "4/4"
        gap_bars:       Bars of silence automatically inserted between sections

    Returns:
        Score summary including title, tempo, key.
    """
    se = _import_score_engine()
    if not se:
        return _err("score_engine module not found in mcp_server/")
    score = se.SongScore(title=title, tempo=tempo, key=key, genre=genre,
                         time_signature=time_signature, gap_bars=gap_bars)
    se.set_current_score(score)
    return _ok({"created": True, "meta": score.meta,
                "instructions": "Score created. Use compose_structure() next."})


@mcp.tool()
def get_score(ctx: Context) -> str:
    """Return the complete active SongScore as JSON.

    Use this to inspect the score before rendering, or to extract the JSON
    for external storage / batch generation.
    """
    se = _import_score_engine()
    if not se:
        return _err("score_engine not available")
    score = se.get_current_score()
    if score is None:
        return _err("No active score. Call new_score() or load_score() first.")
    return _ok({"score": score.to_dict(),
                "warnings": score.validate(),
                "total_bars": score.total_bars()})


@mcp.tool()
def compose_structure(ctx: Context, sections: list) -> str:
    """Define the temporal structure of the active score.

    Calculates start_bar automatically using the score's gap_bars setting.

    Args:
        sections: List of section dicts, each containing:
            - name (str): Section name, e.g. "Intro", "Chorus"
            - duration_bars (int): Length of the section in bars
            - start_bar (float, optional): Override auto-calculated position

    Example sections:
        [
          {"name": "Intro",  "duration_bars": 4},
          {"name": "Verse",  "duration_bars": 8},
          {"name": "Chorus", "duration_bars": 8},
          {"name": "Bridge", "duration_bars": 4},
          {"name": "Outro",  "duration_bars": 4}
        ]
    """
    se = _import_score_engine()
    if not se:
        return _err("score_engine not available")
    try:
        score = se.require_score()
        score.set_structure(sections)
        struct = score.get_structure_dict()
        return _ok({"structure_set": True, "sections": len(struct),
                    "structure": struct, "total_bars": score.total_bars()})
    except Exception as exc:
        return _err(str(exc))


@mcp.tool()
def compose_audio_track(
    ctx: Context,
    track_id: str,
    name: str,
    clips: list,
    mixer: dict = None,
) -> str:
    """Add an audio track to the active score.

    Args:
        track_id: Unique identifier (e.g. "kick", "drum_loop")
        name:     Display name in Ableton (e.g. "Kick")
        clips:    List of clip dicts. Each clip must have:
                    - section (str): Which section this clip belongs to
                    - sample  (str): Sample reference, e.g. "kick/auto" or exact path
                    - loop    (bool, optional, default True)
                    - warp    (bool, optional, default True)
        mixer:    Optional dict with volume (0-1), pan, eq_preset,
                  compression_preset, send_reverb, send_delay

    Sample reference format:
        "kick/auto"         → auto-selects best kick sample
        "drumloops/auto"    → auto-selects best drum loop
        "kick/kick_01.wav"  → exact file within libreria/reggaeton/kick/
    """
    se = _import_score_engine()
    if not se:
        return _err("score_engine not available")
    try:
        score = se.require_score()
        struct = score.get_structure_dict()
        track  = se.TrackDef(
            track_id   = track_id,
            name       = name,
            track_type = "audio",
            clips      = [se.ClipDef.from_raw(c, struct) for c in (clips or [])],
            mixer      = se.MixerDef.from_dict(mixer or {}),
        )
        score.add_track(track)
        return _ok({"track_added": True, "id": track_id, "clips": len(track.clips)})
    except Exception as exc:
        return _err(str(exc))


@mcp.tool()
def compose_midi_track(
    ctx: Context,
    track_id: str,
    name: str,
    instrument: str,
    clips: list,
    mixer: dict = None,
) -> str:
    """Add a MIDI track to the active score.

    Args:
        track_id:   Unique identifier (e.g. "dembow", "bass")
        name:       Display name (e.g. "Dembow", "Sub Bass")
        instrument: Live instrument to load: "Wavetable" or "Operator"
        clips:      List of clip dicts, each with:
                      - section (str): Section name
                      - pattern (str): MIDI pattern name (see below)
                      OR
                      - notes   (list): Explicit MIDI notes
        mixer:      Optional mixer settings dict

    Available patterns:
        dembow_minimal, dembow_standard, dembow_double
        bass_sub, bass_pluck, bass_octaves, bass_sustained
        chords_verse, chords_chorus
        melody_simple
    """
    se = _import_score_engine()
    if not se:
        return _err("score_engine not available")
    try:
        score = se.require_score()
        struct = score.get_structure_dict()
        track  = se.TrackDef(
            track_id   = track_id,
            name       = name,
            track_type = "midi",
            instrument = instrument,
            clips      = [se.ClipDef.from_raw(c, struct) for c in (clips or [])],
            mixer      = se.MixerDef.from_dict(mixer or {}),
        )
        score.add_track(track)
        return _ok({"track_added": True, "id": track_id, "instrument": instrument,
                    "clips": len(track.clips)})
    except Exception as exc:
        return _err(str(exc))


@mcp.tool()
def compose_pattern(
    ctx: Context,
    track_id: str,
    section: str,
    pattern: str,
) -> str:
    """Add a MIDI pattern clip to an existing track in the active score.

    Args:
        track_id: ID of an existing MIDI track (must already be in score)
        section:  Section name where the clip will be placed
        pattern:  Pattern name (dembow_standard, bass_pluck, chords_verse, etc.)
    """
    se = _import_score_engine()
    if not se:
        return _err("score_engine not available")
    try:
        score = se.require_score()
        score.add_clip_to_track(track_id, {"section": section, "pattern": pattern})
        return _ok({"clip_added": True, "track": track_id, "section": section, "pattern": pattern})
    except Exception as exc:
        return _err(str(exc))


@mcp.tool()
def compose_notes(
    ctx: Context,
    track_id: str,
    section: str,
    notes: list,
) -> str:
    """Add explicit MIDI notes to an existing track for a specific section.

    Args:
        track_id: ID of an existing MIDI track
        section:  Section name
        notes:    List of note dicts: [{pitch, start_time, duration, velocity}, ...]
                  - pitch: MIDI note number (0-127)
                  - start_time: position in beats (relative to clip start)
                  - duration: note length in beats
                  - velocity: 0-127
    """
    se = _import_score_engine()
    if not se:
        return _err("score_engine not available")
    try:
        score = se.require_score()
        score.add_clip_to_track(track_id, {"section": section, "notes": notes})
        return _ok({"notes_added": True, "track": track_id, "section": section,
                    "note_count": len(notes)})
    except Exception as exc:
        return _err(str(exc))


@mcp.tool()
def compose_mixer(
    ctx: Context,
    track_id: str,
    volume: float = None,
    pan: float = None,
    eq_preset: str = None,
    compression_preset: str = None,
    send_reverb: float = None,
    send_delay: float = None,
) -> str:
    """Update mixer settings for a track in the active score.

    Args:
        track_id:            Track ID
        volume:              0.0 - 1.0
        pan:                 -1.0 (left) to 1.0 (right)
        eq_preset:           kick, snare, bass, synth, master, kick_sub, etc.
        compression_preset:  kick_punch, bass_glue, buss_glue, parallel_drum, etc.
        send_reverb:         0.0 - 1.0  (Reverb return send level)
        send_delay:          0.0 - 1.0  (Delay return send level)
    """
    se = _import_score_engine()
    if not se:
        return _err("score_engine not available")
    try:
        score = se.require_score()
        kwargs = {k: v for k, v in {
            "volume": volume, "pan": pan, "eq_preset": eq_preset,
            "compression_preset": compression_preset,
            "send_reverb": send_reverb, "send_delay": send_delay,
        }.items() if v is not None}
        score.set_mixer(track_id, **kwargs)
        return _ok({"mixer_updated": True, "track": track_id, "settings": kwargs})
    except Exception as exc:
        return _err(str(exc))


@mcp.tool()
def compose_from_template(
    ctx: Context,
    template_name: str,
    title: str = None,
    tempo: float = None,
    key: str = None,
    gap_bars: float = None,
) -> str:
    """Create a complete SongScore from a predefined template and make it active.

    Available templates:
        reggaeton_basic     — Intro/Verse/Chorus/Bridge/Outro with full track set
        reggaeton_13scenes  — 13-section professional reggaeton structure
        minimal_loop        — Single 8-bar loop with drums + bass

    Args:
        template_name: Template identifier (see above)
        title:         Override title (optional)
        tempo:         Override BPM (optional)
        key:           Override key (optional, e.g. "Dm", "F")
        gap_bars:      Override gap between sections (optional)
    """
    se = _import_score_engine()
    if not se:
        return _err("score_engine not available")
    try:
        overrides = {}
        if title      is not None: overrides["title"]    = title
        if tempo      is not None: overrides["tempo"]    = tempo
        if key        is not None: overrides["key"]      = key
        if gap_bars   is not None: overrides["gap_bars"] = gap_bars

        score = se.SongScore.from_template(template_name, **overrides)
        se.set_current_score(score)
        return _ok({
            "template":   template_name,
            "created":    True,
            "meta":       score.meta,
            "sections":   len(score.structure),
            "tracks":     len(score.tracks),
            "total_bars": score.total_bars(),
            "structure":  score.get_structure_dict(),
            "warnings":   score.validate(),
        })
    except Exception as exc:
        return _err(str(exc))


@mcp.tool()
def compose_validate(ctx: Context) -> str:
    """Validate the active SongScore without touching Ableton.

    Checks structure completeness, track/clip consistency, sample references.

    Returns:
        List of warnings (empty = all good).
    """
    se = _import_score_engine()
    if not se:
        return _err("score_engine not available")
    try:
        score    = se.require_score()
        warnings = score.validate()
        return _ok({
            "valid":       len(warnings) == 0,
            "warnings":    warnings,
            "sections":    len(score.structure),
            "tracks":      len(score.tracks),
            "total_bars":  score.total_bars(),
        })
    except Exception as exc:
        return _err(str(exc))


@mcp.tool()
def save_score(ctx: Context, filename: str = None) -> str:
    """Save the active SongScore to disk as a JSON file.

    Args:
        filename: File name (without path). If omitted, auto-generated from title + timestamp.
                  Extension .json is added automatically if missing.
                  File is saved to: AbletonMCP_AI/mcp_server/scores/

    Returns:
        Absolute path of the saved file.
    """
    se = _import_score_engine()
    if not se:
        return _err("score_engine not available")
    try:
        score = se.require_score()
        if not filename:
            ts       = __import__("datetime").datetime.now().strftime("%Y%m%d_%H%M%S")
            safe     = "".join(c if c.isalnum() or c in "_- " else "_"
                               for c in score.meta.get("title", "untitled"))
            safe     = safe.replace(" ", "_").strip("_")[:40]
            filename = "%s_%s.json" % (safe, ts)
        if not filename.endswith(".json"):
            filename += ".json"
        path = se.SCORES_DIR / filename
        score.save(path)
        return _ok({"saved": True, "filename": filename, "path": str(path),
                    "size_bytes": path.stat().st_size})
    except Exception as exc:
        return _err(str(exc))


@mcp.tool()
def load_score(ctx: Context, filename: str) -> str:
    """Load a SongScore from disk and make it the active score.

    Args:
        filename: File name in scores/ directory (e.g. "perreo_eterno.json").
                  Use list_scores() to see available files.
    """
    se = _import_score_engine()
    if not se:
        return _err("score_engine not available")
    try:
        if not filename.endswith(".json"):
            filename += ".json"
        path = se.SCORES_DIR / filename
        if not path.exists():
            return _err("File not found: %s. Use list_scores() to see available scores." % filename)
        score = se.SongScore.load(path)
        se.set_current_score(score)
        return _ok({
            "loaded":     True,
            "filename":   filename,
            "meta":       score.meta,
            "sections":   len(score.structure),
            "tracks":     len(score.tracks),
            "total_bars": score.total_bars(),
            "warnings":   score.validate(),
        })
    except Exception as exc:
        return _err(str(exc))


@mcp.tool()
def list_scores(ctx: Context) -> str:
    """List all SongScore JSON files saved in mcp_server/scores/.

    Returns file names, sizes, and any readable metadata.
    """
    se = _import_score_engine()
    if not se:
        return _err("score_engine not available")
    try:
        files   = sorted(se.SCORES_DIR.glob("*.json"))
        entries = []
        for f in files:
            entry = {"filename": f.name, "size_bytes": f.stat().st_size}
            try:
                data = __import__("json").loads(f.read_text(encoding="utf-8"))
                m    = data.get("meta", {})
                entry.update({
                    "title": m.get("title", "?"),
                    "tempo": m.get("tempo"),
                    "key":   m.get("key"),
                    "tracks": len(data.get("tracks", [])),
                })
            except Exception:
                pass
            entries.append(entry)
        return _ok({"count": len(entries), "scores": entries,
                    "directory": str(se.SCORES_DIR)})
    except Exception as exc:
        return _err(str(exc))


@mcp.tool()
def render_score(
    ctx: Context,
    clear_first: bool = True,
    score_json: str = None,
) -> str:
    """Render the active SongScore into Ableton Live.

    Translates the score into Ableton operations: creates tracks, places clips
    in Arrangement View at the correct positions, applies mixer settings.

    Args:
        clear_first: Remove all existing tracks/clips before rendering (default True).
        score_json:  Optional. Pass a raw JSON string to render directly without
                     making it the active score. If omitted, uses the active score.

    Returns:
        Summary: tracks created, clips placed, errors.
    """
    se = _import_score_engine()
    sr = _import_score_renderer()
    if not se or not sr:
        return _err("score_engine or score_renderer not available")
    try:
        if score_json:
            score = se.SongScore.from_json(score_json)
        else:
            score = se.require_score()

        renderer = sr.ScoreRenderer(_REGGAETON_LIB)
        result   = renderer.render(score, clear_first=clear_first)
        return _ok(result)
    except Exception as exc:
        return _err(str(exc))


@mcp.tool()
def render_score_from_file(
    ctx: Context,
    filename: str,
    clear_first: bool = True,
) -> str:
    """Load a SongScore JSON from disk and render it into Ableton Live.

    Ideal for batch workflows: save 50 scores with ai_loop.py, then render
    them one by one.

    Args:
        filename:    File name in scores/ (e.g. "song_001.json"). Use list_scores().
        clear_first: Remove existing tracks before rendering (default True).
    """
    se = _import_score_engine()
    sr = _import_score_renderer()
    if not se or not sr:
        return _err("score_engine or score_renderer not available")
    try:
        if not filename.endswith(".json"):
            filename += ".json"
        path = se.SCORES_DIR / filename
        if not path.exists():
            return _err("File not found: %s" % filename)

        score    = se.SongScore.load(path)
        se.set_current_score(score)   # Also make it active

        renderer = sr.ScoreRenderer(_REGGAETON_LIB)
        result   = renderer.render(score, clear_first=clear_first)
        result["filename"] = filename
        return _ok(result)
    except Exception as exc:
        return _err(str(exc))


@mcp.tool()
def render_all_scores(
    ctx: Context,
    clear_between: bool = True,
    delay_seconds: float = 3.0,
    limit: int = 0,
) -> str:
    """Render all SongScore JSON files from scores/ sequentially into Ableton.

    Designed for batch autonomous production. Run this after ai_loop.py has
    generated a batch of scores.

    Args:
        clear_between:  Clear Ableton project between each score (default True).
        delay_seconds:  Wait between renders (give Ableton time to process).
        limit:          Maximum number of scores to render (0 = all).

    Returns:
        Summary of all render results.
    """
    import time as _time

    se = _import_score_engine()
    sr = _import_score_renderer()
    if not se or not sr:
        return _err("score_engine or score_renderer not available")
    try:
        files    = sorted(se.SCORES_DIR.glob("*.json"))
        if limit > 0:
            files = files[:limit]
        if not files:
            return _ok({"rendered": 0, "message": "No score files found in scores/"})

        renderer = sr.ScoreRenderer(_REGGAETON_LIB)
        results  = []
        errors   = 0

        for i, f in enumerate(files):
            logger.info("[render_all] %d/%d: %s", i + 1, len(files), f.name)
            try:
                score  = se.SongScore.load(f)
                result = renderer.render(score, clear_first=clear_between)
                result["filename"] = f.name
                results.append(result)
                if not result.get("success"):
                    errors += 1
            except Exception as exc:
                results.append({"filename": f.name, "success": False, "error": str(exc)})
                errors += 1

            if delay_seconds > 0 and i < len(files) - 1:
                _time.sleep(delay_seconds)

        return _ok({
            "total":      len(files),
            "success":    len(files) - errors,
            "errors":     errors,
            "results":    results,
        })
    except Exception as exc:
        return _err(str(exc))


# Also register timeouts for the new tools
TIMEOUTS.update({
    "new_score":              5.0,
    "get_score":              5.0,
    "compose_structure":      5.0,
    "compose_audio_track":    5.0,
    "compose_midi_track":     5.0,
    "compose_pattern":        5.0,
    "compose_notes":          5.0,
    "compose_mixer":          5.0,
    "compose_from_template":  5.0,
    "compose_validate":       5.0,
    "save_score":             5.0,
    "load_score":             5.0,
    "list_scores":            5.0,
    "render_score":         300.0,
    "render_score_from_file":300.0,
    "render_all_scores":   1800.0,
})


# ------------------------------------------------------------------
# MAIN
# ------------------------------------------------------------------
if __name__ == "__main__":
    mcp.run()
