"""Unified Visual Interface - Natural language + real-time visualization.

Combines:
- Unified Interface (natural language via phi4)
- Visual Workflow Server (WebSocket + node graph)

When you speak, the visualization updates in real-time.

Run: python -m cubesquare.unified_visual
"""
import sys
import io
import asyncio
import threading
import webbrowser
from pathlib import Path
from http.server import HTTPServer, SimpleHTTPRequestHandler

sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')

try:
    import websockets
    # websockets 15.x: use top-level serve (legacy server.serve is deprecated)
    ws_serve = websockets.serve
    HAS_WEBSOCKETS = True
except ImportError:
    HAS_WEBSOCKETS = False

from .unified import UnifiedInterface
from .visual.server import WorkflowServer
from .visual.events import get_event_bus, EventType, Event


class UnifiedVisualInterface:
    """Combined natural language + visual interface.

    - You type naturally in the terminal
    - phi4 understands and routes
    - The browser shows nodes, agents, data flow in real-time
    """

    def __init__(self, name: str = "unified_visual",
                 ws_port: int = 8765, http_port: int = 8766):
        self.name = name
        self.ws_port = ws_port
        self.http_port = http_port

        # Shared event bus
        self.event_bus = get_event_bus()

        # Initialize unified interface (uses same event bus)
        self.unified = UnifiedInterface(name=name)

        # Initialize visual server (uses same event bus)
        self.visual = WorkflowServer(port=ws_port)

        # WebSocket clients
        self.clients = set()
        self._ws_ready = False

    def _run_http_server(self):
        """Run HTTP server for frontend in background thread."""
        frontend_dir = Path(__file__).parent / "visual" / "frontend"

        class Handler(SimpleHTTPRequestHandler):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, directory=str(frontend_dir), **kwargs)
            def log_message(self, format, *args):
                pass  # Suppress logs

        for attempt in range(5):
            port = self.http_port + attempt
            try:
                server = HTTPServer(('localhost', port), Handler)
                if attempt > 0:
                    self.http_port = port
                server.serve_forever()
                return
            except OSError:
                continue

    async def _handle_ws_client(self, websocket):
        """Handle WebSocket client."""
        self.clients.add(websocket)
        try:
            # Send initial state
            state = self.visual.action_get_state()
            await websocket.send(__import__('json').dumps({
                'type': 'state',
                **state,
            }))

            # Keep connection alive
            async for message in websocket:
                # Handle any client messages if needed
                pass
        finally:
            self.clients.discard(websocket)

    def _broadcast_event(self, event: Event):
        """Broadcast event to all WebSocket clients."""
        if not self.clients:
            return

        import json
        message = json.dumps({
            'type': 'event',
            'event': event.to_dict(),
        })

        for client in list(self.clients):
            asyncio.run_coroutine_threadsafe(
                self._send_to_client(client, message),
                self._loop
            )

    async def _send_to_client(self, client, message: str):
        """Send to client safely."""
        try:
            await client.send(message)
        except:
            self.clients.discard(client)

    async def _run_ws_server(self):
        """Run WebSocket server with port-in-use retry."""
        for attempt in range(5):
            port = self.ws_port + attempt
            try:
                async with ws_serve(self._handle_ws_client, "localhost", port):
                    if attempt > 0:
                        self.ws_port = port
                    self._ws_ready = True
                    await asyncio.Future()
            except OSError as e:
                if e.errno in (10048, 98):  # EADDRINUSE (Windows / Linux)
                    continue
                raise
        raise OSError(f"Could not bind WebSocket on ports {self.ws_port}-{self.ws_port+4}")

    def _run_visual_servers(self):
        """Run both HTTP and WebSocket servers."""
        # Start HTTP server in thread
        http_thread = threading.Thread(target=self._run_http_server, daemon=True)
        http_thread.start()

        # Subscribe to events for broadcasting
        self.event_bus.subscribe(self._broadcast_event)

        # Run WebSocket server
        self._loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self._loop)
        try:
            self._loop.run_until_complete(self._run_ws_server())
        except OSError as e:
            print(f"  WebSocket server error: {e}")
            print(f"  Try: Stop previous instances or use --ws-port <port>")

    def run(self):
        """Run the combined interface."""
        if not HAS_WEBSOCKETS:
            print("WARNING: websockets not installed. Visual server disabled.")
            print("Install with: pip install websockets")
            print()
            self.unified.run()
            return

        # Start visual servers in background thread
        visual_thread = threading.Thread(target=self._run_visual_servers, daemon=True)
        visual_thread.start()

        # Give servers time to start
        import time
        time.sleep(1)

        # Open browser
        url = f"http://localhost:{self.http_port}"
        webbrowser.open(url)

        # Print header
        print()
        print("=" * 60)
        print("  CUBESQUARE UNIFIED VISUAL INTERFACE")
        print("=" * 60)
        print()
        print(f"  Visual:    {url}")
        print(f"  WebSocket: ws://localhost:{self.ws_port}")
        print(f"  Sphere:    {self.unified.sphere_mind.sphere_id}")
        print(f"  LLM:       phi4-mini (ready)")
        print()
        print("  Speak naturally. Watch the visualization update.")
        print("  Commands: 'exit' to quit, 'reflect' to see state")
        print()
        print("=" * 60)
        print()

        # Run interactive loop
        while True:
            try:
                user_input = input("you > ").strip()

                if not user_input:
                    continue

                if user_input.lower() == "exit":
                    print("Saving state...")
                    self.unified.sphere_mind.save()
                    break

                if user_input.lower() == "reflect":
                    print()
                    print(self.unified.sphere_mind.reflect())
                    print()
                    continue

                # Process through unified interface
                # (this emits events that go to visualization)
                result = self.unified.process(user_input)

                # Display result in terminal
                print()
                print(f"  [{result['coordinates']['hex_id']}] "
                      f"B{result['coordinates']['basin']}/L{result['coordinates']['layer']} "
                      f"({result['coordinates']['i_state']})")
                print()
                print(f"  Understanding: {result['understanding']['semantic'][:80]}...")
                print(f"  Agent: {result['dispatch']['agent']} -> {result['dispatch']['outcome']}")
                print(f"  Thinking: {result['thinking']['state']}")
                print()

            except (EOFError, KeyboardInterrupt):
                print("\nExiting...")
                self.unified.sphere_mind.save()
                break


def main():
    """Entry point."""
    name = "unified_visual"
    ws_port = 8765
    http_port = 8766

    # Parse args
    args = sys.argv[1:]
    for i, arg in enumerate(args):
        if arg == "--name" and i + 1 < len(args):
            name = args[i + 1]
        elif arg == "--ws-port" and i + 1 < len(args):
            ws_port = int(args[i + 1])
        elif arg == "--http-port" and i + 1 < len(args):
            http_port = int(args[i + 1])

    interface = UnifiedVisualInterface(
        name=name,
        ws_port=ws_port,
        http_port=http_port,
    )
    interface.run()


if __name__ == "__main__":
    main()
