import { clamp, formatTransform } from './math';
import { fetchLayoutRecords, upsertLayoutRecords } from './db';

type DocumentId = string;

interface Point {
  x: number;
  y: number;
}

type Polygon = Point[];

interface TransformOptions {
  centerX?: number;
  centerY?: number;
  width?: number;
  height?: number;
  rotation?: number;
  scale?: number;
  zIndex?: number | null;
}

interface CardDimensions {
  width: number;
  height: number;
}

interface LayoutEntry {
  centerX: number;
  centerY: number;
  rotation: number;
  z: number;
  width?: number;
  height?: number;
}

interface LayoutGenerationEntry {
  id: string;
  width: number;
  height: number;
  seedKey: string;
}

interface LayoutGenerationOptions {
  canvasWidth: number;
  canvasHeight: number;
  padding: number;
  startZ?: number;
  rotationRange?: number;
  minSpacing?: number;
  shelfWidth?: number;
}

interface DocumentSize {
  width: number;
  height: number;
}

interface BaseMetrics {
  baseWidth: number;
  baseHeight: number;
  baseScale: number;
}

interface DragGroupItem {
  docId?: string | number | null;
  width: number;
  height: number;
  currentCenterX?: number;
  currentCenterY?: number;
  displayRotation?: number;
}

interface DragState {
  docKey?: string | null;
  dragScale?: number;
  originCenterX?: number;
  originCenterY?: number;
  groupItems?: DragGroupItem[] | null;
}

interface InertiaSimulationState {
  docId: string;
  restRotation: number;
  rotation: number;
  dynamicRotation: number;
  angularVelocity: number;
  width: number;
  height: number;
  dragScale?: number;
  lastTimestamp: number;
  frameId?: number;
}

interface WorkspaceSnapshot {
  layout: Map<DocumentId, LayoutEntry>;
  canvasSize: { width: number; height: number };
  visibleDocIds: Set<DocumentId>;
  draggingId: string | null;
  tagDropTargetId: string | null;
  pendingTagDocId: string | null;
  pendingRemovalTag: unknown;
  initialLoadDone: boolean;
}

type WorkspaceSubscriber = () => void;

type DeskDocument = { id?: string | number | null } & Record<string, unknown>;

type EnsureDocumentSize = (doc: DeskDocument) => DocumentSize | null | undefined;

type ResolveBaseMetrics = () => BaseMetrics;

interface ItemRefs {
  current: Map<string, HTMLElement | null>;
}

interface WorkspaceEngineOptions {
  allowLayoutPersistence?: boolean;
  tenantId?: string | null;
  viewId?: string | null;
}

type UseSyncExternalStoreHook = <State>(
  subscribe: (listener: () => void) => () => void,
  getSnapshot: () => State,
  getServerSnapshot: () => State,
) => State;

export const DESK_CANVAS_PADDING = 24;
export const DESK_ROTATION_RANGE = 7;
export const DESK_DEFAULT_CANVAS_WIDTH = 1024;
export const DESK_DEFAULT_CANVAS_HEIGHT = 680;
export const DESK_CARD_MIN = 240;
export const DESK_CARD_MAX = 340;

const DEFAULT_Z_START = 10;
export const MIN_TIMESTEP = 1 / 120;
export const MAX_TIMESTEP = 1 / 20;
export const MAX_DYNAMIC_ROTATION = 4;
export const MAX_ANGULAR_VELOCITY = 180;
export const ANGULAR_DAMPING = 11;
export const TORQUE_TO_ACCELERATION = 0.006;
export const SETTLE_ANGULAR_VELOCITY = 1.2;

export const applyDomTransform = (
  node: HTMLElement | null | undefined,
  {
    centerX,
    centerY,
    width,
    height,
    rotation = 0,
    scale = 1,
    zIndex,
  }: TransformOptions = {},
): void => {
  if (!node) {
    return;
  }
  const w = Number(width) || 0;
  const h = Number(height) || 0;
  const cx = Number(centerX) || 0;
  const cy = Number(centerY) || 0;
  const originX = cx - w / 2;
  const originY = cy - h / 2;
  node.style.transform = formatTransform(originX, originY, rotation || 0, scale || 1);
  if (zIndex != null && node.style.zIndex !== String(zIndex)) {
    node.style.zIndex = String(zIndex);
  }
};

export const clampCardDimensions = (width: number, height: number): CardDimensions | null => {
  const w = Number(width);
  const h = Number(height);

  if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {
    return null;
  }

  const low = Math.max(DESK_CARD_MIN / w, DESK_CARD_MIN / h);
  const high = Math.min(DESK_CARD_MAX / w, DESK_CARD_MAX / h);

  const candidates = [];
  const addCandidate = (scale: number) => {
    if (Number.isFinite(scale) && scale > 0) {
      candidates.push(scale);
    }
  };

  addCandidate(1);
  addCandidate(low);
  addCandidate(high);

  const best = candidates.reduce<{ scale: number; violation: number; deviation: number } | null>((acc, scale) => {
    const scaledWidth = w * scale;
    const scaledHeight = h * scale;
    const violation = Math.max(
      Math.max(DESK_CARD_MIN - scaledWidth, 0),
      Math.max(scaledWidth - DESK_CARD_MAX, 0),
      Math.max(DESK_CARD_MIN - scaledHeight, 0),
      Math.max(scaledHeight - DESK_CARD_MAX, 0),
    );
    const deviation = Math.abs(scale - 1);
    if (!acc || violation < acc.violation || (violation === acc.violation && deviation < acc.deviation)) {
      return { scale, violation, deviation };
    }
    return acc;
  }, null);

  const scale = best ? best.scale : 1;
  return {
    width: Math.round(w * scale),
    height: Math.round(h * scale),
  };
};

export const computeFallbackCardSize = (docId: string | number): CardDimensions | null => {
  const baseSeed = seededRandom(`${docId}:fallback-size`);
  const aspectSeed = seededRandom(`${docId}:fallback-aspect`);

  const width = DESK_CARD_MIN + baseSeed * (DESK_CARD_MAX - DESK_CARD_MIN);
  const isPortrait = aspectSeed < 0.5;
  const normalizedSeed = isPortrait ? aspectSeed / 0.5 : (aspectSeed - 0.5) / 0.5;
  const aspectRange = 0.75;
  const aspect = isPortrait
    ? 1 + normalizedSeed * aspectRange
    : 1 / (1 + normalizedSeed * aspectRange);
  const height = width * aspect;

  return clampCardDimensions(width, height);
};

function seededRandom(input: unknown): number {
  const text = String(input);
  let hash = 2166136261;
  for (let index = 0; index < text.length; index += 1) {
    hash ^= text.charCodeAt(index);
    hash = Math.imul(hash, 16777619);
  }
  return (hash >>> 0) / 4294967295;
}

function randomRangeFromSeed(seedKey: string, min: number, max: number): number {
  const span = max - min;
  if (span <= 0) return min;
  const seed = seededRandom(seedKey);
  return min + seed * span;
}

function buildKey(docId: string | number, suffix: string): string {
  return `${docId}::${suffix}`;
}

const signedDistanceToEdge = (edgeStart: Point, edgeEnd: Point, point: Point): number =>
  (edgeEnd.x - edgeStart.x) * (point.y - edgeStart.y)
  - (edgeEnd.y - edgeStart.y) * (point.x - edgeStart.x);

const iterateEdges = (
  polygon: Polygon,
  callback: (current: Point, next: Point, index: number) => boolean | void,
): void => {
  if (!Array.isArray(polygon) || polygon.length === 0) {
    return;
  }
  for (let index = 0; index < polygon.length; index += 1) {
    const current = polygon[index];
    const next = polygon[(index + 1) % polygon.length];
    if (callback(current, next, index) === false) {
      break;
    }
  }
};

const forEachVertex = (
  polygon: Polygon,
  callback: (current: Point, previous: Point, index: number) => boolean | void,
): void => {
  if (!Array.isArray(polygon) || polygon.length === 0) {
    return;
  }
  for (let index = 0; index < polygon.length; index += 1) {
    const current = polygon[index];
    const prev = polygon[(index - 1 + polygon.length) % polygon.length];
    if (callback(current, prev, index) === false) {
      break;
    }
  }
};

const lineIntersection = (p1: Point, p2: Point, cp1: Point, cp2: Point): Point => {
  const A1 = p2.y - p1.y;
  const B1 = p1.x - p2.x;
  const C1 = A1 * p1.x + B1 * p1.y;

  const A2 = cp2.y - cp1.y;
  const B2 = cp1.x - cp2.x;
  const C2 = A2 * cp1.x + B2 * cp1.y;

  const det = A1 * B2 - A2 * B1;
  if (Math.abs(det) < 1e-6) {
    return { x: cp1.x, y: cp1.y };
  }
  return {
    x: (B2 * C1 - B1 * C2) / det,
    y: (A1 * C2 - A2 * C1) / det,
  };
};

const clipPolygon = (subject: Polygon, clipper: Polygon): Polygon => {
  if (!Array.isArray(subject) || !subject.length) {
    return [];
  }
  let output = subject;
  iterateEdges(clipper, (cp1, cp2) => {
    const input = output;
    output = [];
    if (!Array.isArray(input) || !input.length) {
      return false;
    }
    forEachVertex(input, (current, prev) => {
      const currentInside = signedDistanceToEdge(cp1, cp2, current) >= 0;
      const prevInside = signedDistanceToEdge(cp1, cp2, prev) >= 0;
      if (currentInside) {
        if (!prevInside) {
          output.push(lineIntersection(prev, current, cp1, cp2));
        }
        output.push(current);
      } else if (prevInside) {
        output.push(lineIntersection(prev, current, cp1, cp2));
      }
      return true;
    });
    return output.length > 0;
  });
  return output;
};

const isPointInsideConvex = (point: Point, polygon: Polygon): boolean => {
  if (!polygon?.length) {
    return false;
  }
  let sign = 0;
  let inside = true;
  iterateEdges(polygon, (a, b) => {
    const cross = signedDistanceToEdge(a, b, point);
    if (cross === 0) {
      return true;
    }
    const currentSign = cross > 0 ? 1 : -1;
    if (sign === 0) {
      sign = currentSign;
      return true;
    }
    if (sign !== currentSign) {
      inside = false;
      return false;
    }
    return true;
  });
  return inside;
};

const polygonCentroid = (polygon: Polygon): Point => {
  if (!polygon?.length) {
    return { x: 0, y: 0 };
  }
  let area = 0;
  let cx = 0;
  let cy = 0;
  iterateEdges(polygon, (current, next) => {
    const cross = current.x * next.y - next.x * current.y;
    area += cross;
    cx += (current.x + next.x) * cross;
    cy += (current.y + next.y) * cross;
  });
  if (Math.abs(area) < 1e-6) {
    let sumX = 0;
    let sumY = 0;
    forEachVertex(polygon, (point) => {
      sumX += point.x;
      sumY += point.y;
    });
    return {
      x: sumX / polygon.length,
      y: sumY / polygon.length,
    };
  }
  const areaFactor = 1 / (3 * area);
  return {
    x: cx * areaFactor,
    y: cy * areaFactor,
  };
};
const generateInitialLayout = (
  entries: LayoutGenerationEntry[],
  {
    canvasWidth,
    canvasHeight,
    padding,
    startZ = 0,
    rotationRange = DESK_ROTATION_RANGE,
    minSpacing = 48,
    shelfWidth = 0,
  }: LayoutGenerationOptions,
): { layout: Map<string, LayoutEntry>; maxZ: number } => {
  const layout = new Map<string, LayoutEntry>();
  let currentZ = startZ;
  let maxZ = startZ;

  if (!entries.length) {
    return { layout, maxZ };
  }

  const shelfOffset = Math.max(shelfWidth, 0);
  const spacingBuffer = Math.max(minSpacing, 0);
  const placed: Array<{ x: number; y: number; radius: number }> = [];

  const resolveBounds = (width: number, height: number) => {
    const halfWidth = width / 2;
    const halfHeight = height / 2;
    return {
      minCenterX: padding + halfWidth,
      maxCenterX: Math.max(
        padding + halfWidth,
        canvasWidth - shelfOffset - padding - halfWidth,
      ),
      minCenterY: padding + halfHeight,
      maxCenterY: Math.max(padding + halfHeight, canvasHeight - padding - halfHeight),
    };
  };

  const evaluateCandidateSpacing = (x: number, y: number, radius: number) => {
    if (!placed.length) {
      return Number.POSITIVE_INFINITY;
    }
    let best = Number.POSITIVE_INFINITY;
    for (let i = 0; i < placed.length; i += 1) {
      const item = placed[i];
      const dx = item.x - x;
      const dy = item.y - y;
      const distance = Math.sqrt(dx * dx + dy * dy) - item.radius - radius - spacingBuffer;
      if (distance < best) {
        best = distance;
      }
    }
    return best;
  };

  entries.forEach((entry) => {
    const width = Number(entry.width) || 0;
    const height = Number(entry.height) || 0;
    if (!entry.id || width <= 0 || height <= 0) {
      return;
    }

    const { minCenterX, maxCenterX, minCenterY, maxCenterY } = resolveBounds(width, height);

    const radius = Math.sqrt(width * width + height * height) / 2;

    let bestScore = -Infinity;
    let bestX = (minCenterX + maxCenterX) / 2;
    let bestY = (minCenterY + maxCenterY) / 2;
    const samplesPerAxis = 14;
    for (let gx = 0; gx < samplesPerAxis; gx += 1) {
      const fracX = (gx + 0.5) / samplesPerAxis;
      for (let gy = 0; gy < samplesPerAxis; gy += 1) {
        const fracY = (gy + 0.5) / samplesPerAxis;
        const candidateX = minCenterX + fracX * (maxCenterX - minCenterX);
        const candidateY = minCenterY + fracY * (maxCenterY - minCenterY);
        const edgeSpacing = Math.min(
          candidateX - minCenterX,
          maxCenterX - candidateX,
          candidateY - minCenterY,
          maxCenterY - candidateY,
        ) - spacingBuffer * 0.5;
        if (edgeSpacing <= 0) {
          continue;
        }
        const neighborSpacing = evaluateCandidateSpacing(candidateX, candidateY, radius);
        const score = Math.min(edgeSpacing, neighborSpacing);
        if (score > bestScore) {
          bestScore = score;
          bestX = candidateX;
          bestY = candidateY;
        }
      }
    }
    const centerX = clamp(bestX, minCenterX, maxCenterX);
    const centerY = clamp(bestY, minCenterY, maxCenterY);

    const rotation = randomRangeFromSeed(
      buildKey(entry.id, 'rotation'),
      -rotationRange,
      rotationRange,
    );

    currentZ += 1;
    layout.set(entry.id, {
      centerX,
      centerY,
      rotation,
      z: currentZ,
      width,
      height,
    });
    maxZ = Math.max(maxZ, currentZ);

    placed.push({ x: centerX, y: centerY, radius });
  });

  return { layout, maxZ };
};

export class WorkspaceEngine {
  allowLayoutPersistence: boolean;

  tenantId: string | null;

  viewId: string | null;

  layout: Map<DocumentId, LayoutEntry>;

  layoutSnapshot: Map<DocumentId, LayoutEntry>;

  persistedLayout: Map<DocumentId, LayoutEntry>;

  layoutDirty: boolean;

  zCounter: number;

  canvasSize: { width: number; height: number };

  visibleDocIds: Set<DocumentId>;

  draggingId: string | null;

  tagDropTargetId: string | null;

  pendingTagDocId: string | null;

  pendingRemovalTag: unknown;

  dragInProgress: boolean;

  activeDragDocIds: Set<DocumentId>;

  pendingSnapshotSync: boolean;

  pendingPersistSync: boolean;

  persistDebounceId: number | null;

  items: DeskDocument[];

  documentLookup: Map<string, DeskDocument>;

  ensureDocumentSize: EnsureDocumentSize;

  resolveBaseMetrics: ResolveBaseMetrics;

  snapshotCache: WorkspaceSnapshot;

  subscribers: Set<WorkspaceSubscriber>;

  loadingPersisted: boolean;

  pendingPersistence: unknown;

  itemRefs: ItemRefs;

  inertiaAnimations: Map<string, InertiaSimulationState>;

  initialLoadDone: boolean;

  constructor({
    allowLayoutPersistence = false,
    tenantId = null,
    viewId = null,
  }: WorkspaceEngineOptions = {}) {
    this.allowLayoutPersistence = allowLayoutPersistence;
    this.tenantId = tenantId;
    this.viewId = viewId;

    this.layout = new Map();
    this.layoutSnapshot = new Map();
    this.persistedLayout = new Map();
    this.layoutDirty = false;
    this.zCounter = DEFAULT_Z_START;
    this.canvasSize = { width: 0, height: 0 };
    this.visibleDocIds = new Set();
    this.draggingId = null;
    this.tagDropTargetId = null;
    this.pendingTagDocId = null;
    this.pendingRemovalTag = null;
    this.dragInProgress = false;
    this.activeDragDocIds = new Set();
    this.pendingSnapshotSync = false;
    this.pendingPersistSync = false;
    this.persistDebounceId = null;
    this.pendingSnapshotSync = false;
    this.pendingPersistSync = false;

    this.items = [];
    this.documentLookup = new Map();
    this.ensureDocumentSize = () => null;
    this.resolveBaseMetrics = () => ({ baseWidth: 0, baseHeight: 0, baseScale: 1 });

    this.snapshotCache = this.buildSnapshot();
    this.subscribers = new Set();

    this.loadingPersisted = false;
    this.pendingPersistence = null;
    this.itemRefs = { current: new Map() };
    this.inertiaAnimations = new Map();
    this.initialLoadDone = false;
  }

  updateConfig({ allowLayoutPersistence, tenantId, viewId }: WorkspaceEngineOptions): void {
    const allowChanged =
      typeof allowLayoutPersistence === 'boolean'
      && allowLayoutPersistence !== this.allowLayoutPersistence;
    const tenantChanged = tenantId !== undefined && tenantId !== this.tenantId;
    const viewChanged = viewId !== undefined && viewId !== this.viewId;

    if (!allowChanged && !tenantChanged && !viewChanged) {
      if (
        this.allowLayoutPersistence
        && this.tenantId
        && this.viewId
        && !this.initialLoadDone
        && !this.loadingPersisted
      ) {
        this.loadPersistedLayout();
      }
      return;
    }

    if (allowChanged) {
      this.allowLayoutPersistence = allowLayoutPersistence;
    }
    if (tenantChanged) {
      this.tenantId = tenantId;
    }
    if (viewChanged) {
      this.viewId = viewId;
    }

    if (!this.allowLayoutPersistence) {
      this.persistedLayout = new Map();
      this.layoutDirty = false;
      this.emit();
      return;
    }

    if (!this.tenantId || !this.viewId) {
      return;
    }

    if (!this.initialLoadDone) {
      this.loadPersistedLayout();
    }
  }

  setItems(items: DeskDocument[] | null | undefined): void {
    const normalized = Array.isArray(items) ? items : [];
    this.items = normalized;
    const canGenerateLayoutImmediately =
      !this.allowLayoutPersistence
      || !this.tenantId
      || !this.viewId
      || this.initialLoadDone;
    if (canGenerateLayoutImmediately) {
      this.ensureLayoutForItems();
    }
    this.recalcVisibleDocIds();
  }

  setDocumentLookup(map: Map<string, DeskDocument>): void {
    this.documentLookup = map instanceof Map ? map : new Map();
    this.recalcVisibleDocIds();
  }

  setEnsureDocumentSize(fn: EnsureDocumentSize): void {
    if (typeof fn === 'function') {
      this.ensureDocumentSize = fn;
    }
  }

  setResolveBaseMetrics(fn: ResolveBaseMetrics): void {
    if (typeof fn === 'function') {
      this.resolveBaseMetrics = fn;
    }
  }

  setItemRefs(ref: ItemRefs | null | undefined): void {
    this.itemRefs = ref || { current: new Map() };
  }

  setCanvasSize(size: { width?: number | null; height?: number | null }): void {
    const width = Number(size?.width) || 0;
    const height = Number(size?.height) || 0;
    if (this.canvasSize.width === width && this.canvasSize.height === height) {
      return;
    }
    this.canvasSize = { width, height };
    this.ensureLayoutForItems();
    this.recalcVisibleDocIds();
    this.emit();
  }

  setDraggingId(docId: string | number | null): void {
    const normalized = docId != null ? String(docId) : null;
    if (this.draggingId === normalized) {
      return;
    }
    this.draggingId = normalized;
    this.emit();
  }

  beginDrag(docIds: Array<string | number | null> = []): void {
    this.dragInProgress = true;
    if (Array.isArray(docIds)) {
      this.activeDragDocIds = new Set(docIds.map((id) => (id != null ? String(id) : null)).filter(Boolean));
    } else {
      this.activeDragDocIds.clear();
    }
  }

  endDrag(): void {
    this.dragInProgress = false;
    this.activeDragDocIds.clear();
    this.flushPendingLayoutOps();
  }

  flushPendingLayoutOps(): void {
    if (this.pendingSnapshotSync) {
      this.syncLayoutSnapshot();
    }
    if (this.pendingPersistSync) {
      this.persistLayoutSnapshot();
    }
  }

  setTagDropTargetId(docId: string | number | null): void {
    const normalized = docId != null ? String(docId) : null;
    if (this.tagDropTargetId === normalized) {
      return;
    }
    this.tagDropTargetId = normalized;
    this.emit();
  }

  setPendingTagDocId(docId: string | number | null): void {
    const normalized = docId != null ? String(docId) : null;
    if (this.pendingTagDocId === normalized) {
      return;
    }
    this.pendingTagDocId = normalized;
    this.emit();
  }

  setPendingRemovalTag(payload: unknown): void {
    if (payload === this.pendingRemovalTag) {
      return;
    }
    this.pendingRemovalTag = payload;
    this.emit();
  }

  markLayoutDirty(): void {
    this.layoutDirty = true;
  }

  getLayout(docId: string | number | null): LayoutEntry | null {
    if (docId == null) {
      return null;
    }
    const key = String(docId);
    return this.layout.get(key) || null;
  }

  updateLayoutEntry(
    docId: string | number | null,
    updater:
      | LayoutEntry
      | null
      | undefined
      | ((previous: LayoutEntry | null) => LayoutEntry | null | undefined),
  ): void {
    if (docId == null) {
      return;
    }
    const key = String(docId);
    const previous = this.layout.get(key) || null;
    const next = typeof updater === 'function' ? updater(previous) : updater;
    if (!next) {
      this.layout.delete(key);
    } else {
      this.layout.set(key, next);
    }
    this.markLayoutDirty();
    this.syncLayoutSnapshot();
    this.persistLayoutSnapshot();
  }

  bringToFront(docId: string | number | null): void {
    if (docId == null) {
      return;
    }
    const key = String(docId);
    const entry = this.layout.get(key);
    if (!entry) {
      return;
    }
    this.zCounter += 1;
    this.layout.set(key, { ...entry, z: this.zCounter });
    this.markLayoutDirty();
    this.syncLayoutSnapshot();
    this.persistLayoutSnapshot();
    this.recalcVisibleDocIds();
  }

  applyTransform(
    docId: string | number | null,
    centerX: number,
    centerY: number,
    width: number,
    height: number,
    rotation: number,
    scale = 1,
    zIndex: number | null = null,
  ): void {
    const key = docId != null ? String(docId) : null;
    if (!key) {
      return;
    }
    const node = this.itemRefs?.current?.get(key);
    applyDomTransform(node, {
      centerX,
      centerY,
      width,
      height,
      rotation,
      scale,
      zIndex,
    });
  }

  finalizeGroupDrag(dragState: DragState): void {
    if (!dragState?.groupItems) {
      return;
    }

    dragState.groupItems.forEach((item) => {
      if (!item) {
        return;
      }
      const key = item.docId != null ? String(item.docId) : null;
      if (!key) {
        return;
      }
      const entry = this.layout.get(key);
      const centerX = item.currentCenterX ?? entry?.centerX ?? dragState.originCenterX ?? 0;
      const centerY = item.currentCenterY ?? entry?.centerY ?? dragState.originCenterY ?? 0;
      const rotation = item.displayRotation ?? entry?.rotation ?? 0;

      const nextEntry: LayoutEntry = {
        centerX,
        centerY,
        rotation,
        z: entry?.z ?? this.zCounter,
        width: entry?.width ?? item.width,
        height: entry?.height ?? item.height,
      };

      this.layout.set(key, nextEntry);

      this.applyTransform(
        key,
        centerX,
        centerY,
        item.width,
        item.height,
        rotation,
        key === dragState.docKey ? dragState.dragScale || 1 : 1,
        nextEntry.z,
      );
    });

    this.markLayoutDirty();
    this.syncLayoutSnapshot();
    this.persistLayoutSnapshot();
  }

  cancelInertiaAnimation(docId: string | number | null): void {
    const key = docId != null ? String(docId) : null;
    if (!key) {
      return;
    }
    const existing = this.inertiaAnimations.get(key);
    if (existing?.frameId != null) {
      window.cancelAnimationFrame(existing.frameId);
    }
    this.inertiaAnimations.delete(key);
  }

  disposeInertiaAnimations(): void {
    this.inertiaAnimations.forEach((animation) => {
      if (animation?.frameId != null) {
        window.cancelAnimationFrame(animation.frameId);
      }
    });
    this.inertiaAnimations.clear();
  }

  integrateRotation(
    simulationState: InertiaSimulationState,
    dt: number,
    torque = 0,
    dampingOverride: number | null = null,
  ): boolean {
    const key = simulationState.docId != null ? String(simulationState.docId) : null;
    if (!key) {
      return true;
    }
    const entry = this.layout.get(key);
    if (!entry) {
      return true;
    }

    const centerX = Number(entry.centerX);
    const centerY = Number(entry.centerY);
    if (!Number.isFinite(centerX) || !Number.isFinite(centerY)) {
      return true;
    }

    const torqueAcceleration = torque * TORQUE_TO_ACCELERATION;
    let angularVelocity = simulationState.angularVelocity + torqueAcceleration * dt;
    angularVelocity = clamp(angularVelocity, -MAX_ANGULAR_VELOCITY, MAX_ANGULAR_VELOCITY);

    const dampingConstant = typeof dampingOverride === 'number' ? dampingOverride : ANGULAR_DAMPING;
    const dampingFactor = Math.exp(-dampingConstant * dt);
    angularVelocity *= dampingFactor;

    let dynamicRotation = simulationState.dynamicRotation + angularVelocity * dt;
    if (dynamicRotation > MAX_DYNAMIC_ROTATION) {
      dynamicRotation = MAX_DYNAMIC_ROTATION;
      angularVelocity = Math.min(angularVelocity, 0);
    } else if (dynamicRotation < -MAX_DYNAMIC_ROTATION) {
      dynamicRotation = -MAX_DYNAMIC_ROTATION;
      angularVelocity = Math.max(angularVelocity, 0);
    }

    simulationState.angularVelocity = angularVelocity;
    simulationState.dynamicRotation = dynamicRotation;
    simulationState.rotation = simulationState.restRotation + dynamicRotation;

    const rotation = simulationState.rotation;
    const nextEntry = { ...entry, rotation };
    this.layout.set(key, nextEntry);
    this.markLayoutDirty();

    this.applyTransform(
      key,
      centerX,
      centerY,
      simulationState.width,
      simulationState.height,
      rotation,
      simulationState.dragScale || 1,
      nextEntry.z,
    );

    const isSettled = Math.abs(angularVelocity) < SETTLE_ANGULAR_VELOCITY;
    return isSettled;
  }

  startInertiaAnimation(docId: string | number | null, baseState: InertiaSimulationState): void {
    const raf = window.requestAnimationFrame;
    if (!raf) {
      return;
    }
    const key = docId != null ? String(docId) : null;
    if (!key) {
      return;
    }

    this.cancelInertiaAnimation(key);

    const now = performance?.now ? performance.now() : Date.now();

    const simulationState = {
      ...baseState,
      docId: key,
      dragScale: baseState.dragScale || 1,
      lastTimestamp: now,
    };

    const step = (timestamp: number) => {
      const safeTimestamp = Number.isFinite(timestamp) ? timestamp : now + 16;
      const previous = simulationState.lastTimestamp;
      let dt = (safeTimestamp - previous) / 1000;
      if (!Number.isFinite(dt) || dt <= 0) {
        dt = MIN_TIMESTEP;
      }
      dt = clamp(dt, MIN_TIMESTEP, MAX_TIMESTEP);
      simulationState.lastTimestamp = safeTimestamp;

      const settled = this.integrateRotation(simulationState, dt, 0);
      if (settled) {
        this.inertiaAnimations.delete(key);
        this.syncLayoutSnapshot();
        this.persistLayoutSnapshot();
        return;
      }
      simulationState.frameId = raf(step);
    };

    simulationState.frameId = raf(step);
    this.inertiaAnimations.set(key, simulationState);
  }

  syncLayoutSnapshot(): void {
    if (this.dragInProgress) {
      this.pendingSnapshotSync = true;
      return;
    }
    this.pendingSnapshotSync = false;
    this.layoutSnapshot = new Map(this.layout);
    this.emit();
  }

  async persistLayoutSnapshot(): Promise<void> {
    if (this.dragInProgress) {
      this.pendingPersistSync = true;
      return;
    }
    if (!this.allowLayoutPersistence || !this.tenantId || !this.viewId) {
      this.pendingPersistSync = false;
      return;
    }
    if (!this.layoutDirty && !this.pendingPersistSync) {
      return;
    }
    this.pendingPersistSync = false;
    this.layoutDirty = false;
    if (this.persistDebounceId) {
      clearTimeout(this.persistDebounceId);
      this.persistDebounceId = null;
    }
    const snapshotSource = this.layoutSnapshot && this.layoutSnapshot.size
      ? this.layoutSnapshot
      : this.layout;
    const snapshot = new Map(snapshotSource);
    const merged = new Map(this.persistedLayout);
    snapshot.forEach((entry, docId) => {
      if (!docId || !entry) {
        return;
      }
      const centerX = Number(entry.centerX);
      const centerY = Number(entry.centerY);
      if (!Number.isFinite(centerX) || !Number.isFinite(centerY)) {
        return;
      }
      const rotation = Number.isFinite(Number(entry.rotation)) ? Number(entry.rotation) : 0;
      const z = Number.isFinite(Number(entry.z)) ? Number(entry.z) : undefined;
      merged.set(docId, { centerX, centerY, rotation, z });
    });

    this.persistedLayout = merged;

    const records = [];
    merged.forEach((entry, docId) => {
      if (!docId || !entry) {
        return;
      }
      records.push({
        documentId: docId,
        centerX: entry.centerX,
        centerY: entry.centerY,
        rotation: entry.rotation ?? 0,
        zIndex: entry.z ?? 0,
      });
    });

    const persistTask = async () => {
      try {
        await upsertLayoutRecords({ tenantId: this.tenantId, viewId: this.viewId, entries: records });
      } catch (error) {
        console.warn('[desk] Failed to persist layout snapshot', error);
      }
    };

    this.persistDebounceId = window.setTimeout(() => {
      this.persistDebounceId = null;
      void persistTask();
    }, 100);
  }

  ensureLayoutForItems(): void {
    const persistenceReady = !this.allowLayoutPersistence || !this.tenantId || !this.viewId || this.initialLoadDone;
    const canvasReady = Boolean(this.canvasSize.width && this.canvasSize.height);
    const sizesReady = !this.items.some((doc) => !this.ensureDocumentSize(doc));

    if (!persistenceReady) {
      return;
    }

    if (!canvasReady) {
      return;
    }
    if (!this.items.length) {
      if (this.layout.size) {
        this.layout = new Map();
        this.syncLayoutSnapshot();
      }
      return;
    }

    if (!sizesReady) {
      return;
    }

    const next = new Map<DocumentId, LayoutEntry>();
    let maxZ = this.zCounter;
    const canvasWidth = this.canvasSize.width || DESK_DEFAULT_CANVAS_WIDTH;
    const canvasHeight = this.canvasSize.height || DESK_DEFAULT_CANVAS_HEIGHT;
    const docsNeedingLayout: LayoutGenerationEntry[] = [];

    const currentEntries = new Map(this.layout);

    this.items.forEach((doc) => {
      if (!doc?.id) {
        return;
      }
      const docKey = String(doc.id);
      const sizeInfo = this.ensureDocumentSize(doc);
      if (!sizeInfo) {
        return;
      }
      const { width: docWidth, height: docHeight } = sizeInfo;
      const halfWidth = docWidth / 2;
      const halfHeight = docHeight / 2;

      const minCenterX = DESK_CANVAS_PADDING + halfWidth;
      const maxCenterX = Math.max(minCenterX, canvasWidth - DESK_CANVAS_PADDING - halfWidth);
      const minCenterY = DESK_CANVAS_PADDING + halfHeight;
      const maxCenterY = Math.max(minCenterY, canvasHeight - DESK_CANVAS_PADDING - halfHeight);

      const persisted = this.persistedLayout.get(docKey);
      const currentEntry = currentEntries.get(docKey) || null;
      let existing = persisted || currentEntry;
      if (existing) {
        const defaultCenterX = (minCenterX + maxCenterX) / 2;
        const defaultCenterY = (minCenterY + maxCenterY) / 2;
        const prevCenterX = typeof existing.centerX === 'number' ? existing.centerX : defaultCenterX;
        const prevCenterY = typeof existing.centerY === 'number' ? existing.centerY : defaultCenterY;
        const centerX = clamp(prevCenterX, minCenterX, maxCenterX);
        const centerY = clamp(prevCenterY, minCenterY, maxCenterY);
        const rotation = existing.rotation ?? 0;
        const z = existing.z ?? maxZ;
        maxZ = Math.max(maxZ, z);
        next.set(docKey, { centerX, centerY, rotation, z, width: docWidth, height: docHeight });
        return;
      }

      docsNeedingLayout.push({
        id: docKey,
        width: docWidth,
        height: docHeight,
        seedKey: docKey,
      });
    });

    if (docsNeedingLayout.length) {
      const { layout: generatedLayout, maxZ: updatedMaxZ } = generateInitialLayout(
        docsNeedingLayout,
        {
          canvasWidth,
          canvasHeight,
          padding: DESK_CANVAS_PADDING,
          startZ: maxZ,
          rotationRange: DESK_ROTATION_RANGE,
          minSpacing: 48,
          shelfWidth: 0,
        },
      );
      generatedLayout.forEach((entry, docId) => {
        next.set(docId, entry);
      });
      maxZ = Math.max(maxZ, updatedMaxZ);
    }

    this.layout = next;
    this.zCounter = Math.max(this.zCounter, maxZ);
    this.syncLayoutSnapshot();
    this.persistLayoutSnapshot();
    this.recalcVisibleDocIds();
  }

  recalcVisibleDocIds(): void {
    const ensureSize = this.ensureDocumentSize;
    if (typeof ensureSize !== 'function') {
      return;
    }

    const layoutMap = this.layout;
    const canvasWidth = this.canvasSize.width || DESK_DEFAULT_CANVAS_WIDTH;
    const canvasHeight = this.canvasSize.height || DESK_DEFAULT_CANVAS_HEIGHT;

    if (!layoutMap.size || canvasWidth <= 0 || canvasHeight <= 0) {
      if (this.visibleDocIds.size) {
        this.visibleDocIds = new Set();
        this.emit();
      }
      return;
    }

    const viewport = [
      { x: 0, y: 0 },
      { x: canvasWidth, y: 0 },
      { x: canvasWidth, y: canvasHeight },
      { x: 0, y: canvasHeight },
    ];

    const entries: Array<{ key: string; z: number; polygon: Polygon }> = [];
    layoutMap.forEach((entry, docKey) => {
      if (!docKey) {
        return;
      }
      const doc = this.documentLookup.get(docKey);
      if (!doc) {
        return;
      }
      const sizeInfo = ensureSize(doc);
      if (!sizeInfo) {
        return;
      }
      const { width: cardWidth, height: cardHeight } = sizeInfo;
      const rotationDeg = Number(entry?.rotation) || 0;
      const rotationRad = (rotationDeg * Math.PI) / 180;
      const cosRot = Math.cos(rotationRad);
      const sinRot = Math.sin(rotationRad);
      const halfWidth = cardWidth / 2;
      const halfHeight = cardHeight / 2;
      const localCorners = [
        { x: -halfWidth, y: -halfHeight },
        { x: halfWidth, y: -halfHeight },
        { x: halfWidth, y: halfHeight },
        { x: -halfWidth, y: halfHeight },
      ];
      const centerX = entry?.centerX ?? DESK_CANVAS_PADDING + cardWidth / 2;
      const centerY = entry?.centerY ?? DESK_CANVAS_PADDING + cardHeight / 2;
      const corners = localCorners.map(({ x, y }) => ({
        x: centerX + x * cosRot - y * sinRot,
        y: centerY + x * sinRot + y * cosRot,
      }));
      const clipped = clipPolygon(corners, viewport);
      if (!clipped.length) {
        return;
      }
      entries.push({
        key: docKey,
        z: entry?.z ?? 0,
        polygon: clipped,
      });
    });

    if (!entries.length) {
      if (this.visibleDocIds.size) {
        this.visibleDocIds = new Set();
        this.emit();
      }
      return;
    }

    entries.sort((a, b) => (b.z || 0) - (a.z || 0));

    const visiblePolygons: Polygon[] = [];
    const result = new Set<DocumentId>();

    entries.forEach(({ key, polygon }) => {
      if (polygon.length < 3) {
        return;
      }

      let fullyCovered = true;
      for (let i = 0; i < polygon.length; i += 1) {
        const point = polygon[i];
        const inside = visiblePolygons.some((poly) => isPointInsideConvex(point, poly));
        if (!inside) {
          fullyCovered = false;
          break;
        }
      }

      if (fullyCovered) {
        const centroid = polygonCentroid(polygon);
        if (!visiblePolygons.some((poly) => isPointInsideConvex(centroid, poly))) {
          fullyCovered = false;
        }
      }

      if (!fullyCovered) {
        result.add(key);
        visiblePolygons.push(polygon);
      }
    });

    const sameSize = result.size === this.visibleDocIds.size;
    if (sameSize) {
      let identical = true;
      result.forEach((id) => {
        if (!this.visibleDocIds.has(id)) {
          identical = false;
        }
      });
      if (identical) {
        this.visibleDocIds.forEach((id) => {
          if (!result.has(id)) {
            identical = false;
          }
        });
      }
      if (identical) {
        return;
      }
    }

    this.visibleDocIds = result;
    this.emit();
  }

  subscribe(listener: WorkspaceSubscriber): () => void {
    this.subscribers.add(listener);
    return () => {
      this.subscribers.delete(listener);
    };
  }

  getSnapshot = (): WorkspaceSnapshot => this.snapshotCache;

  buildSnapshot(): WorkspaceSnapshot {
    return {
      layout: this.layoutSnapshot,
      canvasSize: this.canvasSize,
      visibleDocIds: this.visibleDocIds,
      draggingId: this.draggingId,
      tagDropTargetId: this.tagDropTargetId,
      pendingTagDocId: this.pendingTagDocId,
      pendingRemovalTag: this.pendingRemovalTag,
      initialLoadDone: this.initialLoadDone,
    };
  }

  emit(): void {
    this.snapshotCache = this.buildSnapshot();
    this.subscribers.forEach((listener) => {
      try {
        listener();
      } catch (error) {
        console.error('WorkspaceEngine listener failed', error);
      }
    });
  }

  async loadPersistedLayout(): Promise<void> {
    if (!this.allowLayoutPersistence || !this.tenantId || !this.viewId) {
      return;
    }
    if (this.loadingPersisted || this.initialLoadDone) {
      return;
    }
    this.loadingPersisted = true;
    try {
      const records = await fetchLayoutRecords({ tenantId: this.tenantId, viewId: this.viewId });
      const map = new Map();
      records.forEach((record) => {
        if (!record || !record.documentId) {
          return;
        }
        map.set(String(record.documentId), {
          centerX: Number(record.centerX) || 0,
          centerY: Number(record.centerY) || 0,
          rotation: Number(record.rotation) || 0,
          z: Number(record.zIndex) || 0,
        });
      });
      this.persistedLayout = map;
      this.layoutDirty = false;
      if (records.length) {
        const maxZ = records.reduce((acc, record) => Math.max(acc, Number(record.zIndex) || 0), DEFAULT_Z_START);
        this.zCounter = Math.max(this.zCounter, maxZ);
      }
      this.layout = new Map(map);
      this.layoutSnapshot = new Map(this.layout);
      this.ensureLayoutForItems();
      this.initialLoadDone = true;
      this.emit();
    } catch (error) {
      console.warn('[desk] Failed to load persisted layout', error);
    } finally {
      this.loadingPersisted = false;
      if (!this.initialLoadDone) {
        this.initialLoadDone = true;
        if (!this.layout.size) {
          this.ensureLayoutForItems();
        }
        this.emit();
      }
    }
  }
}

export const useWorkspaceSnapshot = (
  engine: WorkspaceEngine,
  useSyncExternalStoreHook: UseSyncExternalStoreHook,
): WorkspaceSnapshot => {
  const useSyncExternalStore = useSyncExternalStoreHook;
  if (typeof useSyncExternalStore !== 'function') {
    throw new Error('useWorkspaceSnapshot requires useSyncExternalStore hook');
  }
  return useSyncExternalStore(
    (listener) => engine.subscribe(listener),
    () => engine.getSnapshot(),
    () => engine.getSnapshot(),
  );
};

/* istanbul ignore next */
const commonJsModule = (globalThis as typeof globalThis & {
  module?: { exports?: Record<string, unknown> };
}).module;
if (commonJsModule?.exports) {
  commonJsModule.exports = {
    WorkspaceEngine,
    DESK_CANVAS_PADDING,
    DESK_ROTATION_RANGE,
    DESK_DEFAULT_CANVAS_WIDTH,
    DESK_DEFAULT_CANVAS_HEIGHT,
    DESK_CARD_MIN,
    DESK_CARD_MAX,
    MIN_TIMESTEP,
    MAX_TIMESTEP,
    MAX_DYNAMIC_ROTATION,
    MAX_ANGULAR_VELOCITY,
    ANGULAR_DAMPING,
    TORQUE_TO_ACCELERATION,
    SETTLE_ANGULAR_VELOCITY,
    clampCardDimensions,
    computeFallbackCardSize,
    useWorkspaceSnapshot,
  };
}
