/**
 * React hook for managing remote desktop session connection
 */

import { useState, useEffect, useCallback, useRef } from 'react';
import type { ConnectionStatus, VideoFrame, MouseEvent as ProtoMouseEvent, KeyEvent as ProtoKeyEvent, MouseEventType, KeyEventType, Modifiers } from '../types/protocol';
import { encodeMouseEvent, encodeKeyEvent, decodeVideoFrame } from '../lib/protobuf';

interface UseRemoteSessionOptions {
  serverUrl: string;
  sessionId: string;
  onFrame?: (frame: VideoFrame) => void;
  onStatusChange?: (status: ConnectionStatus) => void;
}

interface UseRemoteSessionReturn {
  status: ConnectionStatus;
  connect: () => void;
  disconnect: () => void;
  sendMouseEvent: (event: ProtoMouseEvent) => void;
  sendKeyEvent: (event: ProtoKeyEvent) => void;
}

export function useRemoteSession(options: UseRemoteSessionOptions): UseRemoteSessionReturn {
  const { serverUrl, sessionId, onFrame, onStatusChange } = options;

  const [status, setStatus] = useState<ConnectionStatus>({
    connected: false,
  });

  const wsRef = useRef<WebSocket | null>(null);
  const reconnectTimeoutRef = useRef<number | null>(null);
  const frameCountRef = useRef(0);
  const lastFpsUpdateRef = useRef(Date.now());

  // Update status and notify
  const updateStatus = useCallback((newStatus: Partial<ConnectionStatus>) => {
    setStatus(prev => {
      const updated = { ...prev, ...newStatus };
      onStatusChange?.(updated);
      return updated;
    });
  }, [onStatusChange]);

  // Calculate FPS
  const updateFps = useCallback(() => {
    const now = Date.now();
    const elapsed = now - lastFpsUpdateRef.current;
    if (elapsed >= 1000) {
      const fps = Math.round((frameCountRef.current * 1000) / elapsed);
      updateStatus({ fps });
      frameCountRef.current = 0;
      lastFpsUpdateRef.current = now;
    }
  }, [updateStatus]);

  // Handle incoming WebSocket messages
  const handleMessage = useCallback((event: MessageEvent) => {
    if (event.data instanceof Blob) {
      event.data.arrayBuffer().then(buffer => {
        const data = new Uint8Array(buffer);
        const frame = decodeVideoFrame(data);
        if (frame) {
          frameCountRef.current++;
          updateFps();
          onFrame?.(frame);
        }
      });
    } else if (event.data instanceof ArrayBuffer) {
      const data = new Uint8Array(event.data);
      const frame = decodeVideoFrame(data);
      if (frame) {
        frameCountRef.current++;
        updateFps();
        onFrame?.(frame);
      }
    }
  }, [onFrame, updateFps]);

  // Connect to server
  const connect = useCallback(() => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      return;
    }

    // Clear any pending reconnect
    if (reconnectTimeoutRef.current) {
      window.clearTimeout(reconnectTimeoutRef.current);
      reconnectTimeoutRef.current = null;
    }

    const wsUrl = `${serverUrl}/ws/viewer?session_id=${encodeURIComponent(sessionId)}`;
    const ws = new WebSocket(wsUrl);
    ws.binaryType = 'arraybuffer';

    ws.onopen = () => {
      updateStatus({
        connected: true,
        sessionId,
      });
    };

    ws.onmessage = handleMessage;

    ws.onclose = (event) => {
      updateStatus({
        connected: false,
        latencyMs: undefined,
        fps: undefined,
      });

      // Auto-reconnect after 2 seconds
      if (!event.wasClean) {
        reconnectTimeoutRef.current = window.setTimeout(() => {
          connect();
        }, 2000);
      }
    };

    ws.onerror = () => {
      updateStatus({ connected: false });
    };

    wsRef.current = ws;
  }, [serverUrl, sessionId, handleMessage, updateStatus]);

  // Disconnect from server
  const disconnect = useCallback(() => {
    if (reconnectTimeoutRef.current) {
      window.clearTimeout(reconnectTimeoutRef.current);
      reconnectTimeoutRef.current = null;
    }

    if (wsRef.current) {
      wsRef.current.close(1000, 'User disconnected');
      wsRef.current = null;
    }

    updateStatus({
      connected: false,
      sessionId: undefined,
      latencyMs: undefined,
      fps: undefined,
    });
  }, [updateStatus]);

  // Send mouse event
  const sendMouseEvent = useCallback((event: ProtoMouseEvent) => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      const data = encodeMouseEvent(event);
      wsRef.current.send(data);
    }
  }, []);

  // Send key event
  const sendKeyEvent = useCallback((event: ProtoKeyEvent) => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      const data = encodeKeyEvent(event);
      wsRef.current.send(data);
    }
  }, []);

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      disconnect();
    };
  }, [disconnect]);

  return {
    status,
    connect,
    disconnect,
    sendMouseEvent,
    sendKeyEvent,
  };
}

/**
 * Helper to create mouse event from DOM mouse event
 */
export function createMouseEvent(
  domEvent: React.MouseEvent<HTMLElement>,
  canvasRect: DOMRect,
  displayWidth: number,
  displayHeight: number,
  eventType: MouseEventType
): ProtoMouseEvent {
  // Calculate position relative to canvas and scale to display coordinates
  const scaleX = displayWidth / canvasRect.width;
  const scaleY = displayHeight / canvasRect.height;

  const x = Math.round((domEvent.clientX - canvasRect.left) * scaleX);
  const y = Math.round((domEvent.clientY - canvasRect.top) * scaleY);

  return {
    x,
    y,
    buttons: {
      left: (domEvent.buttons & 1) !== 0,
      right: (domEvent.buttons & 2) !== 0,
      middle: (domEvent.buttons & 4) !== 0,
      x1: (domEvent.buttons & 8) !== 0,
      x2: (domEvent.buttons & 16) !== 0,
    },
    wheelDeltaX: 0,
    wheelDeltaY: 0,
    eventType,
  };
}

/**
 * Helper to create key event from DOM keyboard event
 */
export function createKeyEvent(
  domEvent: React.KeyboardEvent<HTMLElement>,
  down: boolean
): ProtoKeyEvent {
  const modifiers: Modifiers = {
    ctrl: domEvent.ctrlKey,
    alt: domEvent.altKey,
    shift: domEvent.shiftKey,
    meta: domEvent.metaKey,
    capsLock: domEvent.getModifierState('CapsLock'),
    numLock: domEvent.getModifierState('NumLock'),
  };

  // Use key code for special keys, unicode for regular characters
  const isCharacter = domEvent.key.length === 1;

  return {
    down,
    keyType: isCharacter ? 2 : 0, // KEY_UNICODE or KEY_VK
    vkCode: domEvent.keyCode,
    scanCode: 0, // Not available in browser
    unicode: isCharacter ? domEvent.key : undefined,
    modifiers,
  };
}
