import React, { useEffect, useRef, useState, useCallback } from 'react';

interface FrameHeader {
  frameId: number;
  width: number;
  height: number;
  timestamp: number;
  isFull: boolean;
}

interface VncViewerProps {
  serverUrl?: string;
  width?: number;
  height?: number;
  onConnect?: () => void;
  onDisconnect?: () => void;
  onError?: (error: Error) => void;
}

interface VncViewerState {
  isConnected: boolean;
  isConnecting: boolean;
  fps: number;
  latency: number;
  error: string | null;
}

const DEFAULT_PROPS = {
  serverUrl: '/api/vnc',
  width: 1280,
  height: 720,
};

export default function VncViewer(props: VncViewerProps) {
  const config = { ...DEFAULT_PROPS, ...props };

  const canvasRef = useRef<HTMLCanvasElement>(null);
  const offscreenCanvasRef = useRef<OffscreenCanvas | null>(null);
  const workerRef = useRef<Worker | null>(null);
  const lastFrameIdRef = useRef(0);
  const lastFrameTimeRef = useRef(0);
  const frameCountRef = useRef(0);
  const lastPingTimeRef = useRef(Date.now());
  const pollControllerRef = useRef<AbortController | null>(null);
  const inputQueueRef = useRef<Array<{ type: string; data: Record<string, unknown> }>>([]);

  const [state, setState] = useState<VncViewerState>({
    isConnected: false,
    isConnecting: false,
    fps: 0,
    latency: 0,
    error: null,
  });

  // Performance tracking
  const fpsCounterRef = useRef({
    frames: 0,
    lastTime: Date.now(),
    fps: 0,
  });

  /**
   * Send input event to server
   */
  const sendInput = useCallback(async (type: string, data: Record<string, unknown>) => {
    try {
      await fetch(`${config.serverUrl}/input`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ type, data }),
      });
    } catch (error) {
      console.error('Input error:', error);
    }
  }, [config.serverUrl]);

  /**
   * Process mouse event
   */
  const handleMouseMove = useCallback((event: React.MouseEvent<HTMLCanvasElement>) => {
    if (!canvasRef.current) return;

    const rect = canvasRef.current.getBoundingClientRect();
    const scaleX = config.width! / rect.width;
    const scaleY = config.height! / rect.height;

    const x = Math.floor((event.clientX - rect.left) * scaleX);
    const y = Math.floor((event.clientY - rect.top) * scaleY);

    sendInput('mousemove', { x, y });
  }, [config, sendInput]);

  const handleMouseDown = useCallback((event: React.MouseEvent<HTMLCanvasElement>) => {
    const buttonMap: Record<number, number> = {
      0: 1, // Left button
      1: 2, // Middle button
      2: 3, // Right button
    };

    const button = buttonMap[event.button] || 1;
    sendInput('mousedown', { button });
  }, [sendInput]);

  const handleMouseUp = useCallback((event: React.MouseEvent<HTMLCanvasElement>) => {
    const buttonMap: Record<number, number> = {
      0: 1,
      1: 2,
      2: 3,
    };

    const button = buttonMap[event.button] || 1;
    sendInput('mouseup', { button });
  }, [sendInput]);

  const handleWheel = useCallback((event: React.WheelEvent<HTMLCanvasElement>) => {
    sendInput('wheel', {
      deltaX: event.deltaX,
      deltaY: event.deltaY,
    });
  }, [sendInput]);

  /**
   * Process keyboard event
   */
  const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLCanvasElement>) => {
    // Prevent default browser shortcuts
    if (event.ctrlKey || event.altKey || event.metaKey) {
      return;
    }

    event.preventDefault();
    sendInput('keydown', {
      keyCode: event.which || event.keyCode,
      key: event.key,
      code: event.code,
    });
  }, [sendInput]);

  const handleKeyUp = useCallback((event: React.KeyboardEvent<HTMLCanvasElement>) => {
    event.preventDefault();
    sendInput('keyup', {
      keyCode: event.which || event.keyCode,
      key: event.key,
      code: event.code,
    });
  }, [sendInput]);

  /**
   * Poll for new frames
   */
  const pollFrames = useCallback(async () => {
    if (!canvasRef.current) return;

    const poll = async () => {
      try {
        const startTime = Date.now();

        const response = await fetch(
          `${config.serverUrl}/poll?lastId=${lastFrameIdRef.current}&width=${config.width}&height=${config.height}`,
          {
            signal: pollControllerRef.current?.signal,
          }
        );

        if (!response.ok) {
          throw new Error(`HTTP error: ${response.status}`);
        }

        const latency = Date.now() - startTime;
        lastPingTimeRef.current = Date.now();

        const contentType = response.headers.get('content-type');

        if (contentType?.includes('application/octet-stream')) {
          const buffer = await response.arrayBuffer();
          const data = new Uint8Array(buffer);

          if (data.length > 16) {
            // Parse frame header
            const view = new DataView(data.buffer);

            const header: FrameHeader = {
              frameId: view.getUint32(0, true),
              width: view.getUint16(4, true),
              height: view.getUint16(6, true),
              timestamp: view.getUint32(8, true),
              isFull: view.getUint8(12) === 1,
            };

            const frameData = data.slice(16);

            // Update canvas
            renderFrame(header, frameData);

            lastFrameIdRef.current = header.frameId;

            // Update FPS counter
            fpsCounterRef.current.frames++;
            const now = Date.now();
            if (now - fpsCounterRef.current.lastTime >= 1000) {
              fpsCounterRef.current.fps = fpsCounterRef.current.frames;
              fpsCounterRef.current.frames = 0;
              fpsCounterRef.current.lastTime = now;

              setState(prev => ({
                ...prev,
                fps: fpsCounterRef.current.fps,
                latency,
              }));
            }
          }
        } else {
          // JSON response (timeout or status)
          const json = await response.json();
          if (json.timeout) {
            // Continue polling
            setTimeout(poll, 50);
            return;
          }
        }

        // Continue polling immediately for smooth updates
        requestAnimationFrame(poll);
      } catch (error) {
        if (error instanceof Error && error.name === 'AbortError') {
          return; // Stop polling
        }

        console.error('Poll error:', error);
        setState(prev => ({
          ...prev,
          error: error instanceof Error ? error.message : 'Connection error',
        }));

        // Retry after delay
        setTimeout(poll, 1000);
      }
    };

    poll();
  }, [config]);

  /**
   * Render frame to canvas
   */
  const renderFrame = (header: FrameHeader, data: Uint8Array) => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    // Create or resize offscreen canvas if needed
    if (!offscreenCanvasRef.current ||
        offscreenCanvasRef.current.width !== header.width ||
        offscreenCanvasRef.current.height !== header.height) {
      offscreenCanvasRef.current = new OffscreenCanvas(header.width, header.height);
    }

    const offscreenCtx = offscreenCanvasRef.current.getContext('2d');
    if (!offscreenCtx) return;

    // Create image data
    const imageData = offscreenCtx.createImageData(header.width, header.height);

    if (header.isFull) {
      // Full frame - just copy data
      imageData.data.set(data);
    } else {
      // Diff frame - apply to existing canvas data
      const currentData = offscreenCtx.getImageData(0, 0, header.width, header.height);
      const diffView = new DataView(data.buffer);

      let offset = 0;
      while (offset < data.length) {
        const flags = data[offset];
        const startX = diffView.getUint16(offset + 1, true);
        const startY = diffView.getUint16(offset + 3, true);
        const width = diffView.getUint16(offset + 5, true);
        const height = diffView.getUint16(offset + 7, true);

        offset += 9;

        if (flags === 1) {
          // Changed region
          for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
              const srcIdx = offset + (y * width + x) * 4;
              const dstIdx = ((startY + y) * header.width + (startX + x)) * 4;

              imageData.data[dstIdx] = data[srcIdx];
              imageData.data[dstIdx + 1] = data[srcIdx + 1];
              imageData.data[dstIdx + 2] = data[srcIdx + 2];
              imageData.data[dstIdx + 3] = 255;
            }
          }
          offset += width * height * 4;
        }
      }
    }

    // Put image data to offscreen canvas
    offscreenCtx.putImageData(imageData, 0, 0);

    // Copy to main canvas with scaling
    canvas.width = header.width;
    canvas.height = header.height;
    ctx.imageSmoothingEnabled = true;
    ctx.drawImage(offscreenCanvasRef.current, 0, 0);

    lastFrameTimeRef.current = Date.now();
  };

  /**
   * Initialize VNC connection
   */
  useEffect(() => {
    setState(prev => ({ ...prev, isConnecting: true }));

    // Start polling for frames
    pollFrames();

    // Mark as connected after first frame
    const connectTimeout = setTimeout(() => {
      setState(prev => ({ ...prev, isConnected: true, isConnecting: false }));
      config.onConnect?.();
    }, 2000);

    return () => {
      clearTimeout(connectTimeout);
      pollControllerRef.current?.abort();
      config.onDisconnect?.();
    };
  }, [pollFrames, config]);

  return (
    <div className="vnc-viewer">
      <style jsx>{`
        .vnc-viewer {
          position: relative;
          width: 100%;
          height: 100vh;
          background: #0a0a0f;
          overflow: hidden;
        }

        .canvas-container {
          display: flex;
          justify-content: center;
          align-items: center;
          width: 100%;
          height: 100%;
        }

        .vnc-canvas {
          max-width: 100%;
          max-height: 100%;
          object-fit: contain;
          cursor: crosshair;
        }

        .status-overlay {
          position: absolute;
          top: 16px;
          right: 16px;
          background: rgba(0, 0, 0, 0.75);
          padding: 12px 16px;
          border-radius: 8px;
          font-family: 'SF Mono', 'Consolas', monospace;
          font-size: 12px;
          color: #10b981;
          backdrop-filter: blur(8px);
          border: 1px solid rgba(16, 185, 129, 0.2);
        }

        .status-row {
          display: flex;
          justify-content: space-between;
          gap: 24px;
          margin-bottom: 4px;
        }

        .status-row:last-child {
          margin-bottom: 0;
        }

        .status-label {
          color: #6b7280;
        }

        .status-value {
          color: #10b981;
          font-weight: 600;
        }

        .status-value.error {
          color: #ef4444;
        }

        .status-value.connecting {
          color: #f59e0b;
          animation: pulse 1s infinite;
        }

        @keyframes pulse {
          0%, 100% { opacity: 1; }
          50% { opacity: 0.5; }
        }

        .connection-indicator {
          position: absolute;
          top: 16px;
          left: 16px;
          display: flex;
          align-items: center;
          gap: 8px;
          background: rgba(0, 0, 0, 0.75);
          padding: 8px 12px;
          border-radius: 6px;
          backdrop-filter: blur(8px);
        }

        .indicator-dot {
          width: 8px;
          height: 8px;
          border-radius: 50%;
          background: #6b7280;
        }

        .indicator-dot.connected {
          background: #10b981;
          box-shadow: 0 0 8px #10b981;
        }

        .indicator-dot.connecting {
          background: #f59e0b;
          animation: pulse 1s infinite;
        }

        .indicator-dot.error {
          background: #ef4444;
        }

        .indicator-text {
          font-family: 'SF Mono', 'Consolas', monospace;
          font-size: 12px;
          color: #9ca3af;
        }

        .help-text {
          position: absolute;
          bottom: 16px;
          left: 50%;
          transform: translateX(-50%);
          background: rgba(0, 0, 0, 0.75);
          padding: 8px 16px;
          border-radius: 6px;
          font-family: 'SF Mono', 'Consolas', monospace;
          font-size: 11px;
          color: #6b7280;
          backdrop-filter: blur(8px);
        }
      `}</style>

      <div className="canvas-container">
        <canvas
          ref={canvasRef}
          className="vnc-canvas"
          width={config.width}
          height={config.height}
          onMouseMove={handleMouseMove}
          onMouseDown={handleMouseDown}
          onMouseUp={handleMouseUp}
          onMouseLeave={handleMouseUp}
          onWheel={handleWheel}
          onKeyDown={handleKeyDown}
          onKeyUp={handleKeyUp}
          tabIndex={0}
        />
      </div>

      <div className="connection-indicator">
        <div className={`indicator-dot ${
          state.isConnected ? 'connected' : state.isConnecting ? 'connecting' : 'error'
        }`} />
        <span className="indicator-text">
          {state.isConnected ? 'Connected' : state.isConnecting ? 'Connecting...' : 'Disconnected'}
        </span>
      </div>

      <div className="status-overlay">
        <div className="status-row">
          <span className="status-label">FPS</span>
          <span className="status-value">{state.fps}</span>
        </div>
        <div className="status-row">
          <span className="status-label">Latency</span>
          <span className="status-value">{state.latency}ms</span>
        </div>
        <div className="status-row">
          <span className="status-label">Resolution</span>
          <span className="status-value">{config.width}×{config.height}</span>
        </div>
        {state.error && (
          <div className="status-row">
            <span className="status-label">Status</span>
            <span className="status-value error">{state.error}</span>
          </div>
        )}
      </div>

      <div className="help-text">
        Click to focus • Mouse to navigate • Keyboard to type • Scroll to zoom
      </div>
    </div>
  );
}
