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

import type { WhiteboardStroke } from '@chat-app/shared/chat';

import { useAuth } from '../context/AuthContext';
import { openCursorSession, type CursorEvent, type CursorSession } from '../lib/whiteboardCursors';

export type WhiteboardTool = 'pen' | 'eraser';
export type WhiteboardColor = '#000000' | '#ef4444' | '#3b82f6' | '#22c55e' | '#facc15' | '#a855f7';
export type WhiteboardWidth = 2 | 4 | 8;

export interface WhiteboardStrokePayload {
  tool: WhiteboardTool;
  color: WhiteboardColor;
  width: WhiteboardWidth;
  // [x, y, t-ms-since-stroke-start]
  points: Array<[number, number, number]>;
}

interface Props {
  strokes: WhiteboardStroke[];
  tool: WhiteboardTool;
  color: WhiteboardColor;
  width: WhiteboardWidth;
  onStroke: (payload: WhiteboardStrokePayload) => void;
  logicalWidth?: number;
  logicalHeight?: number;
  /** Enables live-cursor broadcast when set. */
  whiteboardId?: string | null;
}

const DEFAULT_LOGICAL_W = 1280;
const DEFAULT_LOGICAL_H = 720;

export function WhiteboardCanvas({
  strokes,
  tool,
  color,
  width,
  onStroke,
  logicalWidth = DEFAULT_LOGICAL_W,
  logicalHeight = DEFAULT_LOGICAL_H,
  whiteboardId,
}: Props) {
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const draftRef = useRef<WhiteboardStrokePayload | null>(null);
  const strokeStartRef = useRef<number>(0);
  const [, forceTick] = useState(0);

  const { session, profile } = useAuth();
  const [remoteCursors, setRemoteCursors] = useState<Map<string, CursorEvent & { lastSeen: number }>>(
    () => new Map(),
  );
  const cursorSessionRef = useRef<CursorSession | null>(null);

  useEffect(() => {
    if (!whiteboardId) return;
    const me = session?.user;
    if (!me) return;
    const displayName = profile?.displayName ?? me.email ?? me.id.slice(0, 8);
    const s = openCursorSession(
      whiteboardId,
      { userId: me.id, displayName },
      (ev) => {
        setRemoteCursors((prev) => {
          const next = new Map(prev);
          next.set(ev.userId, { ...ev, lastSeen: Date.now() });
          return next;
        });
      },
    );
    cursorSessionRef.current = s;
    return () => {
      s.close();
      cursorSessionRef.current = null;
    };
  }, [whiteboardId, session?.user, profile?.displayName]);

  // Stale-cursor sweep: drop cursors that haven't been heard from in 2s. Cheap
  // poll because the Map is tiny (at most one entry per active collaborator).
  useEffect(() => {
    if (remoteCursors.size === 0) return;
    const id = setInterval(() => {
      const now = Date.now();
      setRemoteCursors((prev) => {
        let changed = false;
        const next = new Map(prev);
        for (const [k, v] of next) {
          if (now - v.lastSeen > 2000) {
            next.delete(k);
            changed = true;
          }
        }
        return changed ? next : prev;
      });
    }, 1000);
    return () => clearInterval(id);
  }, [remoteCursors.size]);

  useEffect(() => {
    const cv = canvasRef.current;
    if (!cv) return;
    cv.width = logicalWidth;
    cv.height = logicalHeight;
    const ctx = cv.getContext('2d');
    if (!ctx) return;
    ctx.fillStyle = '#ffffff';
    ctx.fillRect(0, 0, cv.width, cv.height);
    for (const s of strokes) {
      const payload = s.strokeJson as Partial<WhiteboardStrokePayload> | null;
      if (payload) renderStroke(ctx, payload);
    }
    if (draftRef.current) renderStroke(ctx, draftRef.current);
  });

  function canvasPoint(e: React.PointerEvent<HTMLCanvasElement>): [number, number] {
    const cv = canvasRef.current!;
    const rect = cv.getBoundingClientRect();
    const scaleX = cv.width / rect.width;
    const scaleY = cv.height / rect.height;
    return [(e.clientX - rect.left) * scaleX, (e.clientY - rect.top) * scaleY];
  }

  const handlePointerDown = (e: React.PointerEvent<HTMLCanvasElement>) => {
    const cv = canvasRef.current;
    if (!cv) return;
    cv.setPointerCapture(e.pointerId);
    const [x, y] = canvasPoint(e);
    strokeStartRef.current = Date.now();
    draftRef.current = {
      tool,
      color,
      width,
      points: [[x, y, 0]],
    };
    forceTick((n) => n + 1);
  };

  const handlePointerMove = (e: React.PointerEvent<HTMLCanvasElement>) => {
    const [x, y] = canvasPoint(e);
    cursorSessionRef.current?.send(x, y);
    if (!draftRef.current) return;
    draftRef.current.points.push([x, y, Date.now() - strokeStartRef.current]);
    forceTick((n) => n + 1);
  };

  const handlePointerUp = (e: React.PointerEvent<HTMLCanvasElement>) => {
    const cv = canvasRef.current;
    if (cv && cv.hasPointerCapture(e.pointerId)) cv.releasePointerCapture(e.pointerId);
    const draft = draftRef.current;
    draftRef.current = null;
    if (!draft) return;
    if (draft.points.length < 2) {
      forceTick((n) => n + 1);
      return;
    }
    onStroke(draft);
    forceTick((n) => n + 1);
  };

  return (
    <div
      className="relative"
      style={{ aspectRatio: logicalWidth + ' / ' + logicalHeight, width: '100%' }}
    >
      <canvas
        ref={canvasRef}
        onPointerDown={handlePointerDown}
        onPointerMove={handlePointerMove}
        onPointerUp={handlePointerUp}
        onPointerLeave={handlePointerUp}
        className="block max-h-full max-w-full cursor-crosshair touch-none rounded-lg border border-line/40 bg-white shadow-2xl"
        style={{ width: '100%', height: '100%' }}
      />
      {Array.from(remoteCursors.values()).map((c) => {
        const pctX = (c.x / logicalWidth) * 100;
        const pctY = (c.y / logicalHeight) * 100;
        return (
          <div
            key={c.userId}
            aria-hidden="true"
            className="pointer-events-none absolute"
            style={{ left: pctX + '%', top: pctY + '%', transform: 'translate(-2px, -2px)' }}
          >
            <span
              className="block h-2 w-2 rounded-full border-2 border-white shadow"
              style={{ backgroundColor: colorForUserId(c.userId) }}
            />
            <span className="ml-2 inline-block translate-y-[-2px] rounded-full bg-black/60 px-1.5 py-0.5 text-[9px] font-semibold text-white">
              {c.displayName}
            </span>
          </div>
        );
      })}
    </div>
  );
}

function colorForUserId(userId: string): string {
  // Deterministic hue from the user id so each collaborator gets a stable
  // colour across sessions. Saturation/lightness fixed to keep the cursor
  // legible against the white canvas.
  let hash = 0;
  for (let i = 0; i < userId.length; i++) hash = (hash * 31 + userId.charCodeAt(i)) | 0;
  const hue = Math.abs(hash) % 360;
  return 'hsl(' + hue + ', 70%, 50%)';
}

function renderStroke(
  ctx: CanvasRenderingContext2D,
  s: Partial<WhiteboardStrokePayload>,
): void {
  const points = Array.isArray(s.points) ? s.points : null;
  if (!points || points.length < 1) return;

  ctx.save();
  ctx.lineCap = 'round';
  ctx.lineJoin = 'round';
  ctx.lineWidth = typeof s.width === 'number' ? s.width : 4;
  if (s.tool === 'eraser') {
    ctx.strokeStyle = '#ffffff';
    ctx.lineWidth = Math.max(8, (typeof s.width === 'number' ? s.width : 4) * 4);
  } else {
    ctx.strokeStyle = typeof s.color === 'string' ? s.color : '#000000';
  }

  ctx.beginPath();
  ctx.moveTo(points[0]![0], points[0]![1]);
  for (let i = 1; i < points.length; i++) {
    ctx.lineTo(points[i]![0], points[i]![1]);
  }
  ctx.stroke();
  ctx.restore();
}
