// Embed edit-surface: with ?edit=1 the game becomes a remote-controlled 3D
// canvas for a parent editor page (see editor-slice/PROTOCOL.md). The editor
// owns the doc and all placement rules; this module only renders the last
// received doc, translates pointer input to grid cells, and keeps pan/zoom
// local (only the game knows its camera).
import * as THREE from 'three';
import type { Game } from './game';
import type { Cell } from './level';
import { parseDoc } from './leveled/doc';
import { docToLevel, skitterSpec } from './adapter';

// Hover-ghost tint per tool (palette swatch colors; gradients reduced to their core).
const TOOL_COLORS: Record<string, number> = {
  'wall': 0x3c4356,
  'floor-glow': 0xc22413,
  'spawn': 0x5fe0ff,
  'enemy': 0xff8a3c,
  'turret': 0xffaa22,
  'gigabot': 0xff3030,
  'tree': 0x5dff9a,
};

// Keys forwarded to the editor (plus KeyZ/KeyY with ctrl/meta) per the protocol.
const FORWARD_KEYS = new Set(['Escape', 'KeyF', 'Delete', 'Backspace']);

function asCell(v: unknown): Cell | null {
  if (!Array.isArray(v) || v.length !== 2) return null;
  const [x, y] = v as unknown[];
  if (typeof x !== 'number' || typeof y !== 'number' || !Number.isFinite(x) || !Number.isFinite(y)) return null;
  return [Math.floor(x), Math.floor(y)];
}

export function initEmbedEditing(game: Game): void {
  const post = (type: string, payload: Record<string, unknown> = {}): void => {
    window.parent.postMessage({ type, ...payload }, '*');
  };
  const editing = (): boolean => game.mode === 'editor';

  // ---------- overlays ----------
  // The Game draws its own generic cyan hover cursor in editor mode; the
  // per-tool ghost below replaces it, so blank it out. (The frame loop only
  // touches its .visible/.position, never the material.)
  const builtinGhost = (game as unknown as { ghost: THREE.Mesh }).ghost;
  (builtinGhost.material as THREE.MeshBasicMaterial).opacity = 0;

  // hover ghost for an armed entity tool: translucent tinted slab on the cell
  const ghostMat = new THREE.MeshBasicMaterial({
    color: 0x33ddff,
    transparent: true,
    opacity: 0.42,
    depthWrite: false,
    toneMapped: false,
  });
  const ghost = new THREE.Mesh(new THREE.BoxGeometry(0.96, 0.12, 0.96), ghostMat);
  ghost.visible = false;
  game.scene.add(ghost);

  // hover highlight for the "select" tool: thin white wireframe outline
  const hoverOutline = new THREE.LineSegments(
    new THREE.EdgesGeometry(new THREE.BoxGeometry(1, 0.03, 1)),
    new THREE.LineBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.85, toneMapped: false }),
  );
  hoverOutline.visible = false;
  game.scene.add(hoverOutline);

  // persistent selection highlight (edit:selection): white outline + faint fill
  const selection = new THREE.Group();
  selection.add(
    new THREE.LineSegments(
      new THREE.EdgesGeometry(new THREE.BoxGeometry(1.04, 0.05, 1.04)),
      new THREE.LineBasicMaterial({ color: 0xffffff, toneMapped: false }),
    ),
  );
  const selFill = new THREE.Mesh(
    new THREE.PlaneGeometry(0.98, 0.98),
    new THREE.MeshBasicMaterial({
      color: 0xffffff,
      transparent: true,
      opacity: 0.12,
      depthWrite: false,
      toneMapped: false,
    }),
  );
  selFill.rotation.x = -Math.PI / 2;
  selection.add(selFill);
  selection.visible = false;
  game.scene.add(selection);

  let tool = ''; // armed tool from edit:tool ('' until the editor sends one)
  let hoverCell: Cell | null = null;
  let selectedCell: Cell | null = null;

  const placeAt = (obj: THREE.Object3D, cell: Cell, y: number): void => {
    game.cellToWorld(cell, obj.position);
    obj.position.y = y;
  };

  const refreshHover = (): void => {
    const cell = editing() ? hoverCell : null;
    hoverOutline.visible = tool === 'select' && cell !== null;
    ghost.visible = tool !== '' && tool !== 'select' && cell !== null;
    if (!cell) return;
    if (hoverOutline.visible) placeAt(hoverOutline, cell, 0.08);
    if (ghost.visible) {
      ghostMat.color.setHex(TOOL_COLORS[tool] ?? 0x33ddff);
      placeAt(ghost, cell, 0.1);
    }
  };

  const refreshSelection = (): void => {
    selection.visible = editing() && selectedCell !== null;
    if (selectedCell) placeAt(selection, selectedCell, 0.06);
  };

  game.onModeChange = () => {
    refreshHover();
    refreshSelection();
  };

  // ---------- editor -> game ----------
  window.addEventListener('message', (e: MessageEvent) => {
    const d = e.data as { type?: unknown } | null;
    if (!d || typeof d.type !== 'string') return;
    switch (d.type) {
      case 'edit:doc':
        try {
          game.level = docToLevel(parseDoc((d as { doc?: unknown }).doc, skitterSpec));
          game.refreshLevel();
          refreshSelection();
        } catch (err) {
          console.warn('[skitterdome] ignored bad edit:doc', err);
        }
        break;
      case 'edit:tool':
        tool = typeof (d as { tool?: unknown }).tool === 'string' ? ((d as { tool: string }).tool) : '';
        refreshHover();
        break;
      case 'edit:selection':
        selectedCell = asCell((d as { cell?: unknown }).cell);
        refreshSelection();
        break;
      case 'edit:fit':
        game.fitEditorCamera();
        break;
      case 'edit:exit':
        game.setMode('play'); // in-place playtest of the current doc
        break;
      case 'edit:enter':
        game.setMode('editor');
        break;
    }
  });

  // ---------- gestures (transplanted from the Leveled canvas bindings) ----------
  const el = game.renderer.domElement;
  let panning = false;
  let panPointerId = -1;
  let panLastX = 0;
  let panLastY = 0;
  let spaceHeld = false;

  const cellAt = (e: { clientX: number; clientY: number }): Cell | null => {
    game.updateNdc(e.clientX, e.clientY);
    return game.pointerCell();
  };
  const postCell = (kind: 'down' | 'move' | 'up', cell: Cell | null, e: PointerEvent): void => {
    post('edit:cell', { kind, cell, button: e.button, buttons: e.buttons });
  };

  el.addEventListener('contextmenu', (e) => {
    if (editing()) e.preventDefault();
  });

  el.addEventListener('pointerdown', (e) => {
    if (!editing()) return;
    // middle-drag or hold-Space + left-drag pans the camera (stays local)
    if (e.button === 1 || (e.button === 0 && spaceHeld)) {
      e.preventDefault();
      panning = true;
      panPointerId = e.pointerId;
      panLastX = e.clientX;
      panLastY = e.clientY;
      el.setPointerCapture(e.pointerId);
      return;
    }
    postCell('down', cellAt(e), e);
  });

  el.addEventListener('pointermove', (e) => {
    if (!editing()) return;
    if (panning && e.pointerId === panPointerId) {
      game.panEditor(e.clientX - panLastX, e.clientY - panLastY);
      panLastX = e.clientX;
      panLastY = e.clientY;
      return;
    }
    hoverCell = cellAt(e);
    refreshHover();
    postCell('move', hoverCell, e);
  });

  el.addEventListener('pointerleave', () => {
    hoverCell = null;
    refreshHover();
  });

  window.addEventListener('pointerup', (e) => {
    if (panning) {
      panning = false;
      return;
    }
    if (!editing()) return;
    postCell('up', cellAt(e), e);
  });

  el.addEventListener(
    'wheel',
    (e) => {
      if (!editing()) return;
      e.preventDefault();
      game.zoomEditor(e.deltaY);
    },
    { passive: false },
  );

  // ---------- keys ----------
  // Capture phase so we run before the Game's own window keydown listener:
  // in embed mode the editor owns mode switching, so swallow the E toggle.
  window.addEventListener(
    'keydown',
    (e) => {
      const t = e.target as HTMLElement | null;
      if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT')) return;
      if (e.code === 'KeyE') {
        e.stopPropagation();
        return;
      }
      if (!editing()) return;
      if (e.code === 'Space') {
        spaceHeld = true;
        e.preventDefault();
      } else if ((e.ctrlKey || e.metaKey) && (e.code === 'KeyZ' || e.code === 'KeyY')) {
        e.preventDefault();
        post('edit:key', { code: e.code, ctrl: true, shift: e.shiftKey });
      } else if (FORWARD_KEYS.has(e.code)) {
        e.preventDefault();
        post('edit:key', { code: e.code, ctrl: e.ctrlKey || e.metaKey, shift: e.shiftKey });
      }
    },
    true,
  );
  window.addEventListener('keyup', (e) => {
    if (e.code === 'Space') spaceHeld = false;
  });
  window.addEventListener('blur', () => {
    spaceHeld = false;
    panning = false;
  });

  // ---------- boot ----------
  game.setMode('editor');
  post('edit:ready');
}
