"""
DCC Executor
Handles execution of DCC operations (Maya, Blender, Houdini) on the local machine.
"""

import asyncio
import json
import logging
import os
import subprocess
import tempfile
import time
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Any, Callable
import shutil

from pydantic import BaseModel

from .dcc_discovery import get_dcc_discovery
from .cross_platform_utils import (
    normalize_path,
    to_platform_path,
    run_cross_platform_command,
    get_process_manager,
    CrossPlatformSystemInfo
)
from .maya_server_manager_filebased import MayaServerManagerFileBased as MayaServerManager
from .blender_server_manager_filebased import BlenderServerManagerFileBased as BlenderServerManager
from .houdini_server_manager_filebased import HoudiniServerManagerFileBased as HoudiniServerManager
from .unreal_server_manager_filebased import UnrealServerManagerFileBased as UnrealServerManager
from .gui_server_manager import GUIServerManager

logger = logging.getLogger(__name__)

class OperationResult(BaseModel):
    """Operation result model."""
    operation_id: str
    success: bool
    output_files: List[str] = []
    logs: List[str] = []
    error_message: Optional[str] = None
    execution_time: float
    metadata: Dict[str, Any] = {}

# DCCOperation is imported to avoid circular imports
# We'll define it locally to prevent import issues

class DCCExecutor:
    """Executes DCC operations on local machine."""

    def __init__(self):
        self.discovery = get_dcc_discovery()
        self.running_processes: Dict[str, subprocess.Popen] = {}
        self.temp_directories: Dict[str, str] = {}
        self.maya_session_directories: Dict[str, str] = {}  # session_id -> session_directory mapping

        # Maya persistent server for instant operations (120-320x faster)
        self.maya_server: Optional[MayaServerManager] = None
        self.maya_server_enabled = True  # Feature flag

        # Blender persistent server for instant operations (25-50x faster)
        self.blender_server: Optional[BlenderServerManager] = None
        self.blender_server_enabled = True  # Feature flag

        # Houdini persistent server for instant operations (150-200x faster)
        self.houdini_server: Optional[HoudiniServerManager] = None
        self.houdini_server_enabled = True  # Feature flag

        # Unreal persistent server for instant operations
        self.unreal_server: Optional[UnrealServerManager] = None
        self.unreal_server_enabled = True  # Feature flag

        # GUI DCC server managers (connect to user's open DCC applications)
        self.houdini_gui_server: Optional[GUIServerManager] = None
        self.maya_gui_server: Optional[GUIServerManager] = None
        self.blender_gui_server: Optional[GUIServerManager] = None
        self.unreal_gui_server: Optional[GUIServerManager] = None
        self.gui_mode_enabled = True  # Feature flag for GUI detection

    def _is_wsl(self) -> bool:
        """Check if running in WSL (Windows Subsystem for Linux)."""
        try:
            import platform
            if platform.system() == 'Linux':
                # Check for WSL indicators
                with open('/proc/version', 'r') as f:
                    version_info = f.read().lower()
                    return 'microsoft' in version_info or 'wsl' in version_info
        except:
            pass
        return False

    def _get_windows_temp_from_wsl(self) -> Optional[str]:
        """Get Windows temp directory accessible from WSL.

        Tries multiple detection methods to work on any WSL machine,
        not just a specific user's setup.
        """
        try:
            # Method 1: Ask Windows for the real %TEMP% via WSL interop
            try:
                result = subprocess.run(
                    ['cmd.exe', '/c', 'echo', '%TEMP%'],
                    capture_output=True, text=True, timeout=5
                )
                win_temp = result.stdout.strip().strip('\r\n')
                # Validate: should look like C:\Users\...\Temp, not unexpanded %TEMP%
                if win_temp and ':' in win_temp and '%' not in win_temp:
                    drive = win_temp[0].lower()
                    rest = win_temp[3:].replace('\\', '/')
                    wsl_path = f"/mnt/{drive}/{rest}"
                    if os.path.exists(wsl_path):
                        logger.debug(f"Found Windows temp via cmd.exe: {wsl_path}")
                        return wsl_path
            except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
                pass

            # Method 2: Scan /mnt/c/Users/ for actual user directories
            users_dir = "/mnt/c/Users"
            if os.path.isdir(users_dir):
                skip = {'Public', 'Default', 'Default User', 'All Users', 'desktop.ini'}
                for entry in sorted(os.listdir(users_dir)):
                    if entry in skip or entry.startswith('.'):
                        continue
                    temp_candidate = os.path.join(users_dir, entry, "AppData", "Local", "Temp")
                    if os.path.isdir(temp_candidate):
                        logger.debug(f"Found Windows temp via user scan: {temp_candidate}")
                        return temp_candidate

            logger.warning("Could not find Windows temp directory from WSL")
            return None

        except Exception as e:
            logger.error(f"Failed to get Windows temp directory: {e}")
            return None

    def _wsl_to_windows_path(self, wsl_path: str) -> str:
        """Convert WSL path to Windows path for use in Windows applications."""
        try:
            # Convert /mnt/c/Users/... to C:\Users\...
            if wsl_path.startswith('/mnt/'):
                # Extract drive letter and rest of path
                parts = wsl_path.split('/')
                if len(parts) >= 3:
                    drive_letter = parts[2].upper()
                    remaining_path = '/'.join(parts[3:])
                    # Convert to Windows path
                    windows_path = f"{drive_letter}:\\{remaining_path.replace('/', chr(92))}"
                    return windows_path

            # If not a /mnt/ path, return as-is (might already be Windows path)
            return wsl_path

        except Exception as e:
            logger.error(f"Failed to convert WSL path to Windows: {e}")
            return wsl_path

    async def initialize(self):
        """Initialize the DCC executor."""
        logger.info("🔧 Initializing DCC Executor")

        # Ensure DCC discovery is complete
        if not self.discovery.discovered_dccs:
            self.discovery.discover_all()

        # Create base temp directory
        # If running in WSL, use Windows temp directory for Windows DCC compatibility
        if self._is_wsl():
            # Use Windows temp directory accessible from WSL
            windows_temp = self._get_windows_temp_from_wsl()
            if windows_temp and os.path.exists(windows_temp):
                self.base_temp_dir = tempfile.mkdtemp(prefix="plumber_dcc_", dir=windows_temp)
                logger.info(f"📁 Created base temp directory (Windows): {self.base_temp_dir}")
            else:
                # Fallback to WSL temp
                self.base_temp_dir = tempfile.mkdtemp(prefix="plumber_dcc_")
                logger.warning(f"⚠️  Using WSL temp directory (may cause issues with Windows DCCs): {self.base_temp_dir}")
        else:
            self.base_temp_dir = tempfile.mkdtemp(prefix="plumber_dcc_")
            logger.info(f"📁 Created base temp directory: {self.base_temp_dir}")

        # Check for GUI DCC instances first (they get execution priority)
        await self._initialize_gui_servers()

        # Initialize Maya persistent server (if Maya is available and feature is enabled)
        if self.maya_server_enabled:
            maya_available = self.discovery.discovered_dccs.get('maya', {}).get('available', False)
            if maya_available:
                await self._initialize_maya_server()
            else:
                logger.info("ℹ️  Maya not available, skipping persistent server startup")

        # Initialize Blender persistent server (if Blender is available and feature is enabled)
        if self.blender_server_enabled:
            blender_available = self.discovery.discovered_dccs.get('blender', {}).get('available', False)
            if blender_available:
                await self._initialize_blender_server()
            else:
                logger.info("ℹ️  Blender not available, skipping persistent server startup")

        # Initialize Houdini persistent server (if Houdini is available and feature is enabled)
        if self.houdini_server_enabled:
            houdini_available = self.discovery.discovered_dccs.get('houdini', {}).get('available', False)
            if houdini_available:
                await self._initialize_houdini_server()
            else:
                logger.info("ℹ️  Houdini not available, skipping persistent server startup")

        # Initialize Unreal persistent server (if Unreal is available and feature is enabled)
        if self.unreal_server_enabled:
            unreal_available = self.discovery.discovered_dccs.get('unreal', {}).get('available', False)
            if unreal_available:
                await self._initialize_unreal_server()
            else:
                logger.info("ℹ️  Unreal Engine not available, skipping persistent server startup")

    def _get_persistent_server_base_dir(self, dcc_name: str) -> Optional[Path]:
        """Get WSL-compatible base directory for persistent server IPC files.

        In WSL, DCC executables are Windows programs that cannot access
        WSL-only paths like /tmp/. This returns a Windows-accessible path
        under /mnt/c/Users/.../AppData/Local/Temp/ instead.
        """
        if self._is_wsl():
            windows_temp = self._get_windows_temp_from_wsl()
            if windows_temp and os.path.exists(windows_temp):
                base_dir = Path(windows_temp) / f"plumber_{dcc_name}_persistent"
                logger.info(f"   WSL detected: using Windows temp for {dcc_name} IPC: {base_dir}")
                return base_dir
            else:
                logger.warning(f"   WSL detected but Windows temp not found — {dcc_name} server may fail")
        return None  # Let the server manager use its default

    async def _initialize_maya_server(self):
        """Initialize Maya persistent server for instant operations."""
        logger.info("🚀 Initializing Maya persistent server...")

        try:
            # In WSL, use Windows-accessible temp dir for IPC files
            base_dir = self._get_persistent_server_base_dir("maya")
            self.maya_server = MayaServerManager(base_dir=base_dir)

            # Start server (runs in background thread)
            # This is a blocking operation but only happens once at startup
            success = await asyncio.get_event_loop().run_in_executor(
                None,
                self.maya_server.start_server
            )

            if success:
                logger.info("✅ Maya persistent server initialized successfully")
                logger.info(f"   Startup time: {self.maya_server.server_startup_time:.2f}s")
                logger.info("   All Maya operations will now use persistent server (120-320x faster)")
            else:
                logger.warning("⚠️  Maya persistent server failed to start")
                logger.warning("   Falling back to subprocess approach for Maya operations")
                self.maya_server = None

        except Exception as e:
            logger.error(f"❌ Error initializing Maya persistent server: {e}")
            logger.warning("   Falling back to subprocess approach for Maya operations")
            self.maya_server = None

    async def _initialize_blender_server(self):
        """Initialize Blender persistent server for instant operations."""
        logger.info("🚀 Initializing Blender persistent server...")

        try:
            # Get discovered Blender executable path
            blender_path = self.discovery.get_executable_path("blender")
            if not blender_path:
                logger.warning("⚠️  Blender executable not found in DCC discovery")
                logger.warning("   Falling back to subprocess approach for Blender operations")
                self.blender_server = None
                return

            # In WSL, use Windows-accessible temp dir for IPC files
            base_dir = self._get_persistent_server_base_dir("blender")

            # Initialize server with discovered path and WSL-compatible base_dir
            self.blender_server = BlenderServerManager(blender_path=blender_path, base_dir=base_dir)

            # Start server (runs in background thread)
            # This is a blocking operation but only happens once at startup
            success = await asyncio.get_event_loop().run_in_executor(
                None,
                self.blender_server.start_server
            )

            if success:
                logger.info("✅ Blender persistent server initialized successfully")
                logger.info(f"   Startup time: {self.blender_server.server_startup_time:.2f}s")
                logger.info("   All Blender operations will now use persistent server (25-50x faster)")
            else:
                logger.warning("⚠️  Blender persistent server failed to start")
                logger.warning("   Falling back to subprocess approach for Blender operations")
                self.blender_server = None

        except Exception as e:
            logger.error(f"❌ Error initializing Blender persistent server: {e}")
            logger.warning("   Falling back to subprocess approach for Blender operations")
            self.blender_server = None

    async def _initialize_houdini_server(self):
        """Initialize Houdini persistent server for instant operations."""
        logger.info("🚀 Initializing Houdini persistent server...")

        try:
            # In WSL, use Windows-accessible temp dir for IPC files
            base_dir = self._get_persistent_server_base_dir("houdini")
            self.houdini_server = HoudiniServerManager(base_dir=base_dir)

            # Start server (runs in background thread)
            # This is a blocking operation but only happens once at startup
            success = await asyncio.get_event_loop().run_in_executor(
                None,
                self.houdini_server.start_server
            )

            if success:
                logger.info("✅ Houdini persistent server initialized successfully")
                logger.info(f"   Startup time: {self.houdini_server.server_startup_time:.2f}s")
                logger.info("   All Houdini operations will now use persistent server (150-200x faster)")
            else:
                logger.warning("⚠️  Houdini persistent server failed to start")
                logger.warning("   Falling back to subprocess approach for Houdini operations")
                self.houdini_server = None

        except Exception as e:
            logger.error(f"❌ Error initializing Houdini persistent server: {e}")
            logger.warning("   Falling back to subprocess approach for Houdini operations")
            self.houdini_server = None

    async def _initialize_unreal_server(self):
        """Initialize Unreal Engine persistent server for instant operations."""
        logger.info("🚀 Initializing Unreal Engine persistent server...")

        try:
            base_dir = self._get_persistent_server_base_dir("unreal")

            # Get UE executable path from discovery
            unreal_info = self.discovery.discovered_dccs.get('unreal', {})
            unreal_cmd_path = unreal_info.get('executable')

            self.unreal_server = UnrealServerManager(
                base_dir=base_dir,
                unreal_cmd_path=unreal_cmd_path
            )

            # Start server (runs in background thread)
            # UE startup is heavy — 30-120s timeout
            success = await asyncio.get_event_loop().run_in_executor(
                None,
                lambda: self.unreal_server.start_server(timeout=120)
            )

            if success:
                logger.info("✅ Unreal Engine persistent server initialized successfully")
                logger.info(f"   Startup time: {self.unreal_server.server_startup_time:.2f}s")
                logger.info("   All Unreal operations will now use persistent server")
            else:
                logger.warning("⚠️  Unreal Engine persistent server failed to start")
                logger.warning("   Unreal operations will not be available")
                self.unreal_server = None

        except Exception as e:
            logger.error(f"❌ Error initializing Unreal Engine persistent server: {e}")
            self.unreal_server = None

    async def _initialize_gui_servers(self):
        """Check for running GUI DCC instances with Plumber addon."""
        if not self.gui_mode_enabled:
            return

        logger.info("[GUI] Checking for GUI DCC instances...")

        for dcc_name, attr_name in [
            ("houdini", "houdini_gui_server"),
            ("maya", "maya_gui_server"),
            ("blender", "blender_gui_server"),
            ("unreal", "unreal_gui_server"),
        ]:
            mgr = GUIServerManager(dcc_name)
            if mgr.check_gui_available():
                setattr(self, attr_name, mgr)
                logger.info(f"[GUI] Found {dcc_name} GUI instance (v{mgr.gui_dcc_version}, PID {mgr.gui_dcc_pid})")
            else:
                logger.debug(f"[GUI] No {dcc_name} GUI instance detected")

    async def refresh_gui_servers(self):
        """Re-check for GUI DCC instances. Call periodically (e.g. every 30s from heartbeat)."""
        if not self.gui_mode_enabled:
            return

        for dcc_name, attr_name in [
            ("houdini", "houdini_gui_server"),
            ("maya", "maya_gui_server"),
            ("blender", "blender_gui_server"),
            ("unreal", "unreal_gui_server"),
        ]:
            current = getattr(self, attr_name)
            if current and current.is_alive():
                continue  # Still alive, skip

            # Check for new or reconnected GUI
            mgr = GUIServerManager(dcc_name)
            if mgr.check_gui_available():
                setattr(self, attr_name, mgr)
                logger.info(f"[GUI] Detected {dcc_name} GUI instance (v{mgr.gui_dcc_version})")
            elif current:
                setattr(self, attr_name, None)
                logger.info(f"[GUI] {dcc_name} GUI instance disconnected")

    def get_gui_status(self) -> Dict[str, Any]:
        """Get status of all GUI DCC connections for agent registration/heartbeat."""
        status = {}
        for dcc_name, attr_name in [
            ("houdini", "houdini_gui_server"),
            ("maya", "maya_gui_server"),
            ("blender", "blender_gui_server"),
            ("unreal", "unreal_gui_server"),
        ]:
            mgr = getattr(self, attr_name)
            if mgr and mgr.is_ready:
                status[dcc_name] = {
                    'connected': True,
                    'mode': 'gui',
                    'version': mgr.gui_dcc_version,
                    'pid': mgr.gui_dcc_pid
                }
            else:
                status[dcc_name] = {'connected': False}
        return status

    async def execute_operation(
        self,
        operation,
        progress_callback: Optional[Callable] = None
    ) -> OperationResult:
        """Execute a DCC operation."""
        start_time = time.time()
        operation_id = operation.operation_id
        dcc_type = operation.dcc_type

        logger.info(f"🎬 Executing {dcc_type} operation: {operation_id}")

        try:
            # Create operation-specific temp directory
            op_temp_dir = os.path.join(self.base_temp_dir, operation_id)
            os.makedirs(op_temp_dir, exist_ok=True)
            self.temp_directories[operation_id] = op_temp_dir

            if progress_callback:
                await progress_callback(10, f"Preparing {dcc_type} operation")

            # Route to appropriate DCC handler
            if dcc_type == "maya":
                result = await self._execute_maya_operation(operation, progress_callback)
            elif dcc_type == "blender":
                result = await self._execute_blender_operation(operation, progress_callback)
            elif dcc_type == "houdini":
                result = await self._execute_houdini_operation(operation, progress_callback)
            elif dcc_type == "unreal":
                result = await self._execute_unreal_operation(operation, progress_callback)
            elif dcc_type == "nuke":
                result = await self._execute_nuke_operation(operation, progress_callback)
            elif dcc_type == "natron":
                result = await self._execute_natron_operation(operation, progress_callback)
            elif dcc_type == "vectorworks":
                result = await self._execute_vectorworks_operation(operation, progress_callback)
            else:
                raise ValueError(f"Unsupported DCC type: {dcc_type}")

            execution_time = time.time() - start_time
            result.execution_time = execution_time

            if progress_callback:
                await progress_callback(100, f"{dcc_type} operation completed")

            logger.info(f"✅ Operation {operation_id} completed in {execution_time:.2f}s")
            return result

        except Exception as e:
            execution_time = time.time() - start_time
            error_msg = f"Operation failed: {str(e)}"
            logger.error(f"❌ Operation {operation_id} failed after {execution_time:.2f}s: {e}")

            if progress_callback:
                await progress_callback(0, f"Operation failed: {str(e)}")

            return OperationResult(
                operation_id=operation_id,
                success=False,
                error_message=error_msg,
                execution_time=execution_time
            )

        finally:
            # Cleanup
            if operation_id in self.running_processes:
                del self.running_processes[operation_id]

    async def _execute_maya_operation(self, operation, progress_callback=None) -> OperationResult:
        """Execute Maya operation."""
        start_time = time.time()  # Track execution time for this method

        try:
            # Safe attribute access with debugging
            operation_id = str(operation.operation_id) if hasattr(operation, 'operation_id') else 'unknown'
            op_type = str(operation.operation_type) if hasattr(operation, 'operation_type') else 'unknown'
            params = operation.parameters if hasattr(operation, 'parameters') else {}

            # Use the temp directory we created (which is already Windows-compatible)
            # instead of operation.output_directory which may have WSL paths
            output_dir = self.temp_directories.get(operation_id, '/tmp')

            # ═══════════════════════════════════════════════════════════════
            # PRIORITY 1: GUI SERVER (user's open Maya with addon)
            # ═══════════════════════════════════════════════════════════════
            if self.maya_gui_server and self.maya_gui_server.is_alive():
                logger.info(f"[GUI] Using Maya GUI server for operation: {op_type}")

                try:
                    return await self._execute_maya_via_persistent_server(
                        operation_id, op_type, params, output_dir, progress_callback, start_time,
                        server=self.maya_gui_server
                    )
                except Exception as e:
                    logger.warning(f"[GUI] Maya GUI server failed: {e}, trying headless...")

            # ═══════════════════════════════════════════════════════════════
            # PRIORITY 2: HEADLESS PERSISTENT SERVER (120-320x faster)
            # ═══════════════════════════════════════════════════════════════
            if self.maya_server and self.maya_server.is_ready:
                logger.info(f"🎯 Using Maya persistent server for operation: {op_type}")

                try:
                    return await self._execute_maya_via_persistent_server(
                        operation_id, op_type, params, output_dir, progress_callback, start_time,
                        server=self.maya_server
                    )
                except Exception as e:
                    logger.warning(f"⚠️  Persistent server failed: {e}")
                    logger.info("   Falling back to subprocess approach")

            # ═══════════════════════════════════════════════════════════════
            # PRIORITY 3: SUBPROCESS FALLBACK
            # ═══════════════════════════════════════════════════════════════
            if self.maya_server or self.maya_gui_server:
                logger.debug("Using subprocess fallback (persistent/GUI servers unavailable)")
            else:
                logger.debug("Using subprocess approach (no persistent servers initialized)")

            # WORKAROUND: Backend sends Linux paths (/tmp/plumber_maya_XXX)
            # Convert to Windows temp directory for local Windows agent
            if output_dir.startswith('/tmp/plumber_maya_'):
                # Extract the unique suffix
                maya_suffix = output_dir.replace('/tmp/plumber_maya_', '')
                # Create in Windows temp directory with same suffix
                output_dir = os.path.join(tempfile.gettempdir(), f'plumber_maya_{maya_suffix}')
                os.makedirs(output_dir, exist_ok=True)
                logger.info(f"🔄 Converted backend Linux path to Windows path: {output_dir}")

            # Debug logging
            logger.debug(f"Maya operation - ID: {operation_id}, Type: {op_type}, Params: {type(params)}")

            # Ensure params is a dictionary
            if not isinstance(params, dict):
                logger.warning(f"Parameters is not a dict, got {type(params)}: {params}")
                params = {}

        except Exception as e:
            logger.error(f"Error accessing operation attributes: {e}")
            raise RuntimeError(f"Failed to process operation data: {e}")

        # Handle session communication operations (command, session_close)
        # These operations communicate with existing Maya sessions via files
        if op_type in ["command", "session_close"]:
            return await self._handle_maya_session_communication(
                operation_id, op_type, params, output_dir, progress_callback
            )

        # Get Maya executable
        mayapy_path = self.discovery.get_executable_path("maya", "python")
        if not mayapy_path:
            raise RuntimeError("Maya Python executable not found")

        if progress_callback:
            await progress_callback(20, "Preparing Maya script")

        # Multiple execution strategies for Maya 2026 Qt WebEngine fix
        # OPTIMIZED ORDER: Most reliable strategy first for fast execution
        execution_strategies = []

        # Convert Windows backslashes to forward slashes for Maya compatibility
        script_path_base = os.path.join(self.temp_directories[operation_id], "maya_script")

        # PRIORITY STRATEGY: mayapy.exe direct execution (fastest, most reliable)
        # Use simplified mode for sessions - no Qt overhead!
        mayapy_script_path = f"{script_path_base}_mayapy.py"
        mayapy_script = self._generate_maya_script(op_type, params, output_dir, execution_mode="mayapy_simple")
        with open(mayapy_script_path, 'w', encoding='utf-8') as f:
            f.write(mayapy_script)

        mayapy_script_maya = mayapy_script_path.replace('\\', '/')

        # Strategy 1: mayapy.exe direct (no GUI, pure Python, fastest)
        execution_strategies.append({
            "name": "mayapy_direct_simple",
            "cmd": [mayapy_path, mayapy_script_maya],
            "description": "Maya Python direct execution (simplified, no Qt)",
            "script_path": mayapy_script_path
        })

        # FALLBACK STRATEGY: maya.exe -batch (if mayapy fails)
        maya_exe_path = self.discovery.get_executable_path("maya", "main")
        if maya_exe_path:
            batch_script_path = f"{script_path_base}_batch.py"
            batch_script = self._generate_maya_script(op_type, params, output_dir, execution_mode="batch")
            with open(batch_script_path, 'w', encoding='utf-8') as f:
                f.write(batch_script)

            batch_script_maya = batch_script_path.replace('\\', '/')

            # Strategy 2: Maya batch mode with command (fallback)
            execution_strategies.append({
                "name": "maya_batch_command",
                "cmd": [maya_exe_path, "-batch", "-command", f"python(\"exec(open(r'{batch_script_maya}').read())\")"],
                "description": "Maya batch mode with -command",
                "script_path": batch_script_path
            })


        last_error = None

        for i, strategy in enumerate(execution_strategies):
            cmd = strategy["cmd"]
            logger.info(f"🎯 Trying Maya execution strategy {i+1}/{len(execution_strategies)}: {strategy['description']}")
            logger.info(f"🐍 Running Maya command: {' '.join(cmd)}")

            try:
                # For session operations, launch in background and don't wait for exit
                if op_type == "session":
                    # Launch Maya in background (detached process, no window)
                    # Note: time and subprocess already imported at module level

                    # Create truly headless process - no console window!
                    CREATE_NO_WINDOW = 0x08000000
                    DETACHED_PROCESS = 0x00000008

                    logger.info(f"⏱️ [PROFILING] Creating Maya process...")
                    process_start = time.time()

                    process = subprocess.Popen(
                        cmd,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        text=True,
                        cwd=self.temp_directories[operation_id],
                        creationflags=(CREATE_NO_WINDOW | DETACHED_PROCESS) if os.name == 'nt' else 0,
                        close_fds=True if os.name != 'nt' else False
                    )

                    process_create_time = time.time() - process_start
                    logger.info(f"⏱️ [PROFILING] Process created in {process_create_time:.2f}s (PID: {process.pid})")

                    self.running_processes[operation_id] = process

                    logger.info(f"📝 Maya session process launched (PID: {process.pid})")
                    logger.info(f"⏳ Waiting for session info file to be created...")
                    file_wait_start = time.time()

                    # Wait for session info file to be created (max 60 seconds)
                    session_id = params.get("session_id", "default_session")

                    # Build session info file path
                    # Use os.path.join for native Windows path handling
                    session_info_file = os.path.join(output_dir, f"maya_session_{session_id}_info.txt")

                    # Store session directory for future operations
                    self.maya_session_directories[session_id] = output_dir
                    logger.info(f"📁 Stored session directory for {session_id}: {output_dir}")

                    timeout_start = time.time()
                    session_ready = False
                    output_lines = []

                    while time.time() - timeout_start < 60:
                        # Check if session info file exists and has content
                        if os.path.exists(session_info_file) and os.path.getsize(session_info_file) > 0:
                            # Wait a bit more to ensure file is fully written
                            await asyncio.sleep(0.5)

                            try:
                                import json
                                with open(session_info_file, 'r') as f:
                                    session_data = json.load(f)

                                if session_data.get("session_id"):
                                    session_ready = True
                                    logger.info(f"✅ Session info file ready: {session_info_file}")
                                    break
                            except (json.JSONDecodeError, IOError):
                                # File not fully written yet, keep waiting
                                pass

                        # Update progress
                        if progress_callback:
                            elapsed = time.time() - timeout_start
                            progress_pct = min(80, int((elapsed / 60) * 80))
                            await progress_callback(progress_pct, f"Waiting for Maya session to initialize ({int(elapsed)}s)...")

                        # Read any output from process (profiling timestamps)
                        try:
                            line = process.stdout.readline()
                            if line:
                                output_lines.append(line.strip())
                                # Log profiling lines at INFO level so we can see them
                                if "[PROFILING]" in line or "STEP" in line:
                                    logger.info(f"⏱️ Maya profiling: {line.strip()}")
                                else:
                                    logger.debug(f"Maya output: {line.strip()}")
                        except:
                            pass

                        await asyncio.sleep(0.5)

                    file_wait_time = time.time() - file_wait_start

                    if not session_ready:
                        logger.error(f"⏱️ [PROFILING] File wait timeout after {file_wait_time:.2f}s")
                        raise RuntimeError(f"Session info file not created after 60 seconds. Expected: {session_info_file}")

                    logger.info(f"⏱️ [PROFILING] Session info file appeared after {file_wait_time:.2f}s")

                    # Session is ready! Read session data and return
                    if progress_callback:
                        await progress_callback(80, "Session initialized successfully")

                    with open(session_info_file, 'r') as f:
                        session_data = json.load(f)

                    total_time = time.time() - process_start
                    logger.info(f"⏱️ [PROFILING] TOTAL SESSION CREATION TIME: {total_time:.2f}s")
                    logger.info(f"⏱️ [PROFILING] Breakdown: Process={process_create_time:.2f}s, FileWait={file_wait_time:.2f}s")

                    metadata = {
                        "dcc": "maya",
                        "operation_type": op_type,
                        "execution_strategy": strategy["name"],
                        "session_id": session_data.get("session_id"),
                        "session_status": session_data.get("status"),
                        "command_queue": session_data.get("command_queue"),
                        "response_queue": session_data.get("response_queue"),
                        "maya_version": session_data.get("maya_version"),
                        "process_pid": process.pid,
                        "profiling": {
                            "total_time": total_time,
                            "process_creation": process_create_time,
                            "file_wait": file_wait_time
                        }
                    }

                    logger.info(f"✅ Maya session created successfully: {metadata['session_id']} (PID: {process.pid})")
                    logger.info(f"📋 Session will remain active in background")

                    return OperationResult(
                        operation_id=operation_id,
                        success=True,
                        output_files=[],
                        logs=output_lines + [
                            f"Session created: {session_id}",
                            f"Process PID: {process.pid}",
                            f"Status: {session_data.get('status')}",
                            f"Command queue: {session_data.get('command_queue')}"
                        ],
                        execution_time=time.time() - start_time,
                        metadata=metadata
                    )

                # For non-session operations, use normal blocking execution
                process = subprocess.Popen(
                    cmd,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    text=True,
                    cwd=self.temp_directories[operation_id]
                )

                self.running_processes[operation_id] = process

                # Monitor progress
                output_lines = []
                error_lines = []

                while process.poll() is None:
                    if progress_callback:
                        await progress_callback(50, "Maya processing...")

                    # Read output
                    output = process.stdout.readline()
                    if output:
                        output_lines.append(output.strip())
                        logger.debug(f"Maya output: {output.strip()}")

                    await asyncio.sleep(0.1)

                # Get remaining output
                remaining_out, remaining_err = process.communicate()
                if remaining_out:
                    output_lines.extend(remaining_out.split('\n'))
                if remaining_err:
                    error_lines.extend(remaining_err.split('\n'))

                if progress_callback:
                    await progress_callback(80, "Collecting Maya results")

                # Check result
                if process.returncode == 0:
                    # Success! Collect output files and return
                    output_files = self._collect_output_files(output_dir)
                    logger.info(f"✅ Maya execution successful with strategy: {strategy['description']}")

                    # For session operations, extract session data from session_info file
                    metadata = {
                        "dcc": "maya",
                        "operation_type": op_type,
                        "return_code": process.returncode,
                        "execution_strategy": strategy["name"]
                    }

                    if op_type == "session":
                        # Read session info file to get session_id and communication files
                        session_id = params.get("session_id", "default_session")
                        session_info_file = os.path.join(output_dir, f"maya_session_{session_id}_info.txt")

                        if os.path.exists(session_info_file):
                            try:
                                import json
                                with open(session_info_file, 'r') as f:
                                    session_data = json.load(f)

                                # Add session data to metadata so backend can return it
                                metadata["session_id"] = session_data.get("session_id")
                                metadata["session_status"] = session_data.get("status")
                                metadata["command_queue"] = session_data.get("command_queue")
                                metadata["response_queue"] = session_data.get("response_queue")
                                metadata["maya_version"] = session_data.get("maya_version")

                                logger.info(f"📋 Session created: {metadata['session_id']}")
                            except Exception as e:
                                logger.warning(f"Failed to read session info: {e}")
                        else:
                            logger.warning(f"Session info file not found: {session_info_file}")

                    return OperationResult(
                        operation_id=operation_id,
                        success=True,
                        output_files=output_files,
                        logs=output_lines,
                        execution_time=time.time() - start_time,
                        metadata=metadata
                    )
                else:
                    # This strategy failed, try next one
                    error_msg = f"Maya strategy '{strategy['description']}' failed with return code {process.returncode}"
                    if error_lines:
                        error_msg += f": {' '.join(error_lines)}"

                    last_error = error_msg
                    logger.warning(f"⚠️ {error_msg}")

                    # Check if this is a Qt WebEngine error (try next strategy)
                    full_error = ' '.join(error_lines)
                    if "Qt WebEngine seems to be initialized" in full_error:
                        logger.info(f"🔄 Qt WebEngine error detected, trying next strategy...")
                        continue
                    else:
                        # Different error, try next strategy anyway
                        logger.info(f"🔄 Non-Qt error, trying next strategy...")
                        continue

            except Exception as strategy_error:
                last_error = f"Strategy '{strategy['description']}' failed with exception: {strategy_error}"
                logger.warning(f"⚠️ {last_error}")
                continue

        # Strategy 4: MEL-based execution (bypass Python DLL issues)
        logger.info("🎯 Trying Maya execution strategy 4/4: MEL-based execution (DLL bypass)")
        try:
            mel_success, mel_result = await self._execute_maya_mel_operation(operation_id, op_type, params, output_dir)
            if mel_success:
                logger.info("✅ Maya MEL execution successful")
                return OperationResult(
                    operation_id=operation_id,
                    success=True,
                    result=mel_result,
                    execution_time=time.time() - start_time
                )
            else:
                last_error = f"Maya MEL strategy failed: {mel_result.get('error', 'Unknown error')}"
        except Exception as e:
            last_error = f"Maya MEL strategy failed: {str(e)}"

        # All strategies failed
        if last_error:
            raise RuntimeError(f"All Maya execution strategies failed. Last error: {last_error}")
        else:
            raise RuntimeError("All Maya execution strategies failed with unknown errors")

    async def _execute_maya_mel_operation(self, operation_id: str, operation_type: str, params: Dict, output_dir: str):
        """Execute Maya operation using MEL script to bypass Python DLL issues."""

        try:
            maya_exe_path = self.discovery.get_executable_path("maya", "main")
            if not maya_exe_path or not os.path.exists(maya_exe_path):
                return False, {"error": "Maya executable not found"}

            # Create MEL script content
            mel_script = f'''
// Maya MEL Script - Bypass Python DLL Issues
// Operation: {operation_type}
print("Maya MEL Script Starting - Operation: {operation_type}");

// Create basic scene
file -new -force;

// Example Maya operations via MEL
switch ("{operation_type}") {{
    case "create_sphere":
        // Create sphere
        polySphere -r 1 -sx 20 -sy 20 -ax 0 1 0 -cuv 2 -ch 1;
        rename "pSphere1" "test_sphere";
        print("Created sphere: test_sphere");
        break;

    case "create_cube":
        // Create cube
        polyCube -w 1 -h 1 -d 1 -sx 1 -sy 1 -sz 1 -ax 0 1 0 -cuv 4 -ch 1;
        rename "pCube1" "test_cube";
        print("Created cube: test_cube");
        break;

    default:
        // Generic operation
        print("Executing generic Maya operation: {operation_type}");
        polySphere -r 1;
        print("Created default sphere");
        break;
}}

// Save result to JSON file
string $resultFile = "{output_dir.replace(chr(92), "/")}/maya_result.json";
string $resultJson = "{{" +
    "\\"status\\": \\"success\\", " +
    "\\"operation\\": \\"{operation_type}\\", " +
    "\\"message\\": \\"Maya MEL operation completed\\", " +
    "\\"maya_version\\": \\"" + `about -version` + "\\" " +
    "}}";

// Write result file
int $fileId = `fopen $resultFile "w"`;
if ($fileId > 0) {{
    fprint $fileId $resultJson;
    fclose $fileId;
    print("Result saved to: " + $resultFile);
}} else {{
    print("Failed to save result file: " + $resultFile);
}}

print("Maya MEL Script Completed");
quit -force;
'''

            # Write MEL script to temp file
            mel_script_path = os.path.join(self.temp_directories[operation_id], "maya_script.mel")
            with open(mel_script_path, 'w', encoding='utf-8') as f:
                f.write(mel_script)

            try:
                # Execute Maya with MEL script
                cmd = [
                    maya_exe_path,
                    '-batch',
                    '-command', f'source "{mel_script_path.replace(chr(92), "/")}"'
                ]

                logger.info(f"🐍 Running Maya MEL command: {' '.join(cmd)}")

                # Run Maya with MEL script using cross-platform process management
                process_manager = get_process_manager()
                try:
                    result = await asyncio.get_event_loop().run_in_executor(
                        None,
                        lambda: process_manager.run_command(
                            cmd,
                            cwd=os.path.dirname(maya_exe_path),
                            timeout=300,  # 5 minute timeout for MEL operations
                            capture_output=True
                        )
                    )

                    logger.info(f"Maya MEL execution completed with return code: {result.returncode}")
                    logger.debug(f"STDOUT: {result.stdout}")
                    if result.stderr:
                        logger.debug(f"STDERR: {result.stderr}")

                    # Check for result file
                    result_file = os.path.join(output_dir, "maya_result.json")
                    if os.path.exists(result_file):
                        with open(result_file, 'r') as f:
                            result_data = json.load(f)
                        return True, result_data
                    else:
                        return False, {"error": "No result file generated", "return_code": result.returncode}

                except subprocess.TimeoutExpired:
                    return False, {"error": "Maya MEL execution timed out"}
                except Exception as e:
                    return False, {"error": f"Maya MEL execution failed: {str(e)}"}

            except Exception as e:
                return False, {"error": f"Maya MEL script execution failed: {str(e)}"}

        except Exception as e:
            return False, {"error": f"Maya MEL operation failed: {str(e)}"}

    async def _handle_maya_session_communication(
        self,
        operation_id: str,
        op_type: str,
        params: dict,
        output_dir: str,
        progress_callback=None
    ) -> OperationResult:
        """Handle communication with existing Maya session (command, session_close operations)."""
        start_time = time.time()

        try:
            session_id = params.get("session_id", "default_session")
            logger.info(f"📡 Session communication: {op_type} for session {session_id}")

            # Use stored session directory instead of current operation directory
            if session_id in self.maya_session_directories:
                session_output_dir = self.maya_session_directories[session_id]
                logger.info(f"📁 Using stored session directory: {session_output_dir}")
            else:
                # Fallback to output_dir if session directory not found
                session_output_dir = output_dir
                logger.warning(f"⚠️ Session directory not found for {session_id}, using current output_dir: {output_dir}")

            # Session communication file paths (use os.path.join for native Windows paths)
            command_queue_file = os.path.join(session_output_dir, f"maya_session_{session_id}_queue.txt")
            response_queue_file = os.path.join(session_output_dir, f"maya_session_{session_id}_response.txt")
            status_file = os.path.join(session_output_dir, f"maya_session_{session_id}_status.txt")
            session_info_file = os.path.join(session_output_dir, f"maya_session_{session_id}_info.txt")

            # Check if session exists
            if not os.path.exists(session_info_file):
                raise RuntimeError(
                    f"Maya session '{session_id}' not found. "
                    f"Expected session info at: {session_info_file}"
                )

            # Read session info
            import json
            with open(session_info_file, 'r') as f:
                session_data = json.load(f)

            logger.info(f"📋 Session data: {session_data}")

            if progress_callback:
                await progress_callback(20, f"Communicating with Maya session {session_id}")

            if op_type == "command":
                # Send command to session
                command = params.get("command", "")
                if not command:
                    raise ValueError("No command specified for 'command' operation")

                logger.info(f"📝 Writing command to queue: {command[:100]}...")

                # Clear response queue before sending command
                open(response_queue_file, 'w').close()

                # Write command to queue
                with open(command_queue_file, 'w') as f:
                    f.write(command)

                if progress_callback:
                    await progress_callback(50, "Waiting for Maya to execute command")

                # Wait for response
                timeout = params.get("timeout", 30)  # Default 30s timeout
                poll_start = time.time()

                response = None
                while time.time() - poll_start < timeout:
                    if os.path.exists(response_queue_file) and os.path.getsize(response_queue_file) > 0:
                        with open(response_queue_file, 'r') as f:
                            response = f.read().strip()
                        if response:
                            break

                    await asyncio.sleep(0.1)  # Poll every 100ms

                if response is None:
                    raise RuntimeError(
                        f"Maya session '{session_id}' did not respond within {timeout}s timeout"
                    )

                if progress_callback:
                    await progress_callback(80, "Processing Maya response")

                # Check response
                success = response.startswith("SUCCESS")
                logs = [
                    f"Command sent to Maya session: {session_id}",
                    f"Command: {command[:200]}",  # First 200 chars
                    f"Response: {response}"
                ]

                if not success:
                    logger.error(f"❌ Maya command failed: {response}")
                    return OperationResult(
                        operation_id=operation_id,
                        success=False,
                        error_message=response,
                        logs=logs,
                        execution_time=time.time() - start_time
                    )

                logger.info(f"✅ Maya command executed successfully")
                return OperationResult(
                    operation_id=operation_id,
                    success=True,
                    output_files=[],
                    logs=logs,
                    execution_time=time.time() - start_time,
                    metadata={
                        "session_id": session_id,
                        "command": command,
                        "response": response
                    }
                )

            elif op_type == "session_close":
                # Close Maya session
                logger.info(f"🔒 Closing Maya session: {session_id}")

                # Send close command
                with open(command_queue_file, 'w') as f:
                    f.write("CLOSE_SESSION")

                if progress_callback:
                    await progress_callback(50, "Sending close signal to Maya session")

                # Wait for session to close (check status file)
                timeout = 10  # 10s to close
                poll_start = time.time()

                closed = False
                while time.time() - poll_start < timeout:
                    if os.path.exists(status_file):
                        with open(status_file, 'r') as f:
                            status = f.read().strip()
                        if status in ["closed", "timeout"]:
                            closed = True
                            break

                    await asyncio.sleep(0.2)

                logs = [
                    f"Close signal sent to Maya session: {session_id}",
                    f"Session closed: {closed}"
                ]

                logger.info(f"✅ Maya session close signal sent (closed={closed})")

                return OperationResult(
                    operation_id=operation_id,
                    success=True,
                    output_files=[],
                    logs=logs,
                    execution_time=time.time() - start_time,
                    metadata={
                        "session_id": session_id,
                        "closed": closed
                    }
                )

            else:
                raise ValueError(f"Unknown session communication operation type: {op_type}")

        except Exception as e:
            error_msg = f"Session communication failed: {str(e)}"
            logger.error(f"❌ {error_msg}")

            return OperationResult(
                operation_id=operation_id,
                success=False,
                error_message=error_msg,
                execution_time=time.time() - start_time
            )

    async def _execute_blender_via_persistent_server(
        self,
        operation_id: str,
        op_type: str,
        params: Dict,
        output_dir: str,
        progress_callback,
        start_time: float,
        server=None
    ) -> OperationResult:
        """
        Execute Blender operation via persistent or GUI server.

        This method generates Blender Python code and sends it to the active
        server (GUI or headless), avoiding the 5-10s Blender initialization overhead.

        Args:
            server: Optional server instance to use. If None, uses self.blender_server.
        """
        active_server = server or self.blender_server
        is_gui = isinstance(active_server, GUIServerManager)
        server_label = "Blender GUI" if is_gui else "Blender persistent"

        if progress_callback:
            await progress_callback(20, f"Sending operation to {server_label} server")

        # Generate Blender command for persistent server (sets result as structured dict)
        blender_command = self._generate_blender_command_for_persistent_server(op_type, params, output_dir)

        if progress_callback:
            await progress_callback(40, "Executing Blender command")

        # Execute via active server (GUI or headless)
        result = await asyncio.get_event_loop().run_in_executor(
            None,
            lambda: active_server.execute_command(blender_command, op_type, timeout=60)
        )

        if progress_callback:
            await progress_callback(80, "Processing results")

        execution_time = time.time() - start_time

        # Build operation result
        if result.get('success'):
            logger.info(f"✅ Blender operation '{op_type}' completed in {execution_time*1000:.1f}ms")

            metadata = {
                'dcc': 'blender',
                'operation_type': op_type,
                'via_persistent_server': True,
                'via_gui_server': is_gui,
                'server_execution_time': result.get('execution_time', 0),
                'total_time': result.get('total_time', 0),
                'dcc_result': result.get('result'),
                'console_output': result.get('output', ''),
            }

            if progress_callback:
                await progress_callback(100, "Operation complete")

            return OperationResult(
                operation_id=operation_id,
                success=True,
                output_files=[],
                logs=[f"Executed via {server_label} server in {execution_time*1000:.1f}ms"],
                error_message=None,
                execution_time=execution_time,
                metadata=metadata
            )
        else:
            error_msg = result.get('error', 'Unknown error')
            logger.error(f"❌ Blender operation '{op_type}' failed: {error_msg}")

            return OperationResult(
                operation_id=operation_id,
                success=False,
                output_files=[],
                logs=[],
                error_message=error_msg,
                execution_time=execution_time,
                metadata={
                    'dcc': 'blender',
                    'operation_type': op_type,
                    'via_persistent_server': True,
                    'via_gui_server': is_gui,
                    'dcc_result': result.get('result'),
                    'console_output': result.get('output', ''),
                }
            )

    async def _execute_blender_operation(self, operation, progress_callback=None) -> OperationResult:
        """Execute Blender operation."""
        start_time = time.time()
        try:
            # Safe attribute access
            operation_id = str(operation.operation_id) if hasattr(operation, 'operation_id') else 'unknown'
            op_type = str(operation.operation_type) if hasattr(operation, 'operation_type') else 'unknown'
            params = operation.parameters if hasattr(operation, 'parameters') else {}

            # Use the temp directory we created (which is already Windows-compatible)
            # instead of operation.output_directory which may have WSL paths
            output_dir = self.temp_directories.get(operation_id, '/tmp')

            # Ensure params is a dictionary
            if not isinstance(params, dict):
                logger.warning(f"Blender parameters is not a dict, got {type(params)}: {params}")
                params = {}

        except Exception as e:
            logger.error(f"Error accessing Blender operation attributes: {e}")
            raise RuntimeError(f"Failed to process Blender operation data: {e}")

        # ═══════════════════════════════════════════════════════════════
        # PRIORITY 1: GUI SERVER (user's open Blender with addon)
        # ═══════════════════════════════════════════════════════════════
        if self.blender_gui_server and self.blender_gui_server.is_alive():
            logger.info(f"[GUI] Using Blender GUI server for operation: {op_type}")

            try:
                return await self._execute_blender_via_persistent_server(
                    operation_id, op_type, params, output_dir, progress_callback, start_time,
                    server=self.blender_gui_server
                )
            except Exception as e:
                logger.warning(f"[GUI] Blender GUI server failed: {e}, trying headless...")

        # ═══════════════════════════════════════════════════════════════
        # PRIORITY 2: HEADLESS PERSISTENT SERVER (25-50x faster)
        # ═══════════════════════════════════════════════════════════════
        if self.blender_server and self.blender_server.is_alive():
            logger.info(f"🎯 Using Blender persistent server for operation: {op_type}")

            try:
                return await self._execute_blender_via_persistent_server(
                    operation_id, op_type, params, output_dir, progress_callback, start_time,
                    server=self.blender_server
                )
            except Exception as e:
                logger.warning(f"⚠️  Persistent server failed: {e}")
                logger.info("   Falling back to subprocess approach")

        # ═══════════════════════════════════════════════════════════════
        # PRIORITY 3: SUBPROCESS FALLBACK
        # ═══════════════════════════════════════════════════════════════
        if self.blender_server or self.blender_gui_server:
            logger.debug("Using subprocess fallback (persistent/GUI servers unavailable)")
        else:
            logger.debug("Using subprocess approach (no persistent servers initialized)")

        # Get Blender executable
        blender_path = self.discovery.get_executable_path("blender")
        if not blender_path:
            raise RuntimeError("Blender executable not found")

        if progress_callback:
            await progress_callback(20, "Preparing Blender script")

        # Create Blender script
        script_content = self._generate_blender_script(op_type, params, output_dir)
        script_path = os.path.join(self.temp_directories[operation_id], "blender_script.py")

        with open(script_path, 'w', encoding='utf-8') as f:
            f.write(script_content)

        if progress_callback:
            await progress_callback(30, "Executing Blender operation")

        # Execute Blender in background mode
        cmd = [
            blender_path,
            "--background",
            "--python", script_path
        ]

        # Add input file if specified
        if operation.input_files:
            cmd.extend(["--", "--input-file", operation.input_files[0]])

        logger.info(f"🟠 Running Blender command: {' '.join(cmd)}")

        try:
            process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                cwd=self.temp_directories[operation_id]
            )

            self.running_processes[operation_id] = process

            # Monitor progress
            output_lines = []
            error_lines = []

            while process.poll() is None:
                if progress_callback:
                    await progress_callback(50, "Blender processing...")

                output = process.stdout.readline()
                if output:
                    output_lines.append(output.strip())
                    logger.debug(f"Blender output: {output.strip()}")

                await asyncio.sleep(0.1)

            # Get remaining output
            remaining_out, remaining_err = process.communicate()
            if remaining_out:
                output_lines.extend(remaining_out.split('\n'))
            if remaining_err:
                error_lines.extend(remaining_err.split('\n'))

            if progress_callback:
                await progress_callback(80, "Collecting Blender results")

            if process.returncode != 0:
                error_msg = f"Blender process failed with return code {process.returncode}"
                if error_lines:
                    error_msg += f": {' '.join(error_lines)}"
                raise RuntimeError(error_msg)

            # Collect output files
            output_files = self._collect_output_files(output_dir)

            return OperationResult(
                operation_id=operation_id,
                success=True,
                output_files=output_files,
                logs=output_lines,
                execution_time=time.time() - start_time,
                metadata={
                    "dcc": "blender",
                    "operation_type": op_type,
                    "return_code": process.returncode
                }
            )

        except subprocess.TimeoutExpired:
            process.kill()
            raise RuntimeError(f"Blender operation timed out after {operation.timeout} seconds")

    async def _execute_houdini_operation(self, operation, progress_callback=None) -> OperationResult:
        """Execute Houdini operation."""
        start_time = time.time()
        try:
            # Safe attribute access
            operation_id = str(operation.operation_id) if hasattr(operation, 'operation_id') else 'unknown'
            op_type = str(operation.operation_type) if hasattr(operation, 'operation_type') else 'unknown'
            params = operation.parameters if hasattr(operation, 'parameters') else {}

            # Use the temp directory we created (which is already Windows-compatible)
            # instead of operation.output_directory which may have WSL paths
            output_dir = self.temp_directories.get(operation_id, '/tmp')

            # Ensure params is a dictionary
            if not isinstance(params, dict):
                logger.warning(f"Houdini parameters is not a dict, got {type(params)}: {params}")
                params = {}

        except Exception as e:
            logger.error(f"Error accessing Houdini operation attributes: {e}")
            raise RuntimeError(f"Failed to process Houdini operation data: {e}")

        # ═══════════════════════════════════════════════════════════════
        # PRIORITY 1: GUI SERVER (user's open Houdini with addon)
        # ═══════════════════════════════════════════════════════════════
        if self.houdini_gui_server and self.houdini_gui_server.is_alive():
            logger.info(f"[GUI] Using Houdini GUI server for operation: {op_type}")

            try:
                return await self._execute_houdini_via_persistent_server(
                    operation_id, op_type, params, output_dir, progress_callback, start_time,
                    server=self.houdini_gui_server
                )
            except Exception as e:
                logger.warning(f"[GUI] Houdini GUI server failed: {e}, trying headless...")

        # ═══════════════════════════════════════════════════════════════
        # PRIORITY 2: HEADLESS PERSISTENT SERVER (150-200x faster)
        # ═══════════════════════════════════════════════════════════════
        if self.houdini_server and self.houdini_server.is_alive():
            logger.info(f"🎯 Using Houdini persistent server for operation: {op_type}")

            try:
                return await self._execute_houdini_via_persistent_server(
                    operation_id, op_type, params, output_dir, progress_callback, start_time,
                    server=self.houdini_server
                )
            except Exception as e:
                logger.warning(f"⚠️  Persistent server failed: {e}")
                logger.info("   Falling back to subprocess approach")

        # ═══════════════════════════════════════════════════════════════
        # PRIORITY 3: SUBPROCESS FALLBACK
        # ═══════════════════════════════════════════════════════════════
        if self.houdini_server or self.houdini_gui_server:
            logger.debug("Using subprocess fallback (persistent/GUI servers unavailable)")
        else:
            logger.debug("Using subprocess approach (no persistent servers initialized)")

        # Get Houdini Python executable
        hython_path = self.discovery.get_executable_path("houdini", "python")
        if not hython_path:
            raise RuntimeError("Houdini Python (hython) executable not found")

        if progress_callback:
            await progress_callback(20, "Preparing Houdini script")

        # Create Houdini script
        script_content = self._generate_houdini_script(op_type, params, output_dir)
        script_path = os.path.join(self.temp_directories[operation_id], "houdini_script.py")

        with open(script_path, 'w', encoding='utf-8') as f:
            f.write(script_content)

        if progress_callback:
            await progress_callback(30, "Executing Houdini script")

        # Execute Houdini script
        cmd = [hython_path, script_path]
        logger.info(f"🔶 Running Houdini command: {' '.join(cmd)}")

        try:
            process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                cwd=self.temp_directories[operation_id]
            )

            self.running_processes[operation_id] = process

            # Monitor progress
            output_lines = []
            error_lines = []

            while process.poll() is None:
                if progress_callback:
                    await progress_callback(50, "Houdini processing...")

                output = process.stdout.readline()
                if output:
                    output_lines.append(output.strip())
                    logger.debug(f"Houdini output: {output.strip()}")

                await asyncio.sleep(0.1)

            # Get remaining output
            remaining_out, remaining_err = process.communicate()
            if remaining_out:
                output_lines.extend(remaining_out.split('\n'))
            if remaining_err:
                error_lines.extend(remaining_err.split('\n'))

            if progress_callback:
                await progress_callback(80, "Collecting Houdini results")

            if process.returncode != 0:
                error_msg = f"Houdini process failed with return code {process.returncode}"
                if error_lines:
                    error_msg += f": {' '.join(error_lines)}"
                raise RuntimeError(error_msg)

            # Collect output files
            output_files = self._collect_output_files(output_dir)

            # Build metadata
            metadata = {
                "dcc": "houdini",
                "operation_type": op_type,
                "return_code": process.returncode
            }

            # Add session info for session operations
            if op_type == "session":
                session_id = params.get("session_id", "default")
                metadata.update({
                    'session_id': session_id,
                    'session_status': 'ready',
                    'houdini_version': '21.0'  # Will be extracted from output later
                })

            return OperationResult(
                operation_id=operation_id,
                success=True,
                output_files=output_files,
                logs=output_lines,
                execution_time=time.time() - start_time,
                metadata=metadata
            )

        except subprocess.TimeoutExpired:
            process.kill()
            raise RuntimeError(f"Houdini operation timed out after {operation.timeout} seconds")

    async def _execute_houdini_via_persistent_server(
        self,
        operation_id: str,
        op_type: str,
        params: Dict,
        output_dir: str,
        progress_callback,
        start_time: float,
        server=None
    ) -> OperationResult:
        """
        Execute Houdini operation via persistent or GUI server.

        This method generates Houdini Python code and sends it to the active
        server (GUI or headless), avoiding the 15-30s Houdini initialization overhead.

        Args:
            server: Optional server instance to use. If None, uses self.houdini_server.
        """
        active_server = server or self.houdini_server
        is_gui = isinstance(active_server, GUIServerManager)
        server_label = "Houdini GUI" if is_gui else "Houdini persistent"
        if progress_callback:
            await progress_callback(20, f"Sending operation to {server_label} server")

        # Generate Houdini command from operation type
        houdini_command = self._generate_houdini_command_for_persistent_server(op_type, params, output_dir)

        if progress_callback:
            await progress_callback(40, "Executing Houdini command")

        # Execute via active server (GUI or headless)
        result = await asyncio.get_event_loop().run_in_executor(
            None,
            lambda: active_server.execute_command(houdini_command, op_type, timeout=60)
        )

        if progress_callback:
            await progress_callback(80, "Processing results")

        execution_time = time.time() - start_time

        # Build operation result
        if result.get('success'):
            logger.info(f"✅ Houdini operation '{op_type}' completed in {execution_time*1000:.1f}ms")

            metadata = {
                'dcc': 'houdini',
                'operation_type': op_type,
                'via_persistent_server': True,
                'via_gui_server': is_gui,
                'server_execution_time': result.get('execution_time', 0),
                'total_time': result.get('total_time', 0),
                'dcc_result': result.get('result'),
                'console_output': result.get('output', ''),
            }

            # CRITICAL: Always pass through session_id if present in parameters
            # This ensures session_id flows through Command → Export nodes
            if 'session_id' in params:
                metadata['session_id'] = params['session_id']

            # Handle session operations
            if op_type == "session":
                session_id = params.get("session_id", "default")
                houdini_version = result.get('result', {}).get('houdini_version', '20.5')
                metadata.update({
                    'session_id': session_id,
                    'session_status': 'ready',
                    'houdini_version': houdini_version
                })

            # Handle export operations - collect output files
            output_files = []
            if op_type == "export":
                export_path = params.get("export_path") or params.get("output_file", "")
                if export_path and os.path.exists(export_path):
                    output_files.append(export_path)

            if progress_callback:
                await progress_callback(100, "Operation complete")

            return OperationResult(
                operation_id=operation_id,
                success=True,
                output_files=output_files,
                logs=[f"Executed via {server_label} server in {execution_time*1000:.1f}ms"],
                error_message=None,
                execution_time=execution_time,
                metadata=metadata
            )
        else:
            error_msg = result.get('error', 'Unknown error')
            logger.error(f"❌ Houdini operation '{op_type}' failed: {error_msg}")

            return OperationResult(
                operation_id=operation_id,
                success=False,
                output_files=[],
                logs=[],
                error_message=error_msg,
                execution_time=execution_time,
                metadata={
                    'dcc': 'houdini',
                    'operation_type': op_type,
                    'via_persistent_server': True,
                    'via_gui_server': is_gui,
                    'dcc_result': result.get('result'),
                    'console_output': result.get('output', ''),
                }
            )

    def _generate_houdini_command_for_persistent_server(
        self,
        op_type: str,
        params: Dict,
        output_dir: str
    ) -> str:
        """
        Generate Houdini Python command for persistent server execution.

        This converts operation types to executable Houdini Python code.
        """
        if op_type == "session":
            # Session creation is instant with persistent server (Houdini already running)
            session_id = params.get("session_id", "default")
            return f"""
# Session creation - instant with persistent server
import hou
session_id = "{session_id}"
houdini_version = '.'.join(map(str, hou.applicationVersion()[:2]))
result = {{
    'session_id': session_id,
    'status': 'ready',
    'houdini_version': houdini_version
}}
print(f"Session {{session_id}} ready (Houdini {{houdini_version}})")
"""

        elif op_type == "create_sphere":
            name = params.get("name", "sphere")
            radius = params.get("radius", 1.0)
            return f"""
import hou
obj = hou.node('/obj')
geo = obj.createNode('geo', '{name}')
sphere = geo.createNode('sphere')
sphere.parm('rad').set({radius})
result = {{"node_path": geo.path(), "name": "{name}", "type": "sphere", "radius": {radius}}}
print(f"Created sphere: {{geo.path()}}")
"""

        elif op_type == "create_box":
            name = params.get("name", "box")
            size = params.get("size", 1.0)
            return f"""
import hou
obj = hou.node('/obj')
geo = obj.createNode('geo', '{name}')
box = geo.createNode('box')
box.parm('sizex').set({size})
box.parm('sizey').set({size})
box.parm('sizez').set({size})
result = {{"node_path": geo.path(), "name": "{name}", "type": "box", "size": {size}}}
print(f"Created box: {{geo.path()}}")
"""

        elif op_type == "create_grid":
            name = params.get("name", "grid")
            size = params.get("size", 1.0)
            return f"""
import hou
obj = hou.node('/obj')
geo = obj.createNode('geo', '{name}')
grid = geo.createNode('grid')
grid.parm('sizex').set({size})
grid.parm('sizey').set({size})
result = {{"node_path": geo.path(), "name": "{name}", "type": "grid", "size": {size}}}
print(f"Created grid: {{geo.path()}}")
"""

        elif op_type == "command":
            # Direct Houdini command execution
            command = params.get("command", "")
            return f"""
import hou
{command}
if result is None:
    result = {{"status": "completed", "message": "Command executed"}}
"""

        elif op_type == "export":
            # Export Houdini scene
            export_path = params.get("export_path") or params.get("output_file", "")
            if export_path:
                # Convert to forward slashes for Houdini
                export_path = export_path.replace('\\\\', '/')
                return f"""
import hou
import os
export_path = r"{export_path}"
# Ensure directory exists
export_dir = os.path.dirname(export_path)
if export_dir:
    os.makedirs(export_dir, exist_ok=True)
hou.hipFile.save(export_path)
result = {{"export_path": export_path, "format": "hip", "status": "completed"}}
print(f"Exported scene to: {{export_path}}")
"""
            else:
                # Default export path
                return f"""
import hou
import os
export_path = os.path.join(r"{output_dir}", "exported_scene.hip")
export_dir = os.path.dirname(export_path)
if export_dir:
    os.makedirs(export_dir, exist_ok=True)
hou.hipFile.save(export_path)
result = {{"export_path": export_path, "format": "hip", "status": "completed"}}
print(f"Exported scene to: {{export_path}}")
"""

        else:
            # Generic operation - try to execute as Houdini Python code
            return f"""
import hou
# Generic operation: {op_type}
result = {{"status": "completed", "operation": "{op_type}"}}
print(f"Operation {op_type} executed")
"""

    async def _execute_unreal_operation(self, operation, progress_callback=None) -> OperationResult:
        """Execute Unreal Engine operation."""
        start_time = time.time()
        try:
            operation_id = str(operation.operation_id) if hasattr(operation, 'operation_id') else 'unknown'
            op_type = str(operation.operation_type) if hasattr(operation, 'operation_type') else 'unknown'
            params = operation.parameters if hasattr(operation, 'parameters') else {}
            output_dir = self.temp_directories.get(operation_id, '/tmp')

            if not isinstance(params, dict):
                logger.warning(f"Unreal parameters is not a dict, got {type(params)}: {params}")
                params = {}
        except Exception as e:
            logger.error(f"Error accessing Unreal operation attributes: {e}")
            raise RuntimeError(f"Failed to process Unreal operation data: {e}")

        # ═══════════════════════════════════════════════════════════════
        # PRIORITY 1: GUI SERVER (user's open Unreal Editor with Remote Execution)
        # ═══════════════════════════════════════════════════════════════
        if self.unreal_gui_server and self.unreal_gui_server.is_alive():
            logger.info(f"[GUI] Using Unreal GUI server for operation: {op_type}")

            try:
                return await self._execute_unreal_via_persistent_server(
                    operation_id, op_type, params, output_dir, progress_callback, start_time,
                    server=self.unreal_gui_server
                )
            except Exception as e:
                logger.warning(f"[GUI] Unreal GUI server failed: {e}, trying headless...")

        # ═══════════════════════════════════════════════════════════════
        # PRIORITY 2: HEADLESS PERSISTENT SERVER
        # ═══════════════════════════════════════════════════════════════
        if self.unreal_server and self.unreal_server.is_alive():
            logger.info(f"🎯 Using Unreal persistent server for operation: {op_type}")

            try:
                return await self._execute_unreal_via_persistent_server(
                    operation_id, op_type, params, output_dir, progress_callback, start_time,
                    server=self.unreal_server
                )
            except Exception as e:
                logger.error(f"❌ Unreal persistent server failed: {e}")
                raise RuntimeError(f"Unreal operation failed: {e}. No fallback available (Unreal has no standalone Python).")

        raise RuntimeError(
            "Unreal Engine not available. No GUI server (Remote Execution) or headless server detected. "
            "Ensure Unreal Engine is installed and either the persistent server started or "
            "the Editor is running with Remote Execution enabled."
        )

    async def _execute_unreal_via_persistent_server(
        self,
        operation_id: str,
        op_type: str,
        params: Dict,
        output_dir: str,
        progress_callback,
        start_time: float,
        server=None
    ) -> OperationResult:
        """Execute Unreal operation via persistent server."""
        active_server = server or self.unreal_server
        server_label = "Unreal persistent"
        if progress_callback:
            await progress_callback(20, f"Sending operation to {server_label} server")

        # Generate Unreal command from operation type
        unreal_command = self._generate_unreal_command_for_persistent_server(op_type, params, output_dir)

        if progress_callback:
            await progress_callback(40, "Executing Unreal command")

        # Execute via persistent server
        result = await asyncio.get_event_loop().run_in_executor(
            None,
            lambda: active_server.execute_command(unreal_command, op_type, timeout=120)
        )

        if progress_callback:
            await progress_callback(80, "Processing results")

        execution_time = time.time() - start_time

        if result.get('success'):
            logger.info(f"✅ Unreal operation '{op_type}' completed in {execution_time*1000:.1f}ms")

            metadata = {
                'dcc': 'unreal',
                'operation_type': op_type,
                'via_persistent_server': True,
                'server_execution_time': result.get('execution_time', 0),
                'total_time': result.get('total_time', 0),
                'dcc_result': result.get('result'),
                'console_output': result.get('output', ''),
            }

            if 'session_id' in params:
                metadata['session_id'] = params['session_id']

            # Handle session operations
            if op_type == "session":
                session_id = params.get("session_id", "default")
                unreal_version = result.get('result', {}).get('unreal_version', 'unknown')
                metadata.update({
                    'session_id': session_id,
                    'session_status': 'ready',
                    'unreal_version': unreal_version
                })

            if progress_callback:
                await progress_callback(100, "Operation complete")

            return OperationResult(
                operation_id=operation_id,
                success=True,
                output_files=[],
                logs=[f"Executed via {server_label} server in {execution_time*1000:.1f}ms"],
                error_message=None,
                execution_time=execution_time,
                metadata=metadata
            )
        else:
            error_msg = result.get('error', 'Unknown error')
            logger.error(f"❌ Unreal operation '{op_type}' failed: {error_msg}")

            return OperationResult(
                operation_id=operation_id,
                success=False,
                output_files=[],
                logs=[],
                error_message=error_msg,
                execution_time=execution_time,
                metadata={
                    'dcc': 'unreal',
                    'operation_type': op_type,
                    'via_persistent_server': True,
                    'dcc_result': result.get('result'),
                    'console_output': result.get('output', ''),
                }
            )

    def _generate_unreal_command_for_persistent_server(
        self,
        op_type: str,
        params: Dict,
        output_dir: str
    ) -> str:
        """Generate Unreal Python command for persistent server execution."""
        if op_type == "session":
            session_id = params.get("session_id", "default")
            return f"""
# Session creation - instant with persistent server (Unreal already running)
try:
    engine_version = str(unreal.SystemLibrary.get_engine_version())
except:
    engine_version = "unknown"
session_id = "{session_id}"
result = {{
    'session_id': session_id,
    'status': 'ready',
    'unreal_version': engine_version
}}
print(f"Session {{session_id}} ready (Unreal Engine {{engine_version}})")
"""

        elif op_type == "command":
            command = params.get("command", "")
            return f"""
{command}
if result is None:
    result = {{"status": "completed", "message": "Command executed"}}
"""

        elif op_type == "session_close":
            session_id = params.get("session_id", "default")
            return f"""
# Session close - cleanup
session_id = "{session_id}"
result = {{
    'session_id': session_id,
    'closed': True,
    'status': 'closed'
}}
print(f"Session {{session_id}} closed")
"""

        else:
            return f"""
# Generic Unreal operation: {op_type}
result = {{"status": "completed", "operation": "{op_type}"}}
print(f"Operation {op_type} executed")
"""

    async def _execute_nuke_operation(self, operation, progress_callback=None) -> OperationResult:
        """Execute Nuke operation using command-line Python mode."""
        operation_id = operation.operation_id
        op_type = operation.operation_type
        params = operation.params

        if progress_callback:
            await progress_callback(20, "Preparing Nuke script")

        # Get Nuke executable
        nuke_info = self.discovery.discovered_dccs.get('nuke', {})
        if not nuke_info.get('available'):
            raise RuntimeError("Nuke not found on system")

        nuke_executable = nuke_info['executable']
        output_dir = self.temp_directories[operation_id]

        try:
            # Generate Nuke Python script
            script_content = self._generate_nuke_script(op_type, params, output_dir)
            script_path = os.path.join(output_dir, f"nuke_operation_{operation_id}.py")

            with open(script_path, 'w') as f:
                f.write(script_content)

            if progress_callback:
                await progress_callback(40, "Executing Nuke operation")

            # Execute Nuke in headless Python mode
            # Using -t flag for Python interpreter mode
            command = [nuke_executable, "-t", script_path]

            logger.info(f"🎬 Executing Nuke command: {' '.join(command)}")

            process_manager = get_process_manager()
            result = process_manager.run_command(
                command,
                cwd=output_dir,
                timeout=operation.timeout,
                capture_output=True
            )

            if progress_callback:
                await progress_callback(80, "Processing Nuke results")

            # Process results
            if result.returncode == 0:
                logger.info(f"✅ Nuke operation completed successfully")
                output_files = []

                # Scan output directory for generated files
                for file in os.listdir(output_dir):
                    if file.endswith(('.exr', '.png', '.jpg', '.tiff', '.mov', '.mp4')):
                        output_files.append(os.path.join(output_dir, file))

                return OperationResult(
                    operation_id=operation_id,
                    success=True,
                    output_files=output_files,
                    logs=[result.stdout] if result.stdout else [],
                    execution_time=0.0,
                    metadata={
                        "dcc": "nuke",
                        "operation_type": op_type,
                        "return_code": result.returncode
                    }
                )
            else:
                logger.error(f"❌ Nuke operation failed with return code {result.returncode}")
                logger.error(f"Error output: {result.stderr}")

                return OperationResult(
                    operation_id=operation_id,
                    success=False,
                    output_files=[],
                    logs=[result.stderr] if result.stderr else [],
                    error_message=f"Nuke execution failed: {result.stderr}",
                    execution_time=0.0,
                    metadata={
                        "dcc": "nuke",
                        "operation_type": op_type,
                        "return_code": result.returncode
                    }
                )

        except subprocess.TimeoutExpired:
            process.kill()
            raise RuntimeError(f"Nuke operation timed out after {operation.timeout} seconds")

    async def _execute_natron_operation(self, operation, progress_callback=None) -> OperationResult:
        """Execute Natron operation using NatronRenderer for batch processing."""
        operation_id = operation.operation_id
        op_type = operation.operation_type
        params = operation.params

        if progress_callback:
            await progress_callback(20, "Preparing Natron script")

        # Get Natron executable (prefer NatronRenderer for command-line)
        natron_info = self.discovery.discovered_dccs.get('natron', {})
        if not natron_info.get('available'):
            raise RuntimeError("Natron not found on system")

        natron_executable = natron_info['executable']
        output_dir = self.temp_directories[operation_id]

        try:
            # Generate Natron Python script
            script_content = self._generate_natron_script(op_type, params, output_dir)
            script_path = os.path.join(output_dir, f"natron_operation_{operation_id}.py")

            with open(script_path, 'w') as f:
                f.write(script_content)

            if progress_callback:
                await progress_callback(40, "Executing Natron operation")

            # Execute Natron in batch mode
            # Using -b flag for batch processing and -t for Python interpreter
            command = [natron_executable, "-b", "-t", script_path]

            logger.info(f"🎨 Executing Natron command: {' '.join(command)}")

            process_manager = get_process_manager()
            result = process_manager.run_command(
                command,
                cwd=output_dir,
                timeout=operation.timeout,
                capture_output=True
            )

            if progress_callback:
                await progress_callback(80, "Processing Natron results")

            # Process results
            if result.returncode == 0:
                logger.info(f"✅ Natron operation completed successfully")
                output_files = []

                # Scan output directory for generated files
                for file in os.listdir(output_dir):
                    if file.endswith(('.exr', '.png', '.jpg', '.tiff', '.mov', '.mp4')):
                        output_files.append(os.path.join(output_dir, file))

                return OperationResult(
                    operation_id=operation_id,
                    success=True,
                    output_files=output_files,
                    logs=[result.stdout] if result.stdout else [],
                    execution_time=0.0,
                    metadata={
                        "dcc": "natron",
                        "operation_type": op_type,
                        "return_code": result.returncode
                    }
                )
            else:
                logger.error(f"❌ Natron operation failed with return code {result.returncode}")
                logger.error(f"Error output: {result.stderr}")

                return OperationResult(
                    operation_id=operation_id,
                    success=False,
                    output_files=[],
                    logs=[result.stderr] if result.stderr else [],
                    error_message=f"Natron execution failed: {result.stderr}",
                    execution_time=0.0,
                    metadata={
                        "dcc": "natron",
                        "operation_type": op_type,
                        "return_code": result.returncode
                    }
                )

        except subprocess.TimeoutExpired:
            process.kill()
            raise RuntimeError(f"Natron operation timed out after {operation.timeout} seconds")

    async def _execute_vectorworks_operation(self, operation, progress_callback=None) -> OperationResult:
        """Execute Vectorworks operation using Python API integration."""
        operation_id = operation.operation_id
        op_type = operation.operation_type
        params = operation.params

        if progress_callback:
            await progress_callback(20, "Preparing Vectorworks script")

        # Get Vectorworks executable
        vectorworks_info = self.discovery.discovered_dccs.get('vectorworks', {})
        if not vectorworks_info.get('available'):
            raise RuntimeError("Vectorworks not found on system")

        vectorworks_executable = vectorworks_info['executable']
        output_dir = self.temp_directories[operation_id]

        try:
            # Generate Vectorworks Python script
            script_content = self._generate_vectorworks_script(op_type, params, output_dir)
            script_path = os.path.join(output_dir, f"vectorworks_operation_{operation_id}.py")

            with open(script_path, 'w') as f:
                f.write(script_content)

            if progress_callback:
                await progress_callback(40, "Executing Vectorworks operation")

            # Execute Vectorworks with Python script
            # Note: Vectorworks Python integration may require different approaches
            # depending on version and installation type
            command = [vectorworks_executable, "-script", script_path]

            logger.info(f"🏗️ Executing Vectorworks command: {' '.join(command)}")

            process_manager = get_process_manager()
            result = process_manager.run_command(
                command,
                cwd=output_dir,
                timeout=operation.timeout,
                capture_output=True
            )

            if progress_callback:
                await progress_callback(80, "Processing Vectorworks results")

            # Process results
            if result.returncode == 0:
                logger.info(f"✅ Vectorworks operation completed successfully")
                output_files = []

                # Scan output directory for generated files
                for file in os.listdir(output_dir):
                    if file.endswith(('.dwg', '.dxf', '.vwx', '.pdf', '.png', '.jpg')):
                        output_files.append(os.path.join(output_dir, file))

                return OperationResult(
                    operation_id=operation_id,
                    success=True,
                    output_files=output_files,
                    logs=[result.stdout] if result.stdout else [],
                    execution_time=0.0,
                    metadata={
                        "dcc": "vectorworks",
                        "operation_type": op_type,
                        "return_code": result.returncode
                    }
                )
            else:
                logger.error(f"❌ Vectorworks operation failed with return code {result.returncode}")
                logger.error(f"Error output: {result.stderr}")

                return OperationResult(
                    operation_id=operation_id,
                    success=False,
                    output_files=[],
                    logs=[result.stderr] if result.stderr else [],
                    error_message=f"Vectorworks execution failed: {result.stderr}",
                    execution_time=0.0,
                    metadata={
                        "dcc": "vectorworks",
                        "operation_type": op_type,
                        "return_code": result.returncode
                    }
                )

        except subprocess.TimeoutExpired:
            process.kill()
            raise RuntimeError(f"Vectorworks operation timed out after {operation.timeout} seconds")

    def _generate_maya_script(self, operation_type: str, params: Dict, output_dir: str, execution_mode: str = "mayapy") -> str:
        """Generate Maya Python script for operation."""
        # Use cross-platform path handling
        # Use Windows path with forward slashes (Maya on Windows accepts both)
        output_dir_maya = str(output_dir).replace('\\', '/')

        # Check for export path in both 'output_file' and 'export_path' parameters
        # Backend delegation maps 'export_path' → 'output_file'
        export_path_param = params.get("output_file") or params.get("export_path", "")
        if export_path_param:
            # Maya accepts forward slashes on Windows, avoiding unicode escape errors
            export_path_param = str(export_path_param).replace('\\', '/')
            # Update params with both names for compatibility
            params = {**params, "export_path": export_path_param, "output_file": export_path_param}

        # Generate different initialization based on execution mode
        if execution_mode == "mayapy_simple":
            # Simplified mayapy mode for sessions - NO Qt overhead, headless only
            base_script = f'''
import os
import sys
import time
from datetime import datetime

def log_timestamp(message):
    """Log message with precise timestamp for profiling."""
    timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
    print(f"[{{timestamp}}] {{message}}")

log_timestamp("PROFILING START: Script execution began")

# Simple Maya standalone initialization (no Qt/GUI)
# CRITICAL: Set environment variables BEFORE importing Maya to disable problematic plugins
import os
os.environ["MAYA_DISABLE_CLIC_IPM"] = "1"
os.environ["MAYA_DISABLE_CIP"] = "1"
os.environ["MAYA_SKIP_USERSETUP_PY"] = "1"  # Skip userSetup.py that may load plugins
os.environ["MAYA_PLUG_IN_PATH"] = ""  # Disable auto-loading plugins
os.environ["MAYA_MODULE_PATH"] = ""  # Disable loading modules (including Bifrost)
log_timestamp("STEP 0: Environment variables set to disable problematic plugins")

log_timestamp("STEP 1: About to import maya.standalone")
import maya.standalone
log_timestamp("STEP 2: maya.standalone imported successfully")

log_timestamp("STEP 3: About to call maya.standalone.initialize()")
start_init = time.time()
maya.standalone.initialize(name='python')
init_duration = time.time() - start_init
log_timestamp(f"STEP 4: maya.standalone.initialize() completed in {{init_duration:.2f}}s")

log_timestamp("STEP 5: About to import maya.cmds")
import maya.cmds as cmds
log_timestamp("STEP 6: maya.cmds imported successfully")

maya_version = cmds.about(version=True)
log_timestamp(f"STEP 7: Maya {{maya_version}} fully initialized")

output_dir = "{output_dir_maya}"
log_timestamp(f"STEP 8: Starting Maya operation: {operation_type}")

try:
'''
        elif execution_mode == "batch":
            # For batch mode: Maya is already running, skip Qt setup and standalone initialization
            base_script = f'''
import os
import sys
import time
from datetime import datetime
output_dir = "{output_dir_maya}"

def log_timestamp(message):
    """Log message with precise timestamp for profiling."""
    timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
    print(f"[{{timestamp}}] {{message}}")

# Batch mode: Maya is already running and initialized
log_timestamp("Running in Maya batch mode - Maya already initialized")
import maya.cmds as cmds
maya_already_initialized = True

print(f"Maya operation starting: {operation_type}")

try:
'''
        else:
            # For mayapy mode: Full Qt setup and standalone initialization with DLL fixes
            base_script = f'''
import os
import sys
import platform

# CRITICAL Maya 2026 DLL Environment Setup - MUST BE FIRST
def setup_maya_environment():
    """Complete Maya environment setup for DLL loading."""
    maya_location = r"C:\\Program Files\\Autodesk\\Maya2026"
    maya_bin = os.path.join(maya_location, "bin")
    maya_python = os.path.join(maya_location, "Python")

    # Critical environment variables for DLL loading
    maya_env = {{
        "MAYA_LOCATION": maya_location,
        "MAYA_APP_DIR": os.path.join(os.environ.get("USERPROFILE", ""), "Documents", "maya"),
        "MAYA_DISABLE_CIP": "1",
        "MAYA_DISABLE_CLIC_IPM": "1",
        "MAYA_DISABLE_ADLMPIT_POPUP": "1",
        "MAYA_DISABLE_ADP": "1",
        "MAYA_CM_DISABLE_ERROR_POPUPS": "1",
        "MAYA_NO_CONSOLE_WINDOW": "1",
        "MAYA_DEBUG_NO_DIALOGS": "1",

        # Qt WebEngine fixes for Maya 2026
        "QT_QPA_PLATFORM_PLUGIN_PATH": "",
        "QT_WEBENGINE_DISABLE_SANDBOX": "1",
        "QTWEBENGINE_DISABLE_SANDBOX": "1",
        "QT_LOGGING_RULES": "qt.webenginecontext.debug=false",
        "QT_WEBENGINE_CHROMIUM_FLAGS": "--disable-web-security --disable-features=VizDisplayCompositor",
        "QTWEBENGINE_CHROMIUM_FLAGS": "--disable-web-security --disable-gpu"
    }}

    # Apply environment variables
    for key, value in maya_env.items():
        os.environ[key] = value

    # Update PATH to include Maya bin directory FIRST
    current_path = os.environ.get("PATH", "")
    if maya_bin not in current_path:
        os.environ["PATH"] = maya_bin + os.pathsep + current_path

    # Update PYTHONPATH for Maya Python modules
    maya_python_paths = [
        os.path.join(maya_python, "Lib", "site-packages"),
        os.path.join(maya_location, "devkit", "other", "python", "2.7", "lib"),
        maya_bin
    ]

    current_pythonpath = os.environ.get("PYTHONPATH", "")
    for path in maya_python_paths:
        if os.path.exists(path) and path not in current_pythonpath:
            if current_pythonpath:
                current_pythonpath += os.pathsep + path
            else:
                current_pythonpath = path
            # Add to sys.path for immediate use
            if path not in sys.path:
                sys.path.insert(0, path)

    os.environ["PYTHONPATH"] = current_pythonpath
    print("Maya DLL environment setup completed")

# Setup Maya environment BEFORE any imports
setup_maya_environment()

# Critical Qt Application Context Setup for Maya 2026
try:
    # Import Qt modules BEFORE Maya to set proper attributes
    try:
        from PySide6 import QtCore, QtWidgets
        from PySide6.QtCore import Qt

        # Set critical Qt attributes before any QApplication creation
        QtCore.QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts, True)
        QtCore.QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL, True)

        # Additional Qt WebEngine fixes for Maya 2026
        try:
            from PySide6.QtQuick import QSGRendererInterface, QQuickWindow
            QQuickWindow.setGraphicsApi(QSGRendererInterface.OpenGLRhi)
        except ImportError:
            pass  # Not available in all Qt versions

        print("Qt attributes set successfully for Maya 2026")

    except ImportError:
        # Fallback for older Maya versions or missing PySide6
        try:
            from PySide2 import QtCore, QtWidgets
            from PySide2.QtCore import Qt

            QtCore.QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts, True)
            QtCore.QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL, True)
            print("Qt attributes set with PySide2 fallback")

        except ImportError:
            print("Warning: Could not import Qt modules, proceeding without Qt fixes")

    # Check if we're running inside Maya GUI
    import maya.standalone

    # First check: Try to access Maya's application state
    maya_already_initialized = False
    try:
        import maya.cmds as test_cmds
        # If maya.cmds imports successfully, Maya might already be running
        try:
            test_cmds.about(version=True)
            print("Maya is already initialized (running inside Maya GUI)")
            maya_already_initialized = True
        except RuntimeError:
            # Maya commands not available, try standalone initialization
            print("Maya not initialized, starting standalone mode")
            maya.standalone.initialize()
            maya_already_initialized = False
    except ImportError:
        # Maya not available at all, try standalone initialization
        print("Maya not available, attempting standalone initialization")
        try:
            maya.standalone.initialize()
            maya_already_initialized = False
        except RuntimeError as e:
            if "can not be call inside of Maya" in str(e):
                print("Maya GUI detected - skipping standalone initialization")
                maya_already_initialized = True
            else:
                raise e

except Exception as qt_error:
    print("Qt setup failed: " + str(qt_error) + ", falling back to basic Maya initialization")
    import maya.standalone

    # Fallback Maya initialization logic
    maya_already_initialized = False
    try:
        import maya.cmds as test_cmds
        try:
            test_cmds.about(version=True)
            print("Maya is already initialized (running inside Maya GUI)")
            maya_already_initialized = True
        except RuntimeError:
            print("Maya not initialized, starting standalone mode")
            maya.standalone.initialize()
            maya_already_initialized = False
    except ImportError:
        try:
            maya.standalone.initialize()
            maya_already_initialized = False
        except RuntimeError as e:
            if "can not be call inside of Maya" in str(e):
                print("Maya GUI detected - skipping standalone initialization")
                maya_already_initialized = True
            else:
                raise e

import maya.cmds as cmds

print(f"Maya operation starting: {operation_type}")

try:
'''

        if operation_type == "render":
            script_content = base_script + f'''
    # Set render settings
    cmds.setAttr("defaultRenderGlobals.imageFormat", {params.get("image_format", 8)})  # PNG
    cmds.setAttr("defaultRenderGlobals.startFrame", {params.get("start_frame", 1)})
    cmds.setAttr("defaultRenderGlobals.endFrame", {params.get("end_frame", 1)})

    # Set output directory
    output_path = r"{output_dir_maya}"
    os.makedirs(output_path, exist_ok=True)
    cmds.setAttr("defaultRenderGlobals.imageFilePrefix", f"{output_path}/render", type="string")

    # Render
    cmds.render()
    print("Render completed successfully")

except Exception as e:
    print("Maya operation failed: " + str(e))
    sys.exit(1)
finally:
    # Only uninitialize if we initialized Maya ourselves (not running inside Maya GUI)
    if not maya_already_initialized:
        maya.standalone.uninitialize()
        print("Maya uninitialized")
    else:
        print("Maya left running (inside Maya GUI)")
'''
        elif operation_type == "session":
            # Create persistent Maya session with command queue
            session_id = params.get("session_id", "default_session")
            script_content = base_script + f'''
    # Session configuration
    log_timestamp("STEP 9: Creating session configuration")
    session_id = "{session_id}"
    output_path = r"{output_dir_maya}"
    log_timestamp(f"STEP 10: Output path = {{output_path}}")

    os.makedirs(output_path, exist_ok=True)
    log_timestamp("STEP 11: Directory created")

    # Session communication files
    command_queue_file = os.path.join(output_path, f"maya_session_{{session_id}}_queue.txt")
    response_queue_file = os.path.join(output_path, f"maya_session_{{session_id}}_response.txt")
    status_file = os.path.join(output_path, f"maya_session_{{session_id}}_status.txt")
    session_info_file = os.path.join(output_path, f"maya_session_{{session_id}}_info.txt")
    log_timestamp("STEP 12: File paths configured")

    # Initialize communication files
    log_timestamp("STEP 13: Creating empty queue files")
    open(command_queue_file, 'w').close()
    open(response_queue_file, 'w').close()
    log_timestamp("STEP 14: Queue files created")

    # Write session status
    log_timestamp("STEP 15: Writing status file")
    with open(status_file, 'w') as f:
        f.write('listening')
    log_timestamp("STEP 16: Status file written")

    # Write session info (for client to retrieve session_id)
    log_timestamp("STEP 17: Writing session info file")
    import json
    session_data = {{
        "session_id": session_id,
        "status": "listening",
        "command_queue": command_queue_file,
        "response_queue": response_queue_file,
        "maya_version": cmds.about(version=True)
    }}
    with open(session_info_file, 'w') as f:
        json.dump(session_data, f, indent=2)
    log_timestamp("STEP 18: Session info file written")

    print(f"Maya session {{session_id}} ready and listening")
    print(f"Command queue: {{command_queue_file}}")
    print(f"Session info: {{session_info_file}}")
    log_timestamp("STEP 19: Session files confirmed")

    # Keep Maya alive and listen for commands
    command_timeout = 300  # 5 minutes idle timeout
    last_command_time = time.time()

    while True:
        # Check for timeout (no commands for 5 minutes)
        if time.time() - last_command_time > command_timeout:
            print(f"Session {{session_id}} timeout after {{command_timeout}}s idle")
            with open(status_file, 'w') as f:
                f.write('timeout')
            break

        # Check for commands
        if os.path.exists(command_queue_file) and os.path.getsize(command_queue_file) > 0:
            with open(command_queue_file, 'r') as f:
                command = f.read().strip()

            if command:
                last_command_time = time.time()  # Reset timeout

                if command == "CLOSE_SESSION":
                    print("Closing Maya session")
                    with open(status_file, 'w') as f:
                        f.write('closed')
                    break
                else:
                    # Execute command
                    print(f"Executing command: {{command[:100]}}...")  # Log first 100 chars
                    with open(status_file, 'w') as f:
                        f.write('busy')

                    try:
                        # Create safe execution context
                        exec_globals = {{'cmds': cmds, 'maya': __import__('maya')}}
                        exec(command, exec_globals)

                        # Write success response
                        with open(response_queue_file, 'w') as f:
                            f.write('SUCCESS')
                        print("Command executed successfully")

                    except Exception as e:
                        error_msg = f'ERROR: {{str(e)}}'
                        with open(response_queue_file, 'w') as f:
                            f.write(error_msg)
                        print(f"Command failed: {{e}}")

                    # Clear command queue and return to listening
                    open(command_queue_file, 'w').close()
                    with open(status_file, 'w') as f:
                        f.write('listening')

        time.sleep(0.1)  # Poll every 100ms

    print(f"Maya session {{session_id}} ended")

except Exception as e:
    print("Maya session failed: " + str(e))
    import traceback
    traceback.print_exc()
    sys.exit(1)
finally:
    # Only uninitialize if we initialized Maya ourselves
    if not maya_already_initialized:
        maya.standalone.uninitialize()
        print("Maya uninitialized")
    else:
        print("Maya left running (inside Maya GUI)")
'''
        elif operation_type == "command":
            # Send command to existing Maya session
            session_id = params.get("session_id", "default_session")
            command = params.get("command", "")

            # This operation type should NOT launch Maya - it communicates with existing session via files
            # We'll handle this at the executor level, not in generated script
            raise RuntimeError(
                f"'command' operation type should be handled by session communication, "
                f"not by launching new Maya process. Session ID: {session_id}"
            )

        elif operation_type == "session_close":
            # Close existing Maya session
            session_id = params.get("session_id", "default_session")

            # This operation type should NOT launch Maya - it sends close signal to existing session
            # We'll handle this at the executor level, not in generated script
            raise RuntimeError(
                f"'session_close' operation type should be handled by session communication, "
                f"not by launching new Maya process. Session ID: {session_id}"
            )

        else:
            # Default script for other operations
            # Convert params dict to string to avoid f-string nesting issues
            params_str = str(params)
            script_content = base_script + f'''
    print("Executing Maya operation: {operation_type}")
    print("Parameters: {params_str}")
    print("Output directory: {output_dir_maya}")

    # Create output directory
    output_path = r"{output_dir_maya}"
    os.makedirs(output_path, exist_ok=True)

    # Basic scene operations
    if "{operation_type}" == "export":
        # Export scene - use user-specified path if provided
        user_export_path = '{params.get("export_path", "")}'
        if user_export_path:
            export_path = user_export_path
        else:
            export_path = os.path.join(output_path, "exported_scene.ma")

        # Ensure directory exists
        export_dir = os.path.dirname(export_path)
        if export_dir:
            os.makedirs(export_dir, exist_ok=True)

        cmds.file(export_path, exportAll=True, type="mayaAscii")
        print("Scene exported to: " + export_path)

    print("Maya operation completed successfully")

except Exception as e:
    print("Maya operation failed: " + str(e))
    sys.exit(1)
finally:
    # Only uninitialize if we initialized Maya ourselves (not running inside Maya GUI)
    if not maya_already_initialized:
        maya.standalone.uninitialize()
        print("Maya uninitialized")
    else:
        print("Maya left running (inside Maya GUI)")
'''

        return script_content

    def _generate_blender_command_for_persistent_server(
        self,
        op_type: str,
        params: Dict,
        output_dir: str
    ) -> str:
        """
        Generate Blender Python command for persistent server execution.

        This converts operation types to executable Blender Python code
        that sets a structured `result` dict (captured by the persistent server).
        """
        if op_type == "session":
            session_id = params.get("session_id", "default")
            return f"""
# Session creation - instant with persistent server
import bpy
session_id = "{session_id}"
blender_version = '.'.join(str(x) for x in bpy.app.version[:3])
result = {{
    'session_id': session_id,
    'status': 'ready',
    'blender_version': blender_version
}}
print(f"Session {{session_id}} ready (Blender {{blender_version}})")
"""

        elif op_type == "command":
            command = params.get("command", "")
            return f"""
import bpy
{command}
if result is None:
    result = {{"status": "completed", "message": "Command executed"}}
"""

        elif op_type == "render":
            file_format = params.get("file_format", "PNG")
            start_frame = params.get("start_frame", 1)
            end_frame = params.get("end_frame", 1)
            return f"""
import bpy
import os

scene = bpy.context.scene
scene.render.image_settings.file_format = '{file_format}'
output_path = r"{self._wsl_to_windows_path(output_dir)}"
os.makedirs(output_path, exist_ok=True)
scene.render.filepath = os.path.join(output_path, "render")
scene.frame_start = {start_frame}
scene.frame_end = {end_frame}

bpy.ops.render.render(write_still=True)
result = {{
    "status": "completed",
    "output_path": scene.render.filepath,
    "format": "{file_format}",
    "frames": [{start_frame}, {end_frame}]
}}
print(f"Render completed: {{scene.render.filepath}}")
"""

        elif op_type == "export":
            export_format = params.get("export_format", "fbx")
            export_path_param = params.get("export_path") or params.get("output_file", "")
            if export_path_param:
                export_path_param = self._wsl_to_windows_path(export_path_param)
                export_path_param = export_path_param.replace('\\', '/')

            return f"""
import bpy
import os

export_format = '{export_format}'
user_export_path = '{export_path_param}'

if user_export_path:
    base_path = os.path.splitext(user_export_path)[0]
    export_path = f"{{base_path}}.{{export_format}}"
else:
    output_path = r"{self._wsl_to_windows_path(output_dir)}"
    os.makedirs(output_path, exist_ok=True)
    export_path = os.path.join(output_path, f"exported_scene.{{export_format}}")

export_dir = os.path.dirname(export_path)
if export_dir:
    os.makedirs(export_dir, exist_ok=True)

if export_format == "blend":
    bpy.ops.wm.save_as_mainfile(filepath=export_path)
elif export_format == "fbx":
    bpy.ops.export_scene.fbx(filepath=export_path)
elif export_format == "obj":
    bpy.ops.wm.obj_export(filepath=export_path)
elif export_format == "dae":
    bpy.ops.wm.collada_export(filepath=export_path)
elif export_format == "ply":
    bpy.ops.wm.ply_export(filepath=export_path)
elif export_format == "stl":
    bpy.ops.wm.stl_export(filepath=export_path)
else:
    bpy.ops.wm.save_as_mainfile(filepath=export_path)

result = {{
    "export_path": export_path,
    "export_format": export_format,
    "status": "completed"
}}
print(f"Exported to: {{export_path}} (format: {{export_format}})")
"""

        elif op_type == "session_close":
            return """
import bpy
# Clear scene for next session
bpy.ops.wm.read_homefile(use_empty=True)
result = {"status": "closed", "message": "Session closed"}
print("Blender session closed")
"""

        elif op_type == "modeling":
            command = params.get("command", "")
            return f"""
import bpy
{command}
if result is None:
    result = {{"status": "completed", "operation": "modeling"}}
"""

        elif op_type == "process":
            command = params.get("command", "")
            return f"""
import bpy
{command}
if result is None:
    result = {{"status": "completed", "operation": "process"}}
"""

        elif op_type == "import":
            import_path = params.get("import_path") or params.get("file_path", "")
            import_format = params.get("import_format", "")
            if import_path:
                import_path = self._wsl_to_windows_path(import_path)
                import_path = import_path.replace('\\', '/')
            return f"""
import bpy
import os

import_path = r'{import_path}'
import_format = '{import_format}'

if not import_format:
    import_format = os.path.splitext(import_path)[1].lstrip('.').lower()

if import_format == 'fbx':
    bpy.ops.import_scene.fbx(filepath=import_path)
elif import_format == 'obj':
    bpy.ops.wm.obj_import(filepath=import_path)
elif import_format in ('gltf', 'glb'):
    bpy.ops.import_scene.gltf(filepath=import_path)
elif import_format == 'dae':
    bpy.ops.wm.collada_import(filepath=import_path)
elif import_format == 'ply':
    bpy.ops.wm.ply_import(filepath=import_path)
elif import_format == 'stl':
    bpy.ops.wm.stl_import(filepath=import_path)
else:
    print(f"Unsupported import format: {{import_format}}")

imported = [obj.name for obj in bpy.context.selected_objects]
result = {{
    "import_path": import_path,
    "import_format": import_format,
    "imported_objects": imported,
    "count": len(imported),
    "status": "completed"
}}
print(f"Imported {{len(imported)}} objects from {{import_path}}")
"""

        else:
            # Generic fallback
            return f"""
import bpy
result = {{"status": "completed", "operation": "{op_type}"}}
print(f"Operation {op_type} executed")
"""

    def _generate_blender_script(self, operation_type: str, params: Dict, output_dir: str) -> str:
        """Generate Blender Python script for operation."""
        # Convert WSL path to Windows path for Windows Blender
        windows_output_dir = self._wsl_to_windows_path(output_dir)

        # Check for export path in both 'output_file' and 'export_path' parameters
        # Backend delegation maps 'export_path' → 'output_file'
        export_path_param = params.get("output_file") or params.get("export_path", "")
        if export_path_param:
            export_path_param = self._wsl_to_windows_path(export_path_param)
            # Convert to forward slashes for Blender/Houdini (avoids unicode escape errors in generated script)
            export_path_param = export_path_param.replace('\\', '/')
            # Update params with both names for compatibility
            params = {**params, "export_path": export_path_param, "output_file": export_path_param}

        # DEBUG: Log params being used for script generation
        logger.info(f"[BLENDER SCRIPT GEN] Operation: {operation_type}")
        logger.info(f"[BLENDER SCRIPT GEN] Params received: {params}")
        logger.info(f"[BLENDER SCRIPT GEN] export_format param: {params.get('export_format', 'NOT FOUND')}")
        logger.info(f"[BLENDER SCRIPT GEN] export_path param: {params.get('export_path', 'NOT FOUND')}")

        script_content = f'''
import bpy
import os
import sys

print(f"Blender operation starting: {operation_type}")

try:
    # Clear default scene
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete(use_global=False)

    # Set output directory (converted to Windows path if needed)
    output_path = r"{windows_output_dir}"
    os.makedirs(output_path, exist_ok=True)

    if "{operation_type}" == "render":
        # Set render settings
        scene = bpy.context.scene
        scene.render.image_settings.file_format = '{params.get("file_format", "PNG")}'
        scene.render.filepath = os.path.join(output_path, "render")
        scene.frame_start = {params.get("start_frame", 1)}
        scene.frame_end = {params.get("end_frame", 1)}

        # Add basic scene
        bpy.ops.mesh.primitive_cube_add()

        # Render
        bpy.ops.render.render(write_still=True)
        print("Render completed successfully")

    elif "{operation_type}" == "export":
        # Add basic scene and export
        bpy.ops.mesh.primitive_cube_add()

        # Get export format and path
        export_format = '{params.get("export_format", "fbx")}'  # Match backend node default
        user_export_path = '{params.get("export_path", "")}'

        # DEBUG: Print what Blender received
        print(f"[BLENDER DEBUG] export_format received: {{export_format}}")
        print(f"[BLENDER DEBUG] user_export_path received: {{user_export_path}}")

        if user_export_path:
            # CRITICAL FIX: Enforce extension matches export_format
            # This ensures .fbx paths become .blend when format is "blend"
            base_path = os.path.splitext(user_export_path)[0]
            export_path = f"{{base_path}}.{{export_format}}"
            print(f"[BLENDER DEBUG] Extension enforced: {{user_export_path}} -> {{export_path}}")
        else:
            # Use default export path with appropriate extension
            export_path = os.path.join(output_path, f"exported_scene.{{export_format}}")

        print(f"[BLENDER DEBUG] Final export_path: {{export_path}}")

        # Ensure directory exists
        export_dir = os.path.dirname(export_path)
        if export_dir:
            os.makedirs(export_dir, exist_ok=True)

        # Export based on format
        if export_format == "blend":
            bpy.ops.wm.save_as_mainfile(filepath=export_path)
        elif export_format == "fbx":
            bpy.ops.export_scene.fbx(filepath=export_path)
        elif export_format == "obj":
            bpy.ops.wm.obj_export(filepath=export_path)
        elif export_format == "dae":
            bpy.ops.wm.collada_export(filepath=export_path)
        elif export_format == "ply":
            bpy.ops.wm.ply_export(filepath=export_path)
        elif export_format == "stl":
            bpy.ops.wm.stl_export(filepath=export_path)
        else:
            # Default to .blend if format unknown
            bpy.ops.wm.save_as_mainfile(filepath=export_path)

        print(f"Scene exported to: {{export_path}} (format: {{export_format}})")

    print("Blender operation completed successfully")

except Exception as e:
    print("Blender operation failed: " + str(e))
    sys.exit(1)
'''

        return script_content

    def _generate_houdini_script(self, operation_type: str, params: Dict, output_dir: str) -> str:
        """Generate Houdini Python script for operation."""
        # Convert WSL path to Windows path for Windows Houdini
        windows_output_dir = self._wsl_to_windows_path(output_dir)

        # Check for export path in both 'output_file' and 'export_path' parameters
        # Backend delegation maps 'export_path' → 'output_file'
        export_path_param = params.get("output_file") or params.get("export_path", "")
        if export_path_param:
            export_path_param = self._wsl_to_windows_path(export_path_param)
            # Convert to forward slashes for Blender/Houdini (avoids unicode escape errors in generated script)
            export_path_param = export_path_param.replace('\\', '/')
            # Update params with both names for compatibility
            params = {**params, "export_path": export_path_param, "output_file": export_path_param}

        script_content = f'''
import hou
import os
import sys

print(f"Houdini operation starting: {operation_type}")

try:
    # Initialize Houdini
    hou.hipFile.clear(suppress_save_prompt=True)

    # Set output directory (converted to Windows path if needed)
    output_path = r"{windows_output_dir}"
    os.makedirs(output_path, exist_ok=True)

    if "{operation_type}" == "render":
        # Create basic scene
        obj = hou.node("/obj")
        geo = obj.createNode("geo", "test_geo")
        box = geo.createNode("box")

        # Create render node
        out = hou.node("/out")
        mantra = out.createNode("ifd", "mantra_render")
        mantra.parm("vm_picture").set(os.path.join(output_path, "render.png"))

        # Render
        mantra.render()
        print("Render completed successfully")

    elif "{operation_type}" == "export":
        # Create basic scene and save
        obj = hou.node("/obj")
        geo = obj.createNode("geo", "test_geo")
        box = geo.createNode("box")

        # Use user-specified export path if provided, otherwise use default
        user_export_path = '{params.get("export_path", "")}'
        if user_export_path:
            export_path = user_export_path
        else:
            export_path = os.path.join(output_path, "exported_scene.hip")

        # Ensure directory exists
        export_dir = os.path.dirname(export_path)
        if export_dir:
            os.makedirs(export_dir, exist_ok=True)

        hou.hipFile.save(export_path)
        print("Scene exported to: " + export_path)

    print("Houdini operation completed successfully")

except Exception as e:
    print("Houdini operation failed: " + str(e))
    sys.exit(1)
'''

        return script_content

    def _collect_output_files(self, output_dir: str) -> List[str]:
        """Collect output files from output directory."""
        output_files = []

        if os.path.exists(output_dir):
            for root, dirs, files in os.walk(output_dir):
                for file in files:
                    full_path = os.path.join(root, file)
                    output_files.append(full_path)

        return output_files

    def _generate_nuke_script(self, operation_type: str, params: Dict, output_dir: str) -> str:
        """Generate Nuke Python script for operation."""
        # Use cross-platform path handling
        output_dir_path = normalize_path(output_dir)
        output_dir_nuke = to_platform_path(output_dir_path, 'Linux')  # Nuke prefers forward slashes

        base_script = f'''
import os
import nuke
import sys

output_dir = "{output_dir_nuke}"
print(f"Nuke operation starting: {operation_type}")

try:
    # Initialize Nuke for headless operation
    nuke.scriptClear()

    print("Nuke initialized successfully")
'''

        if operation_type == "composite":
            # Basic compositing operation
            script = base_script + f'''
    # Composite operation
    input_image = "{params.get('input_image', '')}"
    output_image = os.path.join(output_dir, "{params.get('output_name', 'output.exr')}")

    if input_image and os.path.exists(input_image):
        # Create Read node
        read_node = nuke.createNode("Read")
        read_node["file"].setValue(input_image)

        # Create Write node
        write_node = nuke.createNode("Write")
        write_node["file"].setValue(output_image)
        write_node.setInput(0, read_node)

        # Execute the render
        nuke.execute(write_node, 1, 1)
        print("Composite operation completed: " + output_image)
    else:
        print("Input image not found: " + input_image)

except Exception as e:
    print("Error in Nuke operation: " + str(e))
    sys.exit(1)

print("Nuke operation completed successfully")
'''
        elif operation_type == "color_grade":
            # Color grading operation
            script = base_script + f'''
    # Color grading operation
    input_image = "{params.get('input_image', '')}"
    output_image = os.path.join(output_dir, "{params.get('output_name', 'graded.exr')}")
    brightness = {params.get('brightness', 1.0)}
    contrast = {params.get('contrast', 1.0)}
    saturation = {params.get('saturation', 1.0)}

    if input_image and os.path.exists(input_image):
        # Create Read node
        read_node = nuke.createNode("Read")
        read_node["file"].setValue(input_image)

        # Create ColorCorrect node
        color_node = nuke.createNode("ColorCorrect")
        color_node["gain"].setValue(brightness)
        color_node["contrast"].setValue(contrast)
        color_node["saturation"].setValue(saturation)
        color_node.setInput(0, read_node)

        # Create Write node
        write_node = nuke.createNode("Write")
        write_node["file"].setValue(output_image)
        write_node.setInput(0, color_node)

        # Execute the render
        nuke.execute(write_node, 1, 1)
        print("Color grading completed: " + output_image)
    else:
        print("Input image not found: " + input_image)

except Exception as e:
    print("Error in Nuke operation: " + str(e))
    sys.exit(1)

print("Nuke operation completed successfully")
'''
        else:
            # Default operation
            script = base_script + f'''
    # Default Nuke operation
    print("Executing default Nuke operation")
    output_file = os.path.join(output_dir, "nuke_output.txt")

    with open(output_file, 'w') as f:
        f.write("Nuke operation executed: {operation_type}\\n")
        f.write("Parameters: " + str(params) + "\\n")
        f.write("Operation completed successfully\\n")

    print("Default operation completed: " + output_file)

except Exception as e:
    print("Error in Nuke operation: " + str(e))
    sys.exit(1)

print("Nuke operation completed successfully")
'''

        return script

    def _generate_natron_script(self, operation_type: str, params: Dict, output_dir: str) -> str:
        """Generate Natron Python script for operation."""
        # Use cross-platform path handling
        output_dir_path = normalize_path(output_dir)
        output_dir_natron = to_platform_path(output_dir_path, 'Linux')  # Natron prefers forward slashes

        base_script = f'''
import os
import sys

# Add Natron's Python path
try:
    import NatronEngine
    import NatronGui
    natron_available = True
except ImportError as e:
    print("Natron modules not available: " + str(e))
    natron_available = False

output_dir = "{output_dir_natron}"
print(f"Natron operation starting: {operation_type}")

try:
'''

        if operation_type == "composite":
            # Basic compositing operation
            script = base_script + f'''
    # Composite operation
    input_image = "{params.get('input_image', '')}"
    output_image = os.path.join(output_dir, "{params.get('output_name', 'output.exr')}")

    if natron_available:
        try:
            # Get the current project
            app = natron.getGuiInstance(0)

            # Create Read node
            read_node = app.createNode("fr.inria.built-in.Read")
            read_node.getParam("filename").setValue(input_image)

            # Create Write node
            write_node = app.createNode("fr.inria.built-in.Write")
            write_node.getParam("filename").setValue(output_image)
            write_node.connectInput(0, read_node)

            # Render
            write_node.getParam("renderButton").trigger()
            print("Composite operation completed: " + output_image)

        except Exception as api_error:
            print("Natron API error: " + str(api_error))
            # Create placeholder output
            with open(output_image, 'w') as f:
                f.write("Natron composite operation placeholder\\n")
    else:
        # Fallback for environments without full Natron API
        print("Natron API not available, creating placeholder output")
        with open(output_image, 'w') as f:
            f.write("Natron composite operation placeholder\\n")

except Exception as e:
    print("Error in Natron operation: " + str(e))
    sys.exit(1)

print("Natron operation completed successfully")
'''
        else:
            # Default operation
            script = base_script + f'''
    # Default Natron operation
    print("Executing default Natron operation")
    output_file = os.path.join(output_dir, "natron_output.txt")

    with open(output_file, 'w') as f:
        f.write("Natron operation executed: {operation_type}\\n")
        f.write("Parameters: " + str(params) + "\\n")
        f.write("Operation completed successfully\\n")

    print("Default operation completed: " + output_file)

except Exception as e:
    print("Error in Natron operation: " + str(e))
    sys.exit(1)

print("Natron operation completed successfully")
'''

        return script

    def _generate_vectorworks_script(self, operation_type: str, params: Dict, output_dir: str) -> str:
        """Generate Vectorworks Python script for operation."""
        # Use cross-platform path handling
        output_dir_path = normalize_path(output_dir)
        output_dir_vw = to_platform_path(output_dir_path, 'Windows')  # Vectorworks prefers Windows paths

        base_script = f'''
import os
import sys

# Add Vectorworks Python modules to path if available
try:
    import vs  # Vectorworks scripting namespace
    vectorworks_available = True
    print("Vectorworks scripting module available")
except ImportError:
    print("Vectorworks scripting module not available")
    vectorworks_available = False

output_dir = r"{output_dir_vw}"
print(f"Vectorworks operation starting: {operation_type}")

try:
'''

        if operation_type == "cad_import":
            # CAD import operation
            script = base_script + f'''
    # CAD import operation
    input_file = r"{params.get('input_file', '')}"
    output_file = os.path.join(output_dir, "{params.get('output_name', 'imported.vwx')}")

    if vectorworks_available:
        try:
            # Import CAD file
            if input_file and os.path.exists(input_file):
                # Use Vectorworks import functions
                success = vs.ImportDWG(input_file)
                if success:
                    # Save as Vectorworks file
                    vs.SaveAs(output_file)
                    print("CAD import completed: " + output_file)
                else:
                    print("Failed to import CAD file: " + input_file)
            else:
                print("Input file not found: " + input_file)

        except Exception as e:
            print("Vectorworks API error: " + str(e))
            # Create placeholder output
            with open(os.path.join(output_dir, "import_placeholder.txt"), 'w') as f:
                f.write("Vectorworks CAD import placeholder\\n")
    else:
        # Fallback for environments without Vectorworks API
        print("Vectorworks API not available, creating placeholder output")
        with open(os.path.join(output_dir, "import_placeholder.txt"), 'w') as f:
            f.write("Vectorworks CAD import operation placeholder\\n")
            f.write("Input file: " + input_file + "\\n")

except Exception as e:
    print("Error in Vectorworks operation: " + str(e))
    sys.exit(1)

print("Vectorworks operation completed successfully")
'''
        else:
            # Default operation
            script = base_script + f'''
    # Default Vectorworks operation
    print("Executing default Vectorworks operation")
    output_file = os.path.join(output_dir, "vectorworks_output.txt")

    with open(output_file, 'w') as f:
        f.write("Vectorworks operation executed: {operation_type}\\n")
        f.write("Parameters: " + str(params) + "\\n")
        f.write("Operation completed successfully\\n")

    print("Default operation completed: " + output_file)

except Exception as e:
    print("Error in Vectorworks operation: " + str(e))
    sys.exit(1)

print("Vectorworks operation completed successfully")
'''

        return script

    async def cancel_operation(self, operation_id: str):
        """Cancel running operation."""
        if operation_id in self.running_processes:
            process = self.running_processes[operation_id]
            process.terminate()

            # Wait a bit for graceful shutdown
            await asyncio.sleep(2)

            # Force kill if still running
            if process.poll() is None:
                process.kill()

            logger.info(f"🚫 Cancelled operation: {operation_id}")

    async def _execute_maya_via_persistent_server(
        self,
        operation_id: str,
        op_type: str,
        params: Dict,
        output_dir: str,
        progress_callback,
        start_time: float,
        server=None
    ) -> OperationResult:
        """
        Execute Maya operation via persistent or GUI server.

        This method generates Maya Python code and sends it to the active
        server (GUI or headless), avoiding the 6-18s Maya initialization overhead.

        Args:
            server: Optional server instance to use. If None, uses self.maya_server.
        """
        active_server = server or self.maya_server
        is_gui = isinstance(active_server, GUIServerManager)
        server_label = "Maya GUI" if is_gui else "Maya persistent"

        if progress_callback:
            await progress_callback(20, f"Sending operation to {server_label} server")

        # Generate Maya command from operation type
        maya_command = self._generate_maya_command_for_persistent_server(op_type, params, output_dir)

        if progress_callback:
            await progress_callback(40, "Executing Maya command")

        # Execute via active server (GUI or headless)
        result = await asyncio.get_event_loop().run_in_executor(
            None,
            lambda: active_server.execute_command(maya_command, op_type, timeout=60)
        )

        if progress_callback:
            await progress_callback(80, "Processing results")

        execution_time = time.time() - start_time

        # Build operation result
        if result.get('success'):
            logger.info(f"✅ Maya operation '{op_type}' completed in {execution_time*1000:.1f}ms")

            metadata = {
                'dcc': 'maya',
                'operation_type': op_type,
                'via_persistent_server': True,
                'via_gui_server': is_gui,
                'server_execution_time': result.get('execution_time', 0),
                'total_time': result.get('total_time', 0),
                'dcc_result': result.get('result'),
                'console_output': result.get('output', ''),
            }

            # Handle session operations
            if op_type == "session":
                session_id = params.get("session_id", "default")
                metadata.update({
                    'session_id': session_id,
                    'session_status': 'ready',
                    'maya_version': '2026'
                })

            if progress_callback:
                await progress_callback(100, "Operation complete")

            return OperationResult(
                operation_id=operation_id,
                success=True,
                output_files=[],
                logs=[f"Executed via {server_label} server in {execution_time*1000:.1f}ms"],
                error_message=None,
                execution_time=execution_time,
                metadata=metadata
            )
        else:
            error_msg = result.get('error', 'Unknown error')
            logger.error(f"❌ Maya operation '{op_type}' failed: {error_msg}")

            return OperationResult(
                operation_id=operation_id,
                success=False,
                output_files=[],
                logs=[],
                error_message=error_msg,
                execution_time=execution_time,
                metadata={
                    'dcc': 'maya',
                    'operation_type': op_type,
                    'via_persistent_server': True,
                    'via_gui_server': is_gui,
                    'dcc_result': result.get('result'),
                    'console_output': result.get('output', ''),
                }
            )

    def _generate_maya_command_for_persistent_server(
        self,
        op_type: str,
        params: Dict,
        output_dir: str
    ) -> str:
        """
        Generate Maya Python command for persistent server execution.

        This converts operation types to executable Maya Python code.
        """
        if op_type == "session":
            # Session creation is instant with persistent server (Maya already running)
            session_id = params.get("session_id", "default")
            return f"""
# Session creation - instant with persistent server
import maya.cmds as cmds
session_id = "{session_id}"
maya_version = cmds.about(version=True)
result = {{
    'session_id': session_id,
    'status': 'ready',
    'maya_version': maya_version
}}
print(f"Session {{session_id}} ready (Maya {{maya_version}})")
"""

        elif op_type == "create_sphere":
            name = params.get("name", "sphere")
            radius = params.get("radius", 1.0)
            return f"""
import maya.cmds as cmds
sphere_result = cmds.polySphere(name='{name}', radius={radius})
result = {{"created": sphere_result, "name": "{name}", "type": "sphere", "radius": {radius}}}
print(f"Created sphere: {{sphere_result}}")
"""

        elif op_type == "create_cube":
            name = params.get("name", "cube")
            size = params.get("size", 1.0)
            return f"""
import maya.cmds as cmds
cube_result = cmds.polyCube(name='{name}', width={size}, height={size}, depth={size})
result = {{"created": cube_result, "name": "{name}", "type": "cube", "size": {size}}}
print(f"Created cube: {{cube_result}}")
"""

        elif op_type == "create_plane":
            name = params.get("name", "plane")
            width = params.get("width", 1.0)
            height = params.get("height", 1.0)
            return f"""
import maya.cmds as cmds
plane_result = cmds.polyPlane(name='{name}', width={width}, height={height})
result = {{"created": plane_result, "name": "{name}", "type": "plane", "width": {width}, "height": {height}}}
print(f"Created plane: {{plane_result}}")
"""

        elif op_type == "command":
            # Direct Maya command execution
            command = params.get("command", "")
            return f"""
import maya.cmds as cmds
{command}
if result is None:
    result = {{"status": "completed", "message": "Command executed"}}
"""

        elif op_type == "export":
            # Export Maya scene or selection
            # Get export path (backend uses 'output_file', fallback to 'export_path' for compatibility)
            export_path = params.get("output_file") or params.get("export_path") or f"{output_dir}/exported_scene.ma"

            # Get export format (backend uses 'export_format', fallback to 'export_type')
            export_format = params.get("export_format", params.get("export_type", "ma"))

            # Map format abbreviations to Maya file type strings
            format_mapping = {
                "ma": "mayaAscii",
                "mb": "mayaBinary",
                "obj": "OBJexport",
                "fbx": "FBX export"
            }
            export_type = format_mapping.get(export_format, "mayaAscii")

            # Get export mode (backend uses 'export_mode', fallback to 'export_all')
            export_mode = params.get("export_mode", "all")
            export_all = export_mode == "all" if isinstance(export_mode, str) else params.get("export_all", True)

            logger.info(f"[EXPORT DEBUG] Export to: {export_path}")
            logger.info(f"[EXPORT DEBUG] Format: {export_format} -> {export_type}, Mode: {export_mode}")

            # Convert Windows path format if needed (Maya prefers forward slashes)
            export_path_normalized = export_path.replace('\\', '/')

            return f"""
import maya.cmds as cmds
import os

# Export scene
export_path = r'{export_path_normalized}'

# Create output directory
export_dir = os.path.dirname(export_path)
if export_dir and not os.path.exists(export_dir):
    os.makedirs(export_dir, exist_ok=True)
    print(f"Created directory: {{export_dir}}")

# Perform export
if {export_all}:
    print(f"Exporting all scene to: {{export_path}}")
    cmds.file(export_path, exportAll=True, type='{export_type}', force=True)
    print(f"Scene exported successfully to: {{export_path}}")
else:
    # Export selection
    selected = cmds.ls(selection=True)
    if selected:
        print(f"Exporting selection ({{len(selected)}} objects) to: {{export_path}}")
        cmds.file(export_path, exportSelected=True, type='{export_type}', force=True)
        print(f"Selection exported successfully to: {{export_path}}")
    else:
        print("Warning: No objects selected for export")
        export_path = ""

result = {{"export_path": export_path, "export_format": "{export_format}", "export_type": "{export_type}", "status": "completed" if export_path else "no_selection"}}
"""

        else:
            # Generic operation - try to extract command from params
            command = params.get("command", params.get("script", ""))
            if command:
                return f"""
import maya.cmds as cmds
{command}
if result is None:
    result = {{"status": "completed", "message": "Generic operation executed"}}
"""
            else:
                return f"""
# Unknown operation type: {op_type}
import maya.cmds as cmds
result = {{"status": "not_implemented", "operation": "{op_type}", "message": "Operation type not yet implemented for persistent server"}}
print(f"Operation type '{op_type}' not yet implemented for persistent server")
"""

    def cleanup(self):
        """Cleanup resources."""
        # Shutdown Maya persistent server
        if self.maya_server:
            logger.info("🛑 Shutting down Maya persistent server...")
            try:
                self.maya_server.shutdown()
                logger.info("✅ Maya persistent server shut down successfully")
            except Exception as e:
                logger.error(f"❌ Error shutting down Maya server: {e}")

        # Kill any running processes
        for operation_id, process in self.running_processes.items():
            try:
                process.terminate()
            except:
                pass

        # Clean up temp directories
        if hasattr(self, 'base_temp_dir') and os.path.exists(self.base_temp_dir):
            try:
                shutil.rmtree(self.base_temp_dir)
                logger.info(f"🧹 Cleaned up temp directory: {self.base_temp_dir}")
            except Exception as e:
                logger.warning(f"Failed to cleanup temp directory: {e}")