"""FreeCAD Robust MCP Bridge Plugin - Socket Server with Queue-based Thread Safety.

This module provides a socket server that runs inside FreeCAD to handle
MCP bridge requests. It must be executed within FreeCAD's Python environment.

Design inspired by neka-nat/freecad-mcp (MIT License):
- Queue-based GUI communication for thread safety
- XML-RPC compatibility mode (port 9875)
- Screenshot capture with view type detection

Attribution:
    The queue-based thread safety pattern and XML-RPC protocol design were
    inspired by neka-nat/freecad-mcp (https://github.com/neka-nat/freecad-mcp),
    which is licensed under the MIT License. This implementation is a complete
    rewrite with additional features (JSON-RPC 2.0, async socket server).
"""

from __future__ import annotations

import asyncio
import contextlib
import errno
import io
import json
import queue
import sys
import threading
import time
import traceback
import uuid
import xmlrpc.server
from contextlib import redirect_stderr, redirect_stdout
from typing import Any

# These imports only work inside FreeCAD
try:
    import FreeCAD
    import FreeCADGui

    FREECAD_AVAILABLE = True
except ImportError:
    FREECAD_AVAILABLE = False

# Default configuration
DEFAULT_SOCKET_PORT = 9876
DEFAULT_XMLRPC_PORT = 9875
QUEUE_POLL_INTERVAL_MS = 50
STATUS_UPDATE_INTERVAL_MS = 5000  # Update status bar every 5 seconds
HEADLESS_POLL_INTERVAL_S = 0.1  # Headless mode poll interval in seconds


def _get_qt_core() -> Any:
    """Get the QtCore module if GUI mode is available.

    This helper checks if FreeCAD is available with GUI enabled and
    attempts to import QtCore from PySide2 or PySide6.

    Returns:
        The QtCore module if available in GUI mode, None otherwise.
    """
    if not (FREECAD_AVAILABLE and FreeCAD.GuiUp):
        return None

    # Try PySide2 first, then PySide6
    with contextlib.suppress(ImportError):
        from PySide2 import QtCore

        return QtCore

    with contextlib.suppress(ImportError):
        from PySide6 import QtCore

        return QtCore

    return None


class ExecutionRequest:
    """Represents a code execution request."""

    def __init__(
        self,
        code: str,
        timeout_ms: int = 30000,
        request_id: str | None = None,
    ) -> None:
        """Initialize execution request.

        Args:
            code: Python code to execute.
            timeout_ms: Execution timeout in milliseconds.
            request_id: Optional request ID for tracking.
        """
        self.code = code
        self.timeout_ms = timeout_ms
        self.request_id = request_id
        self.result: dict[str, Any] | None = None
        self.completed = threading.Event()


class FreecadMCPPlugin:
    """Plugin that runs inside FreeCAD to handle MCP bridge requests.

    This class creates servers that accept connections from the MCP server
    and executes commands in FreeCAD's context using a thread-safe queue
    system for GUI operations.

    Attributes:
        socket_host: Hostname for socket server.
        socket_port: Port for JSON-RPC socket server.
        xmlrpc_port: Port for XML-RPC server.
    """

    def __init__(
        self,
        host: str = "localhost",
        port: int = DEFAULT_SOCKET_PORT,
        xmlrpc_port: int = DEFAULT_XMLRPC_PORT,
        enable_xmlrpc: bool = True,
    ) -> None:
        """Initialize the plugin.

        Args:
            host: Hostname to bind to.
            port: Port for JSON-RPC socket server.
            xmlrpc_port: Port for XML-RPC server.
            enable_xmlrpc: Whether to enable XML-RPC server.
        """
        # Generate unique instance ID for this server
        self._instance_id = str(uuid.uuid4())

        self._host = host
        self._port = port
        self._xmlrpc_port = xmlrpc_port
        self._enable_xmlrpc = enable_xmlrpc

        # Server instances
        self._socket_server: asyncio.Server | None = None
        self._xmlrpc_server: xmlrpc.server.SimpleXMLRPCServer | None = None
        self._socket_loop: asyncio.AbstractEventLoop | None = None

        # Threading
        self._socket_thread: threading.Thread | None = None
        self._xmlrpc_thread: threading.Thread | None = None
        self._running = False

        # Queue-based execution for thread safety (learned from neka-nat)
        self._request_queue: queue.Queue[ExecutionRequest] = queue.Queue()
        self._timer = None
        self._queue_thread: threading.Thread | None = None
        self._headless = False

        # Status bar tracking
        self._status_timer = None
        self._request_count = 0
        self._last_request_time: float | None = None

    # =========================================================================
    # Public API (for external access without using private attributes)
    # =========================================================================

    @property
    def is_running(self) -> bool:
        """Check if the MCP bridge server is currently running.

        Returns:
            True if the server is running, False otherwise.
        """
        return self._running

    @property
    def instance_id(self) -> str:
        """Get the unique instance ID for this server.

        Returns:
            UUID string identifying this server instance.
        """
        return self._instance_id

    @property
    def socket_port(self) -> int:
        """Get the JSON-RPC socket server port.

        Returns:
            Port number for the socket server.
        """
        return self._port

    @property
    def xmlrpc_port(self) -> int:
        """Get the XML-RPC server port.

        Returns:
            Port number for the XML-RPC server.
        """
        return self._xmlrpc_port

    @property
    def request_count(self) -> int:
        """Get the total number of requests processed.

        Returns:
            Number of requests processed since server start.
        """
        return self._request_count

    def get_status(self) -> dict[str, Any]:
        """Get the current status of the MCP bridge server.

        Returns:
            Dictionary containing:
                - running: Whether the server is running
                - instance_id: Unique server instance ID
                - socket_port: JSON-RPC socket port
                - xmlrpc_port: XML-RPC port
                - xmlrpc_enabled: Whether XML-RPC is enabled
                - request_count: Total requests processed
                - last_request_time: Timestamp of last request (or None)
                - headless: Whether running in headless mode
        """
        return {
            "running": self._running,
            "instance_id": self._instance_id,
            "socket_port": self._port,
            "xmlrpc_port": self._xmlrpc_port,
            "xmlrpc_enabled": self._enable_xmlrpc,
            "request_count": self._request_count,
            "last_request_time": self._last_request_time,
            "headless": self._headless,
        }

    def start(self) -> None:
        """Start all servers."""
        if self._running:
            return

        self._running = True

        # Print instance ID to stderr for test automation to capture.
        # Stdout may be reserved for JSON-RPC when running in stdio mode.
        print(
            f"FREECAD_MCP_BRIDGE_INSTANCE_ID={self._instance_id}",
            file=sys.stderr,
            flush=True,
        )

        # Start the queue processing timer on the main thread
        self._start_queue_processor()

        # Start socket server
        self._socket_thread = threading.Thread(
            target=self._run_socket_server,
            daemon=True,
            name="MCP-Socket",
        )
        self._socket_thread.start()

        # Start XML-RPC server if enabled
        if self._enable_xmlrpc:
            self._xmlrpc_thread = threading.Thread(
                target=self._run_xmlrpc_server,
                daemon=True,
                name="MCP-XMLRPC",
            )
            self._xmlrpc_thread.start()

        if FREECAD_AVAILABLE:
            FreeCAD.Console.PrintMessage(
                f"MCP Bridge started (Instance ID: {self._instance_id}):\n"
                f"  - JSON-RPC: {self._host}:{self._port}\n"
            )
            if self._enable_xmlrpc:
                FreeCAD.Console.PrintMessage(
                    f"  - XML-RPC: {self._host}:{self._xmlrpc_port}\n"
                )

        # Start status bar updates in GUI mode
        self._start_status_updates()

    def stop(self) -> None:
        """Stop all servers."""
        self._running = False

        # Stop status bar updates
        self._stop_status_updates()

        # Stop queue processor timer (GUI mode)
        if self._timer:
            with contextlib.suppress(Exception):
                self._timer.stop()
            self._timer = None

        # Stop XML-RPC server by closing its socket directly
        # This will cause handle_request() to raise an exception and exit
        # Keep server reference until thread exits to avoid race condition
        if self._xmlrpc_server:
            with contextlib.suppress(Exception):
                self._xmlrpc_server.socket.close()

        # Stop socket server - close the server and stop the event loop
        if self._socket_loop and self._socket_server:
            self._socket_loop.call_soon_threadsafe(self._socket_server.close)
            self._socket_loop.call_soon_threadsafe(self._socket_loop.stop)

        # Wait briefly for threads - they're daemon threads so they'll
        # be killed when the main thread exits anyway
        if self._queue_thread and self._queue_thread.is_alive():
            self._queue_thread.join(timeout=0.5)
        self._queue_thread = None

        if self._socket_thread and self._socket_thread.is_alive():
            self._socket_thread.join(timeout=0.5)
        self._socket_thread = None

        # Wait for XML-RPC thread to exit before clearing server reference
        if self._xmlrpc_thread and self._xmlrpc_thread.is_alive():
            self._xmlrpc_thread.join(timeout=0.5)
        self._xmlrpc_thread = None
        # Now safe to clear the server reference
        self._xmlrpc_server = None

        if FREECAD_AVAILABLE:
            FreeCAD.Console.PrintMessage("MCP Bridge stopped\n")

    def run_forever(self) -> None:
        """Run the server indefinitely.

        This method blocks until interrupted (Ctrl+C) or stop() is called.
        Works in both GUI and headless modes:
        - GUI mode: Uses Qt event loop to allow timers to fire
        - Headless mode: Uses short sleep intervals for responsive shutdown
        """
        self.start()
        if FREECAD_AVAILABLE:
            FreeCAD.Console.PrintMessage("Server running. Press Ctrl+C to stop.\n")

        # Check if we're in GUI mode and have Qt available
        QtCore = _get_qt_core()

        try:
            if QtCore is not None:
                # GUI mode: use Qt's processEvents to keep the event loop running
                # This allows QTimers to fire for queue processing
                app = QtCore.QCoreApplication.instance()
                if app is not None:
                    while self._running:
                        # Process Qt events (including our QTimer callbacks)
                        app.processEvents()
                        # Small sleep to prevent busy-waiting
                        time.sleep(0.01)
                else:
                    # No QApplication - fall back to headless behavior
                    self._run_forever_headless()
            else:
                # Headless mode
                self._run_forever_headless()
        except KeyboardInterrupt:
            pass  # Normal exit via Ctrl+C
        finally:
            if FREECAD_AVAILABLE:
                FreeCAD.Console.PrintMessage("\nShutting down...\n")
            self.stop()

    def _run_forever_headless(self) -> None:
        """Run forever in headless mode using short sleep intervals.

        Uses a short sleep interval to allow responsive shutdown when
        stop() sets _running to False.
        """
        while self._running:
            # Use short sleep to allow responsive shutdown
            # This is more portable than signal.pause() and responds
            # quickly when stop() sets _running = False
            time.sleep(HEADLESS_POLL_INTERVAL_S)

    # =========================================================================
    # Status Bar Updates (GUI mode only)
    # =========================================================================

    def _start_status_updates(self) -> None:
        """Start periodic status bar updates in GUI mode."""
        QtCore = _get_qt_core()
        if QtCore is None:
            return

        # Create timer for status updates
        timer = QtCore.QTimer()
        timer.timeout.connect(self._update_status_bar)
        timer.start(STATUS_UPDATE_INTERVAL_MS)
        self._status_timer = timer

        # Show initial status
        self._update_status_bar()

    def _stop_status_updates(self) -> None:
        """Stop status bar updates and clear the status."""
        if self._status_timer:
            with contextlib.suppress(Exception):
                self._status_timer.stop()
            self._status_timer = None

        # Clear status bar message
        if FREECAD_AVAILABLE and FreeCAD.GuiUp:
            self._set_status_bar("")

    def _update_status_bar(self) -> None:
        """Update the FreeCAD status bar with MCP bridge status."""
        if not (FREECAD_AVAILABLE and FreeCAD.GuiUp):
            return

        # Build status message
        ports = f"XML-RPC:{self._xmlrpc_port}" if self._enable_xmlrpc else ""
        if ports:
            ports = f" ({ports})"

        if self._request_count > 0:
            # Show activity info
            if self._last_request_time:
                elapsed = time.time() - self._last_request_time
                if elapsed < 60:
                    time_ago = f"{int(elapsed)}s ago"
                else:
                    time_ago = f"{int(elapsed / 60)}m ago"
                status = f"🔌 MCP Bridge active{ports} | {self._request_count} requests | last: {time_ago}"
            else:
                status = f"🔌 MCP Bridge active{ports} | {self._request_count} requests"
        else:
            status = f"🔌 MCP Bridge running{ports} | waiting for connections..."

        self._set_status_bar(status)

    def _set_status_bar(self, message: str) -> None:
        """Set the FreeCAD main window status bar message.

        Args:
            message: Message to display in status bar.
        """
        if not (FREECAD_AVAILABLE and FreeCAD.GuiUp):
            return

        try:
            main_window = FreeCADGui.getMainWindow()
            if main_window:
                status_bar = main_window.statusBar()
                if status_bar:
                    if message:
                        # Show message persistently (0 = no timeout)
                        status_bar.showMessage(message, 0)
                    else:
                        status_bar.clearMessage()
        except Exception:
            # Silently ignore status bar errors
            pass

    def _record_request(self) -> None:
        """Record that a request was processed (for status tracking)."""
        self._request_count += 1
        self._last_request_time = time.time()

    # =========================================================================
    # Queue-based Thread Safety (from neka-nat)
    # =========================================================================

    def _start_queue_processor(self) -> None:
        """Start the queue processor on the main GUI thread or as background thread."""
        # Check if we're in GUI mode using FreeCAD.GuiUp
        # Note: Qt (PySide) may be available even in headless mode, but without
        # a running event loop, Qt timers won't fire. Use GuiUp to detect this.
        gui_available = FREECAD_AVAILABLE and FreeCAD.GuiUp

        if gui_available:
            # GUI mode: use Qt timer for thread-safe GUI operations
            try:
                from PySide2 import QtCore
            except ImportError:
                try:
                    from PySide6 import QtCore
                except ImportError:
                    QtCore = None  # type: ignore[assignment]

            if QtCore is not None:
                timer = QtCore.QTimer()
                timer.timeout.connect(self._process_queue)
                timer.start(QUEUE_POLL_INTERVAL_MS)
                self._timer = timer
                return

        # Headless mode: use a background thread for queue processing
        # In headless mode, there's no GUI thread concern, so direct
        # processing in a background thread is safe
        self._headless = True
        self._queue_thread = threading.Thread(
            target=self._run_queue_processor_loop,
            daemon=True,
            name="MCP-QueueProcessor",
        )
        self._queue_thread.start()
        if FREECAD_AVAILABLE:
            FreeCAD.Console.PrintMessage(
                "Running in headless mode (queue processor thread started)\n"
            )

    def _run_queue_processor_loop(self) -> None:
        """Run queue processor in a loop for headless mode."""
        while self._running:
            self._process_queue()
            time.sleep(QUEUE_POLL_INTERVAL_MS / 1000.0)

    def _process_queue(self) -> None:
        """Process pending execution requests on the main thread.

        This method is called periodically by a Qt timer to ensure
        GUI operations happen on the main thread.
        """
        while not self._request_queue.empty():
            try:
                request = self._request_queue.get_nowait()
                result = self._execute_code_sync(request.code)
                request.result = result
                request.completed.set()
                # Track request for status bar
                self._record_request()
            except queue.Empty:
                break
            except Exception as e:
                if FREECAD_AVAILABLE:
                    FreeCAD.Console.PrintError(f"Queue processing error: {e}\n")

    def _execute_via_queue(
        self,
        code: str,
        timeout_ms: int = 30000,
    ) -> dict[str, Any]:
        """Execute code via the queue system for thread safety.

        Args:
            code: Python code to execute.
            timeout_ms: Execution timeout in milliseconds.

        Returns:
            Execution result dictionary.
        """
        request = ExecutionRequest(code, timeout_ms)
        self._request_queue.put(request)

        # Wait for completion
        if request.completed.wait(timeout=timeout_ms / 1000):
            return request.result or {
                "success": False,
                "error_type": "InternalError",
                "error_message": "No result returned",
            }
        else:
            return {
                "success": False,
                "error_type": "TimeoutError",
                "error_message": f"Execution timed out after {timeout_ms}ms",
                "execution_time_ms": timeout_ms,
            }

    def _execute_code_sync(self, code: str) -> dict[str, Any]:
        """Execute Python code synchronously (call on main thread only).

        Args:
            code: Python code to execute.

        Returns:
            Execution result dictionary.
        """
        start = time.perf_counter()
        stdout_capture = io.StringIO()
        stderr_capture = io.StringIO()

        exec_globals: dict[str, Any] = {
            "__builtins__": __builtins__,
        }

        if FREECAD_AVAILABLE:
            exec_globals["FreeCAD"] = FreeCAD
            exec_globals["App"] = FreeCAD
            exec_globals["FreeCADGui"] = FreeCADGui
            exec_globals["Gui"] = FreeCADGui

        try:
            with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):
                compiled = compile(code, "<mcp>", "exec")
                exec(compiled, exec_globals)  # noqa: S102

            elapsed = (time.perf_counter() - start) * 1000
            return {
                "success": True,
                "result": exec_globals.get("_result_"),
                "stdout": stdout_capture.getvalue(),
                "stderr": stderr_capture.getvalue(),
                "execution_time_ms": elapsed,
            }

        except Exception as e:
            elapsed = (time.perf_counter() - start) * 1000
            return {
                "success": False,
                "result": None,
                "stdout": stdout_capture.getvalue(),
                "stderr": stderr_capture.getvalue(),
                "execution_time_ms": elapsed,
                "error_type": type(e).__name__,
                "error_message": str(e),
                "error_traceback": traceback.format_exc(),
            }

    # =========================================================================
    # Socket Server (JSON-RPC 2.0)
    # =========================================================================

    def _run_socket_server(self) -> None:
        """Run the asyncio event loop in background thread."""
        self._socket_loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self._socket_loop)

        try:
            self._socket_loop.run_until_complete(self._start_socket_server())
            self._socket_loop.run_forever()
        except OSError as e:
            # Server failed to start - mark as not running
            self._running = False
            if e.errno == errno.EADDRINUSE:
                if FREECAD_AVAILABLE:
                    FreeCAD.Console.PrintWarning(
                        f"MCP Bridge: JSON-RPC port {self._port} already in use. "
                        f"Another instance may be running.\n"
                    )
            elif FREECAD_AVAILABLE:
                FreeCAD.Console.PrintError(
                    f"MCP Bridge: Failed to start JSON-RPC server: {e}\n"
                )
        finally:
            self._socket_loop.close()

    async def _start_socket_server(self) -> None:
        """Start the TCP server."""
        self._socket_server = await asyncio.start_server(
            self._handle_socket_client,
            self._host,
            self._port,
        )

    async def _handle_socket_client(
        self,
        reader: asyncio.StreamReader,
        writer: asyncio.StreamWriter,
    ) -> None:
        """Handle a connected socket client.

        Args:
            reader: Stream reader for incoming data.
            writer: Stream writer for outgoing data.
        """
        try:
            while self._running:
                data = await reader.readline()
                if not data:
                    break

                try:
                    request = json.loads(data.decode("utf-8"))
                    response = await self._process_jsonrpc_request(request)
                except json.JSONDecodeError as e:
                    response = {
                        "jsonrpc": "2.0",
                        "id": None,
                        "error": {
                            "code": -32700,
                            "message": "Parse error",
                            "data": str(e),
                        },
                    }

                response_data = json.dumps(response).encode("utf-8") + b"\n"
                writer.write(response_data)
                await writer.drain()

        except Exception as e:
            if FREECAD_AVAILABLE:
                FreeCAD.Console.PrintError(f"MCP socket error: {e}\n")
        finally:
            writer.close()
            with contextlib.suppress(Exception):
                await writer.wait_closed()

    async def _process_jsonrpc_request(
        self,
        request: dict[str, Any],
    ) -> dict[str, Any]:
        """Process a JSON-RPC 2.0 request.

        Args:
            request: JSON-RPC request dictionary.

        Returns:
            JSON-RPC response dictionary.
        """
        request_id = request.get("id")
        method = request.get("method")
        params = request.get("params", {})

        # Handle ping specially (no queue needed)
        if method == "ping":
            return {
                "jsonrpc": "2.0",
                "id": request_id,
                "result": {
                    "pong": True,
                    "timestamp": time.time(),
                    "instance_id": self._instance_id,
                },
            }

        # Handle get_instance_id specially (no queue needed)
        if method == "get_instance_id":
            return {
                "jsonrpc": "2.0",
                "id": request_id,
                "result": {"instance_id": self._instance_id},
            }

        # Handle execute via queue
        if method == "execute":
            code = params.get("code", "")
            timeout_ms = params.get("timeout_ms", 30000)

            # Execute via queue for thread safety
            loop = asyncio.get_event_loop()
            result = await loop.run_in_executor(
                None,
                lambda: self._execute_via_queue(code, timeout_ms),
            )

            return {
                "jsonrpc": "2.0",
                "id": request_id,
                "result": result,
            }

        # Unknown method
        return {
            "jsonrpc": "2.0",
            "id": request_id,
            "error": {
                "code": -32601,
                "message": "Method not found",
                "data": f"Unknown method: {method}",
            },
        }

    # =========================================================================
    # XML-RPC Server (neka-nat compatible)
    # =========================================================================

    def _run_xmlrpc_server(self) -> None:
        """Run the XML-RPC server."""
        try:
            self._xmlrpc_server = xmlrpc.server.SimpleXMLRPCServer(
                (self._host, self._xmlrpc_port),
                allow_none=True,
                logRequests=False,
            )
        except OSError as e:
            if e.errno == errno.EADDRINUSE:
                if FREECAD_AVAILABLE:
                    FreeCAD.Console.PrintWarning(
                        f"MCP Bridge: XML-RPC port {self._xmlrpc_port} already in use. "
                        f"Another instance may be running.\n"
                    )
            elif FREECAD_AVAILABLE:
                FreeCAD.Console.PrintError(
                    f"MCP Bridge: Failed to start XML-RPC server: {e}\n"
                )
            return

        # Set a timeout so handle_request() doesn't block forever
        # This allows the server to check self._running periodically
        self._xmlrpc_server.timeout = 0.5

        # Register methods (type: ignore needed - xmlrpc types are overly restrictive)
        self._xmlrpc_server.register_function(self._xmlrpc_execute, "execute")  # type: ignore[arg-type]
        self._xmlrpc_server.register_function(self._xmlrpc_ping, "ping")  # type: ignore[arg-type]
        self._xmlrpc_server.register_function(
            self._xmlrpc_get_instance_id, "get_instance_id"
        )  # type: ignore[arg-type]
        self._xmlrpc_server.register_function(self._xmlrpc_get_view, "get_view")  # type: ignore[arg-type]
        self._xmlrpc_server.register_introspection_functions()

        while self._running:
            try:
                self._xmlrpc_server.handle_request()
            except OSError:
                # Socket was closed during shutdown - this is expected
                break

    def _xmlrpc_ping(self) -> dict[str, Any]:
        """XML-RPC ping handler."""
        return {
            "pong": True,
            "timestamp": time.time(),
            "instance_id": self._instance_id,
        }

    def _xmlrpc_get_instance_id(self) -> dict[str, Any]:
        """XML-RPC get_instance_id handler.

        Returns:
            Dictionary containing the unique instance ID for this bridge.
        """
        return {"instance_id": self._instance_id}

    def _xmlrpc_execute(self, code: str) -> dict[str, Any]:
        """XML-RPC execute handler (neka-nat compatible).

        Args:
            code: Python code to execute.

        Returns:
            Execution result dictionary.
        """
        return self._execute_via_queue(code, 30000)

    # Valid view types for screenshot capture
    _VALID_VIEW_TYPES = frozenset(
        {"FitAll", "Isometric", "Front", "Back", "Top", "Bottom", "Left", "Right"}
    )

    def _xmlrpc_get_view(
        self,
        width: int = 800,
        height: int = 600,
        view_type: str = "Isometric",
    ) -> dict[str, Any]:
        """XML-RPC get_view handler for screenshots (neka-nat compatible).

        Args:
            width: Image width.
            height: Image height.
            view_type: View angle type.

        Returns:
            Dictionary with base64 image data or error.
        """
        # Validate inputs to prevent code injection
        # Type hints don't enforce at runtime, so explicit conversion is needed
        try:
            width = int(width)
            height = int(height)
        except (ValueError, TypeError) as e:
            return {"success": False, "error": f"Invalid dimensions: {e}"}

        if view_type not in self._VALID_VIEW_TYPES:
            return {
                "success": False,
                "error": f"Invalid view_type: {view_type}. "
                f"Must be one of: {', '.join(sorted(self._VALID_VIEW_TYPES))}",
            }

        code = f"""
import base64
import tempfile
import os

if not FreeCAD.GuiUp:
    _result_ = {{"success": False, "error": "GUI not available"}}
else:
    doc = FreeCAD.ActiveDocument
    if doc is None:
        _result_ = {{"success": False, "error": "No active document"}}
    else:
        view = FreeCADGui.ActiveDocument.ActiveView
        if view is None:
            _result_ = {{"success": False, "error": "No active view"}}
        else:
            # Check view type
            view_class = view.__class__.__name__
            if view_class not in ["View3DInventor", "View3DInventorPy"]:
                _result_ = {{"success": False, "error": f"Cannot capture from {{view_class}}"}}
            else:
                # Set view angle
                view_type = {view_type!r}
                if view_type == "FitAll":
                    view.fitAll()
                elif view_type == "Isometric":
                    view.viewIsometric()
                elif view_type == "Front":
                    view.viewFront()
                elif view_type == "Back":
                    view.viewRear()
                elif view_type == "Top":
                    view.viewTop()
                elif view_type == "Bottom":
                    view.viewBottom()
                elif view_type == "Left":
                    view.viewLeft()
                elif view_type == "Right":
                    view.viewRight()

                # Capture screenshot
                with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
                    temp_path = f.name

                view.saveImage(temp_path, {width}, {height}, "Current")

                with open(temp_path, "rb") as f:
                    image_data = base64.b64encode(f.read()).decode("utf-8")

                os.unlink(temp_path)

                _result_ = {{
                    "success": True,
                    "data": image_data,
                    "format": "png",
                    "width": {width},
                    "height": {height},
                }}
"""
        result = self._execute_via_queue(code, 30000)
        if result.get("success") and result.get("result"):
            return result["result"]
        return {"success": False, "error": result.get("error_message", "Unknown error")}


# Backwards compatibility
start = FreecadMCPPlugin
