/**
 * Minimal protobuf encoder/decoder for GuruConnect messages
 *
 * For MVP, we use a simplified binary format. In production,
 * this would use a proper protobuf library like protobufjs.
 */

import type { MouseEvent, KeyEvent, MouseEventType, KeyEventType, VideoFrame, RawFrame } from '../types/protocol';

// Message type identifiers (matching proto field numbers)
const MSG_VIDEO_FRAME = 10;
const MSG_MOUSE_EVENT = 20;
const MSG_KEY_EVENT = 21;

/**
 * Encode a mouse event to binary format
 */
export function encodeMouseEvent(event: MouseEvent): Uint8Array {
  const buffer = new ArrayBuffer(32);
  const view = new DataView(buffer);

  // Message type
  view.setUint8(0, MSG_MOUSE_EVENT);

  // Event type
  view.setUint8(1, event.eventType);

  // Coordinates (scaled to 16-bit for efficiency)
  view.setInt16(2, event.x, true);
  view.setInt16(4, event.y, true);

  // Buttons bitmask
  let buttons = 0;
  if (event.buttons.left) buttons |= 1;
  if (event.buttons.right) buttons |= 2;
  if (event.buttons.middle) buttons |= 4;
  if (event.buttons.x1) buttons |= 8;
  if (event.buttons.x2) buttons |= 16;
  view.setUint8(6, buttons);

  // Wheel deltas
  view.setInt16(7, event.wheelDeltaX, true);
  view.setInt16(9, event.wheelDeltaY, true);

  return new Uint8Array(buffer, 0, 11);
}

/**
 * Encode a key event to binary format
 */
export function encodeKeyEvent(event: KeyEvent): Uint8Array {
  const buffer = new ArrayBuffer(32);
  const view = new DataView(buffer);

  // Message type
  view.setUint8(0, MSG_KEY_EVENT);

  // Key down/up
  view.setUint8(1, event.down ? 1 : 0);

  // Key type
  view.setUint8(2, event.keyType);

  // Virtual key code
  view.setUint16(3, event.vkCode, true);

  // Scan code
  view.setUint16(5, event.scanCode, true);

  // Modifiers bitmask
  let mods = 0;
  if (event.modifiers.ctrl) mods |= 1;
  if (event.modifiers.alt) mods |= 2;
  if (event.modifiers.shift) mods |= 4;
  if (event.modifiers.meta) mods |= 8;
  if (event.modifiers.capsLock) mods |= 16;
  if (event.modifiers.numLock) mods |= 32;
  view.setUint8(7, mods);

  // Unicode character (if present)
  if (event.unicode && event.unicode.length > 0) {
    const charCode = event.unicode.charCodeAt(0);
    view.setUint16(8, charCode, true);
    return new Uint8Array(buffer, 0, 10);
  }

  return new Uint8Array(buffer, 0, 8);
}

/**
 * Decode a video frame from binary format
 */
export function decodeVideoFrame(data: Uint8Array): VideoFrame | null {
  if (data.length < 2) return null;

  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  const msgType = view.getUint8(0);

  if (msgType !== MSG_VIDEO_FRAME) return null;

  const encoding = view.getUint8(1);
  const displayId = view.getUint8(2);
  const sequence = view.getUint32(3, true);
  const timestamp = Number(view.getBigInt64(7, true));

  // Frame dimensions
  const width = view.getUint16(15, true);
  const height = view.getUint16(17, true);

  // Compressed flag
  const compressed = view.getUint8(19) === 1;

  // Is keyframe
  const isKeyframe = view.getUint8(20) === 1;

  // Frame data starts at offset 21
  const frameData = data.slice(21);

  const encodingStr = ['raw', 'vp9', 'h264', 'h265'][encoding] as 'raw' | 'vp9' | 'h264' | 'h265';

  if (encodingStr === 'raw') {
    return {
      timestamp,
      displayId,
      sequence,
      encoding: 'raw',
      raw: {
        width,
        height,
        data: frameData,
        compressed,
        dirtyRects: [], // TODO: Parse dirty rects
        isKeyframe,
      },
    };
  }

  return {
    timestamp,
    displayId,
    sequence,
    encoding: encodingStr,
    encoded: {
      data: frameData,
      keyframe: isKeyframe,
      pts: timestamp,
      dts: timestamp,
    },
  };
}

/**
 * Simple zstd decompression placeholder
 * In production, use a proper zstd library like fzstd
 */
export async function decompressZstd(data: Uint8Array): Promise<Uint8Array> {
  // For MVP, assume uncompressed frames or use fzstd library
  // This is a placeholder - actual implementation would use:
  // import { decompress } from 'fzstd';
  // return decompress(data);
  return data;
}
