import { ref, onBeforeUnmount } from "vue";
import { Call } from "@wailsio/runtime";

const APP = "github.com/vstockwell/wraith/internal/app.WraithApp";

/**
 * RDP mouse event flags — match the Go constants in internal/rdp/input.go
 */
export const MouseFlag = {
  Move: 0x0800,
  Button1: 0x1000, // Left
  Button2: 0x2000, // Right
  Button3: 0x4000, // Middle
  Down: 0x8000,
  Wheel: 0x0200,
  WheelNeg: 0x0100,
  HWheel: 0x0400,
} as const;

/**
 * JavaScript KeyboardEvent.code → RDP scancode mapping.
 * Mirrors the Go ScancodeMap in internal/rdp/input.go.
 */
export const ScancodeMap: Record<string, number> = {
  // Row 0: Escape + Function keys
  Escape: 0x0001,
  F1: 0x003b,
  F2: 0x003c,
  F3: 0x003d,
  F4: 0x003e,
  F5: 0x003f,
  F6: 0x0040,
  F7: 0x0041,
  F8: 0x0042,
  F9: 0x0043,
  F10: 0x0044,
  F11: 0x0057,
  F12: 0x0058,

  // Row 1: Number row
  Backquote: 0x0029,
  Digit1: 0x0002,
  Digit2: 0x0003,
  Digit3: 0x0004,
  Digit4: 0x0005,
  Digit5: 0x0006,
  Digit6: 0x0007,
  Digit7: 0x0008,
  Digit8: 0x0009,
  Digit9: 0x000a,
  Digit0: 0x000b,
  Minus: 0x000c,
  Equal: 0x000d,
  Backspace: 0x000e,

  // Row 2: QWERTY
  Tab: 0x000f,
  KeyQ: 0x0010,
  KeyW: 0x0011,
  KeyE: 0x0012,
  KeyR: 0x0013,
  KeyT: 0x0014,
  KeyY: 0x0015,
  KeyU: 0x0016,
  KeyI: 0x0017,
  KeyO: 0x0018,
  KeyP: 0x0019,
  BracketLeft: 0x001a,
  BracketRight: 0x001b,
  Backslash: 0x002b,

  // Row 3: Home row
  CapsLock: 0x003a,
  KeyA: 0x001e,
  KeyS: 0x001f,
  KeyD: 0x0020,
  KeyF: 0x0021,
  KeyG: 0x0022,
  KeyH: 0x0023,
  KeyJ: 0x0024,
  KeyK: 0x0025,
  KeyL: 0x0026,
  Semicolon: 0x0027,
  Quote: 0x0028,
  Enter: 0x001c,

  // Row 4: Bottom row
  ShiftLeft: 0x002a,
  KeyZ: 0x002c,
  KeyX: 0x002d,
  KeyC: 0x002e,
  KeyV: 0x002f,
  KeyB: 0x0030,
  KeyN: 0x0031,
  KeyM: 0x0032,
  Comma: 0x0033,
  Period: 0x0034,
  Slash: 0x0035,
  ShiftRight: 0x0036,

  // Row 5: Bottom modifiers + space
  ControlLeft: 0x001d,
  MetaLeft: 0xe05b,
  AltLeft: 0x0038,
  Space: 0x0039,
  AltRight: 0xe038,
  MetaRight: 0xe05c,
  ContextMenu: 0xe05d,
  ControlRight: 0xe01d,

  // Navigation cluster
  PrintScreen: 0xe037,
  ScrollLock: 0x0046,
  Pause: 0x0045,
  Insert: 0xe052,
  Home: 0xe047,
  PageUp: 0xe049,
  Delete: 0xe053,
  End: 0xe04f,
  PageDown: 0xe051,

  // Arrow keys
  ArrowUp: 0xe048,
  ArrowLeft: 0xe04b,
  ArrowDown: 0xe050,
  ArrowRight: 0xe04d,

  // Numpad
  NumLock: 0x0045,
  NumpadDivide: 0xe035,
  NumpadMultiply: 0x0037,
  NumpadSubtract: 0x004a,
  Numpad7: 0x0047,
  Numpad8: 0x0048,
  Numpad9: 0x0049,
  NumpadAdd: 0x004e,
  Numpad4: 0x004b,
  Numpad5: 0x004c,
  Numpad6: 0x004d,
  Numpad1: 0x004f,
  Numpad2: 0x0050,
  Numpad3: 0x0051,
  NumpadEnter: 0xe01c,
  Numpad0: 0x0052,
  NumpadDecimal: 0x0053,
};

/**
 * Look up the RDP scancode for a JS KeyboardEvent.code string.
 */
export function jsKeyToScancode(code: string): number | null {
  return ScancodeMap[code] ?? null;
}

export interface UseRdpReturn {
  /** Whether the RDP session is connected (mock: always true after init) */
  connected: ReturnType<typeof ref<boolean>>;
  /** Whether keyboard capture is enabled */
  keyboardGrabbed: ReturnType<typeof ref<boolean>>;
  /** Whether clipboard sync is enabled */
  clipboardSync: ReturnType<typeof ref<boolean>>;
  /** Fetch the current frame as RGBA ImageData */
  fetchFrame: (sessionId: string) => Promise<ImageData | null>;
  /** Send a mouse event to the backend */
  sendMouse: (
    sessionId: string,
    x: number,
    y: number,
    flags: number,
  ) => void;
  /** Send a key event to the backend */
  sendKey: (sessionId: string, code: string, pressed: boolean) => void;
  /** Send clipboard text to the remote session */
  sendClipboard: (sessionId: string, text: string) => void;
  /** Start the frame rendering loop */
  startFrameLoop: (
    sessionId: string,
    canvas: HTMLCanvasElement,
    width: number,
    height: number,
  ) => void;
  /** Stop the frame rendering loop */
  stopFrameLoop: () => void;
  /** Toggle keyboard grab */
  toggleKeyboardGrab: () => void;
  /** Toggle clipboard sync */
  toggleClipboardSync: () => void;
}

/**
 * Composable that manages an RDP session's rendering and input.
 *
 * All backend calls are currently stubs that will be replaced with
 * Wails bindings once the Go RDP service is wired up.
 */
export function useRdp(): UseRdpReturn {
  const connected = ref(false);
  const keyboardGrabbed = ref(false);
  const clipboardSync = ref(false);

  let animFrameId: number | null = null;
  let frameCount = 0;

  /**
   * Fetch the current frame from the Go RDP backend.
   *
   * Go's GetFrame returns []byte (raw RGBA, Width*Height*4 bytes).
   * Wails serialises Go []byte as a base64-encoded string over the JSON bridge,
   * so we decode it back to a Uint8ClampedArray and wrap it in an ImageData.
   */
  async function fetchFrame(
    sessionId: string,
    width = 1920,
    height = 1080,
  ): Promise<ImageData | null> {
    let raw: string;
    try {
      raw = (await Call.ByName(`${APP}.RDPGetFrame`, sessionId)) as string;
    } catch {
      // Session may not be connected yet or backend returned an error — skip frame
      return null;
    }

    if (!raw) return null;

    // Decode base64 → binary string → Uint8ClampedArray
    const binaryStr = atob(raw);
    const bytes = new Uint8ClampedArray(binaryStr.length);
    for (let i = 0; i < binaryStr.length; i++) {
      bytes[i] = binaryStr.charCodeAt(i);
    }

    // Validate: RGBA requires exactly width * height * 4 bytes
    const expected = width * height * 4;
    if (bytes.length !== expected) {
      console.warn(
        `[useRdp] Frame size mismatch: got ${bytes.length}, expected ${expected}`,
      );
      return null;
    }

    return new ImageData(bytes, width, height);
  }

  /**
   * Send a mouse event to the remote session.
   * Calls Go WraithApp.RDPSendMouse(sessionId, x, y, flags).
   * Fire-and-forget — mouse events are best-effort.
   */
  function sendMouse(
    sessionId: string,
    x: number,
    y: number,
    flags: number,
  ): void {
    Call.ByName(`${APP}.RDPSendMouse`, sessionId, x, y, flags).catch(
      (err: unknown) => {
        console.warn("[useRdp] sendMouse failed:", err);
      },
    );
  }

  /**
   * Send a key event, mapping the JS KeyboardEvent.code to an RDP scancode.
   * Calls Go WraithApp.RDPSendKey(sessionId, scancode, pressed).
   * Unmapped keys are silently dropped — not every JS key has an RDP scancode.
   */
  function sendKey(
    sessionId: string,
    code: string,
    pressed: boolean,
  ): void {
    const scancode = jsKeyToScancode(code);
    if (scancode === null) return;

    Call.ByName(`${APP}.RDPSendKey`, sessionId, scancode, pressed).catch(
      (err: unknown) => {
        console.warn("[useRdp] sendKey failed:", err);
      },
    );
  }

  /**
   * Send clipboard text to the remote RDP session.
   * Calls Go WraithApp.RDPSendClipboard(sessionId, text).
   */
  function sendClipboard(sessionId: string, text: string): void {
    Call.ByName(`${APP}.RDPSendClipboard`, sessionId, text).catch(
      (err: unknown) => {
        console.warn("[useRdp] sendClipboard failed:", err);
      },
    );
  }

  /**
   * Start the rendering loop. Fetches frames and draws them on the canvas
   * using requestAnimationFrame.
   */
  function startFrameLoop(
    sessionId: string,
    canvas: HTMLCanvasElement,
    width: number,
    height: number,
  ): void {
    connected.value = true;
    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    canvas.width = width;
    canvas.height = height;

    function renderLoop(): void {
      frameCount++;

      // Throttle to ~30fps (skip every other frame at 60fps rAF)
      if (frameCount % 2 === 0) {
        fetchFrame(sessionId, width, height).then((imageData) => {
          if (imageData && ctx) {
            ctx.putImageData(imageData, 0, 0);
          }
        });
      }

      animFrameId = requestAnimationFrame(renderLoop);
    }

    animFrameId = requestAnimationFrame(renderLoop);
  }

  /**
   * Stop the rendering loop.
   */
  function stopFrameLoop(): void {
    if (animFrameId !== null) {
      cancelAnimationFrame(animFrameId);
      animFrameId = null;
    }
    connected.value = false;
  }

  function toggleKeyboardGrab(): void {
    keyboardGrabbed.value = !keyboardGrabbed.value;
  }

  function toggleClipboardSync(): void {
    clipboardSync.value = !clipboardSync.value;
  }

  onBeforeUnmount(() => {
    stopFrameLoop();
  });

  return {
    connected,
    keyboardGrabbed,
    clipboardSync,
    fetchFrame,
    sendMouse,
    sendKey,
    sendClipboard,
    startFrameLoop,
    stopFrameLoop,
    toggleKeyboardGrab,
    toggleClipboardSync,
  };
}
