import React, {
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import { resolveDocumentAssetUrl, createAssetView } from './asset_manager';
import { useAssetNavigator } from './hooks/useAssetNavigator';
import { ArrowLeftIcon, ArrowRightIcon, RefreshIcon } from './ui/icons';
import { clamp, formatTransform } from './desktop/math';
import { preventAll } from './desktop/events';
import useDocumentDrag from './desktop/useDocumentDrag';
import { DesktopProvider, useDesktopContext } from './desktop/context';
import PreviewZoomOverlay from './detail/PreviewZoomOverlay';
import { getTagColorStyle } from './utils/colors';
import './DesktopWorkspace.css';

const CANVAS_PADDING = 24;
const ROTATION_RANGE = 7;
const DEFAULT_CANVAS_WIDTH = 1024;
const DEFAULT_CANVAS_HEIGHT = 680;
const TAG_MIME_TYPES = ['application/x-papercrate-tag', 'text/papercrate-tag'];

const CARD_MIN = 240;
const CARD_MAX = 340;
const TAG_REMOVE_DISTANCE = 160;

const DEBUG_DRAG = false;
const DEBUG_FOCUS = true;
const DEBUG_DROP = true;

const signedDistance = (ax, ay, bx, by, px, py) => (bx - ax) * (py - ay) - (by - ay) * (px - ax);

const clipPolygonWithEdge = (subject, edgeStart, edgeEnd) => {
  if (!subject.length) {
    return [];
  }
  const result = [];
  let prev = subject[subject.length - 1];
  let prevInside = signedDistance(edgeStart.x, edgeStart.y, edgeEnd.x, edgeEnd.y, prev.x, prev.y) >= 0;
  subject.forEach((curr) => {
    const currInside = signedDistance(edgeStart.x, edgeStart.y, edgeEnd.x, edgeEnd.y, curr.x, curr.y) >= 0;
    if (currInside !== prevInside) {
      const dx = curr.x - prev.x;
      const dy = curr.y - prev.y;
      const denom = (edgeEnd.x - edgeStart.x) * dy - (edgeEnd.y - edgeStart.y) * dx;
      if (Math.abs(denom) > 1e-9) {
        const t = ((edgeStart.x - prev.x) * dy - (edgeStart.y - prev.y) * dx) / denom;
        result.push({
          x: edgeStart.x + t * (edgeEnd.x - edgeStart.x),
          y: edgeStart.y + t * (edgeEnd.y - edgeStart.y),
        });
      }
    }
    if (currInside) {
      result.push(curr);
    }
    prev = curr;
    prevInside = currInside;
  });
  return result;
};

const clipPolygon = (subject, clipShape) => {
  if (!subject.length) {
    return [];
  }
  let output = subject;
  let prev = clipShape[clipShape.length - 1];
  for (let index = 0; index < clipShape.length; index += 1) {
    const curr = clipShape[index];
    output = clipPolygonWithEdge(output, prev, curr);
    if (!output.length) {
      return [];
    }
    prev = curr;
  }
  return output;
};

const isPointInsideConvex = (point, polygon) => {
  if (!polygon.length) {
    return false;
  }
  let prev = polygon[polygon.length - 1];
  for (let index = 0; index < polygon.length; index += 1) {
    const curr = polygon[index];
    if (signedDistance(prev.x, prev.y, curr.x, curr.y, point.x, point.y) < -1e-6) {
      return false;
    }
    prev = curr;
  }
  return true;
};

const polygonCentroid = (polygon) => {
  let x = 0;
  let y = 0;
  polygon.forEach((point) => {
    x += point.x;
    y += point.y;
  });
  const count = polygon.length || 1;
  return {
    x: x / count,
    y: y / count,
  };
};

const readTransferData = (dataTransfer, mimeTypes) => {
  if (!dataTransfer) {
    return null;
  }
  for (let index = 0; index < mimeTypes.length; index += 1) {
    const type = mimeTypes[index];
    try {
      const raw = dataTransfer.getData(type);
      if (raw) {
        return raw;
      }
    } catch (error) {
      if (DEBUG_DROP) {
        console.warn('[skeuo] readTransferData failed for type', type, error);
      }
    }
  }
  return null;
};

const parseTagTransferPayload = (event) => {
  const raw = readTransferData(event?.dataTransfer, [
    'application/x-papercrate-tag',
    'text/papercrate-tag',
  ]);
  if (!raw) {
    return null;
  }
  try {
    return JSON.parse(raw);
  } catch (error) {
    console.warn('[skeuo] parseTagTransferPayload failed', error);
  }
  return null;
};

const resolveTagKey = (tag) => {
  if (!tag) {
    return null;
  }
  const key = tag.id ?? tag.uuid ?? tag.slug ?? tag.label;
  return key != null ? String(key) : null;
};

const DesktopPreviewCard = ({
  doc,
  title,
  ensureAssetUrl,
  getDocumentAsset,
  prefetch = 3,
  onNavigatorSnapshot,
  shouldLoad = true,
}) => {
  const navigator = useAssetNavigator({
    document: doc,
    assetType: 'preview',
    ensureAssetUrl: shouldLoad ? ensureAssetUrl : null,
    getAsset: getDocumentAsset,
    prefetch,
  });

  const { currentUrl, cardinality, canGoPrev, canGoNext, currentMetadata, ordinal } = navigator;
  const docId = doc?.id ?? null;

  const metadataWidth = Number(currentMetadata?.width);
  const metadataHeight = Number(currentMetadata?.height);

  useEffect(() => {
    if (!onNavigatorSnapshot || !docId) {
      return undefined;
    }
    const snapshot = {
      url: currentUrl || null,
      alt: title,
      canGoPrev,
      canGoNext,
      goPrev: navigator.goPrev,
      goNext: navigator.goNext,
      ordinal,
      width: Number.isFinite(metadataWidth) && metadataWidth > 0 ? metadataWidth : null,
      height: Number.isFinite(metadataHeight) && metadataHeight > 0 ? metadataHeight : null,
    };
    onNavigatorSnapshot(docId, snapshot);
    return () => onNavigatorSnapshot(docId, null);
  }, [
    docId,
    currentUrl,
    title,
    canGoPrev,
    canGoNext,
    ordinal,
    metadataWidth,
    metadataHeight,
    navigator.goPrev,
    navigator.goNext,
    onNavigatorSnapshot,
  ]);
  const hasPreview = Boolean(currentUrl);
  const cardClasses = ['skeuo-item__card'];
  if (!hasPreview) cardClasses.push('skeuo-item__card--empty');
  const showNav = hasPreview && (cardinality > 1 || canGoPrev || canGoNext);

  return (
    <div className={cardClasses.join(' ')}>
      {hasPreview ? (
        <img src={currentUrl} alt={title} />
      ) : (
        <div className="skeuo-item__empty">
          <div className="skeuo-item__placeholder">DOC</div>
          <div className="skeuo-item__title" title={title}>
            {title}
          </div>
        </div>
      )}
      {showNav ? (
        <div className="skeuo-card__nav">
          <button
            type="button"
            className="skeuo-card__nav-button"
            onClick={(event) => {
              preventAll(event);
              navigator.goPrev();
            }}
            onPointerDown={(event) => {
              preventAll(event);
            }}
            onPointerUp={(event) => {
              preventAll(event);
            }}
            onMouseDown={(event) => {
              preventAll(event);
            }}
            onMouseUp={(event) => {
              preventAll(event);
            }}
            disabled={!canGoPrev}
            aria-label="Previous preview"
          >
            <ArrowLeftIcon />
          </button>
          <button
            type="button"
            className="skeuo-card__nav-button"
            onClick={(event) => {
              preventAll(event);
              navigator.goNext();
            }}
            onPointerDown={(event) => {
              preventAll(event);
            }}
            onPointerUp={(event) => {
              preventAll(event);
            }}
            onMouseDown={(event) => {
              preventAll(event);
            }}
            onMouseUp={(event) => {
              preventAll(event);
            }}
            disabled={!canGoNext}
            aria-label="Next preview"
          >
            <ArrowRightIcon />
          </button>
        </div>
      ) : null}
    </div>
  );
};

const generateInitialLayout = (
  entries,
  {
    canvasWidth,
    canvasHeight,
    padding,
    startZ = 0,
    rotationRange = ROTATION_RANGE,
    minSpacing = 48,
    shelfWidth = 0,
  },
) => {
  const layout = new Map();
  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 = [];

  const resolveBounds = (width, height) => {
    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, y, radius) => {
    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 };
};

const createDragPreview = (node, clientX, clientY) => {
  if (!(node instanceof HTMLElement)) {
    return null;
  }
  const rect = node.getBoundingClientRect();
  const safeClientX = Number.isFinite(clientX) ? clientX : rect.left + rect.width / 2;
  const safeClientY = Number.isFinite(clientY) ? clientY : rect.top + rect.height / 2;
  const offsetX = clamp(safeClientX - rect.left, 0, rect.width);
  const offsetY = clamp(safeClientY - rect.top, 0, rect.height);
  const clone = node.cloneNode(true);
  clone.style.position = 'absolute';
  clone.style.top = '-9999px';
  clone.style.left = '-9999px';
  clone.style.pointerEvents = 'none';
  clone.style.opacity = '1';
  clone.style.transform = 'none';
  document.body.appendChild(clone);
  return { clone, offsetX, offsetY };
};

const cleanupPreview = (previewNode) => {
  if (previewNode && previewNode.parentNode) {
    previewNode.parentNode.removeChild(previewNode);
  }
};

const usePreviewMetadata = (documents, getDocumentAsset, ensureAssetUrl) => {
  const [metadataMap, setMetadataMap] = useState(() => new Map());

  useEffect(() => {
    let cancelled = false;
    const docs = Array.isArray(documents) ? documents : [];
    if (!docs.length) {
      setMetadataMap(new Map());
      return () => {
        cancelled = true;
      };
    }

    const fetchMetadataForDoc = async (doc) => {
      if (!doc?.id) {
        return null;
      }

      const docId = String(doc.id);
      const resolveAsset = (type) => (typeof getDocumentAsset === 'function' ? getDocumentAsset(doc, type) : null);

      let asset = resolveAsset('preview') || resolveAsset('thumbnail');
      let view = createAssetView(asset);
      let metadata = view.getPrimaryMetadata();

      const hasDimensions = (meta) =>
        Number.isFinite(Number(meta?.width)) && Number.isFinite(Number(meta?.height)) &&
        Number(meta.width) > 0 && Number(meta.height) > 0;

      if (!hasDimensions(metadata) && ensureAssetUrl && docId && asset?.id) {
        try {
          const ensured = await ensureAssetUrl(doc.id, asset, { start: 1, limit: 1 });
          if (ensured) {
            asset = ensured;
            view = createAssetView(asset);
            metadata = view.getPrimaryMetadata();
          }
        } catch (error) {
          console.warn('[skeuo] ensureDocumentSize metadata fetch failed', error);
        }
      }

      if (!hasDimensions(metadata)) {
        return null;
      }

      const width = Number(metadata.width);
      const height = Number(metadata.height);
      if (!Number.isFinite(width) || !Number.isFinite(height)) {
        return null;
      }

      return [docId, { width, height }];
    };

    Promise.all(docs.map((doc) => fetchMetadataForDoc(doc)))
      .then((entries) => {
        if (cancelled) return;
        const next = new Map();
        entries.forEach((entry) => {
          if (entry) {
            next.set(entry[0], entry[1]);
          }
        });
        setMetadataMap(next);
      })
      .catch(() => {
        if (!cancelled) {
          setMetadataMap(new Map());
        }
      });

    return () => {
      cancelled = true;
    };
  }, [documents, getDocumentAsset, ensureAssetUrl]);

  return metadataMap;
};

function seededRandom(input) {
  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, min, max) {
  const span = max - min;
  if (span <= 0) return min;
  const seed = seededRandom(seedKey);
  return min + seed * span;
}

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

const clampCardDimensions = (width, height) => {
  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(CARD_MIN / w, CARD_MIN / h);
  const high = Math.min(CARD_MAX / w, CARD_MAX / h);

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

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

  const best = candidates.reduce((acc, scale) => {
    const scaledWidth = w * scale;
    const scaledHeight = h * scale;
    const violation = Math.max(
      Math.max(CARD_MIN - scaledWidth, 0),
      Math.max(scaledWidth - CARD_MAX, 0),
      Math.max(CARD_MIN - scaledHeight, 0),
      Math.max(scaledHeight - 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),
  };
};

const computeFallbackCardSize = (docId) => {
  const baseSeed = seededRandom(`${docId}:fallback-size`);
  const aspectSeed = seededRandom(`${docId}:fallback-aspect`);

  const width = CARD_MIN + baseSeed * (CARD_MAX - CARD_MIN);
  const isPortrait = aspectSeed < 0.5;
  const normalizedSeed = isPortrait ? aspectSeed / 0.5 : (aspectSeed - 0.5) / 0.5;
  const aspectRange = 0.75; // keeps generated ratio pleasant but varied
  const aspect = isPortrait
    ? 1 + normalizedSeed * aspectRange
    : 1 / (1 + normalizedSeed * aspectRange);
  const height = width * aspect;

  return clampCardDimensions(width, height);
};

const DesktopWorkspace = ({
  documents = [],
  searchResults = null,
  onDocumentOpen,
  onAssignTagToDocument = null,
  onRemoveTagFromDocument = null,
  ensureAssetUrl = null,
  getDocumentAsset = () => null,
  activeTagIds = [],
}) => {
  const items = useMemo(() => (searchResults ? searchResults : documents), [documents, searchResults]);


  const containerRef = useRef(null);
  const layoutRef = useRef(new Map());
  const itemRefs = useRef(new Map());
  const zCounterRef = useRef(10);

  const [layoutSnapshot, setLayoutSnapshot] = useState(() => new Map());

  const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });
  const [visibleDocIds, setVisibleDocIds] = useState(() => new Set());
  const [draggingId, setDraggingId] = useState(null);
  const [overlayDocId, setOverlayDocId] = useState(null);
  const [overlayOriginRect, setOverlayOriginRect] = useState(null);
  const [overlayOriginTransform, setOverlayOriginTransform] = useState(null);
  const [previewSnapshots, setPreviewSnapshots] = useState(() => new Map());
  const [docSizeVersion, setDocSizeVersion] = useState(0);
  const [tagDropTargetId, setTagDropTargetId] = useState(null);
  const [pendingTagDocId, setPendingTagDocId] = useState(null);
  const [pendingRemovalTag, setPendingRemovalTag] = useState(null);
  const draggingTagRef = useRef(null);
  const pendingDocTagDragRef = useRef(null);
  const docSizeMapRef = useRef(new Map());
  const documentLookupRef = useRef(new Map());
  const removalCursorActiveRef = useRef(false);
  const previewMetadata = usePreviewMetadata(items, getDocumentAsset, ensureAssetUrl);
  const documentLookup = useMemo(() => {
    const map = new Map();
    items.forEach((doc) => {
      const key = doc?.id != null ? String(doc.id) : null;
      if (key) {
        map.set(key, doc);
      }
    });
    return map;
  }, [items]);

  useEffect(() => {
    documentLookupRef.current = documentLookup;
  }, [documentLookup]);

  const applySnapshotDimensions = useCallback((docKey, snapshot) => {
    const width = Number(snapshot?.width);
    const height = Number(snapshot?.height);
    if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
      return;
    }
    const normalized = clampCardDimensions(width, height);
    if (!normalized) {
      return;
    }
    const existing = docSizeMapRef.current.get(docKey);
    if (existing && existing.width === normalized.width && existing.height === normalized.height) {
      return;
    }
    const next = new Map(docSizeMapRef.current);
    next.set(docKey, { ...normalized, source: 'snapshot' });
    docSizeMapRef.current = next;
    setDocSizeVersion((value) => value + 1);
  }, []);
  const handleNavigatorSnapshot = useCallback(
    (docId, snapshot) => {
      const docKey = docId != null ? String(docId) : null;
      if (!docKey) {
        return;
      }

      setPreviewSnapshots((previous) => {
        const prevSnapshot = previous.get(docKey);
        if (!snapshot) {
          if (!previous.has(docKey)) {
            return previous;
          }
          const next = new Map(previous);
          next.delete(docKey);
          return next;
        }

        const next = new Map(previous);
        const sameSnapshot =
          prevSnapshot &&
          prevSnapshot.url === snapshot.url &&
          prevSnapshot.alt === snapshot.alt &&
          prevSnapshot.canGoPrev === snapshot.canGoPrev &&
          prevSnapshot.canGoNext === snapshot.canGoNext &&
          prevSnapshot.goPrev === snapshot.goPrev &&
          prevSnapshot.goNext === snapshot.goNext &&
          prevSnapshot.ordinal === snapshot.ordinal &&
          prevSnapshot.width === snapshot.width &&
          prevSnapshot.height === snapshot.height;
        if (sameSnapshot) {
          return previous;
        }
        next.set(docKey, snapshot);
        return next;
      });

      if (snapshot) {
        applySnapshotDimensions(docKey, snapshot);
      }
    },
    [applySnapshotDimensions],
  );
  const activeTagSet = useMemo(() => {
    if (!Array.isArray(activeTagIds) || activeTagIds.length === 0) {
      return new Set();
    }
    const set = new Set();
    activeTagIds.forEach((id) => {
      if (id != null) {
        set.add(String(id));
      }
    });
    return set;
  }, [activeTagIds]);

  const resolvePreviewDimensions = useCallback(
    (doc) => {
      if (!doc?.id) {
        return null;
      }
      return previewMetadata.get(String(doc.id)) || null;
    },
    [previewMetadata],
  );

  useEffect(() => {
    if (!ensureAssetUrl) {
      return;
    }

    visibleDocIds.forEach((docId) => {
      const doc = documentLookup.get(docId);
      if (!doc) {
        return;
      }
      resolveDocumentAssetUrl(doc, 'preview', {
        ensureAssetUrl,
        getAsset: getDocumentAsset,
      });
    });
  }, [visibleDocIds, ensureAssetUrl, getDocumentAsset, documentLookup]);

  const requestCanvasFocus = useCallback(() => {
    const canvas = containerRef.current;
    if (!canvas || typeof canvas.focus !== 'function') {
      return;
    }

    const focusTarget = () => {
      try {
        if (DEBUG_FOCUS) {
          console.log('[skeuo] focusCanvas -> attempting focus', canvas);
        }
        canvas.focus({ preventScroll: true });
        if (DEBUG_FOCUS) {
          console.log('[skeuo] focusCanvas: applied focus. activeElement:', document?.activeElement);
        }
      } catch (error) {
        if (DEBUG_FOCUS) {
          console.warn('[skeuo] focusTarget failed to focus canvas', error);
        }
      }
    };

    if (typeof window === 'undefined') {
      focusTarget();
      return;
    }

    if (DEBUG_FOCUS) {
      console.log('[skeuo] requestCanvasFocus -> scheduling deferred focus');
    }

    if (typeof window.requestAnimationFrame === 'function') {
      window.requestAnimationFrame(() => {
        if (DEBUG_FOCUS) {
          console.log('[skeuo] requestCanvasFocus -> executing deferred focus (rAF)');
        }
        focusTarget();
      });
    } else {
      setTimeout(() => {
        if (DEBUG_FOCUS) {
          console.log('[skeuo] requestCanvasFocus -> executing deferred focus (timeout)');
        }
        focusTarget();
      }, 0);
    }
  }, []);

  const updateRemovalCursor = useCallback((active) => {
    if (typeof document === 'undefined') {
      return;
    }
    if (removalCursorActiveRef.current === active) {
      return;
    }
    const body = document.body;
    if (!body) {
      return;
    }
    removalCursorActiveRef.current = active;
    if (active) {
      body.classList.add('skeuo-cursor-remove');
    } else {
      body.classList.remove('skeuo-cursor-remove');
    }
  }, []);

  useEffect(
    () => () => {
      updateRemovalCursor(false);
    },
    [updateRemovalCursor],
  );

  const isTagTransfer = useCallback((event) => {
    const types = event.dataTransfer?.types;
    if (!types) return false;
    return TAG_MIME_TYPES.some((type) =>
      typeof types.includes === 'function'
        ? types.includes(type)
        : Array.from(types).includes(type),
    );
  }, []);

  const handleTagDragEnd = useCallback(() => {
    updateRemovalCursor(false);
    setTagDropTargetId(null);
  }, [updateRemovalCursor]);

  const ensureDocumentSize = useCallback((doc) => {
    if (!doc?.id) {
      return null;
    }
    return docSizeMapRef.current.get(String(doc.id)) || null;
  }, []);

  useEffect(() => {
    const current = docSizeMapRef.current;
    const next = new Map(current);
    const itemKeys = new Set(items.filter((doc) => doc?.id != null).map((doc) => String(doc.id)));
    let changed = false;

    items.forEach((doc) => {
      if (!doc?.id) {
        return;
      }
      const key = String(doc.id);
      const existing = next.get(key) || null;
      const meta = previewMetadata.get(key);
      if (meta) {
        const normalized = clampCardDimensions(meta.width, meta.height);
        if (normalized) {
          if (existing?.source === 'snapshot') {
            return;
          }
          if (!existing || existing.width !== normalized.width || existing.height !== normalized.height || existing.source !== 'metadata') {
            next.set(key, { ...normalized, source: 'metadata' });
            changed = true;
          }
          return;
        }
      }

      if (!existing) {
        const fallback = computeFallbackCardSize(key);
        if (fallback) {
          next.set(key, { ...fallback, source: 'fallback' });
          changed = true;
        }
      }
    });

    current.forEach((_, key) => {
      if (!itemKeys.has(key)) {
        next.delete(key);
        changed = true;
      }
    });

    if (changed) {
      docSizeMapRef.current = next;
      setDocSizeVersion((value) => value + 1);
    }
  }, [items, previewMetadata]);

  const overlayDisplay = useMemo(() => {
    if (!overlayDocId) {
      return null;
    }
    const snapshot = previewSnapshots.get(overlayDocId);
    if (!snapshot || !snapshot.url) {
      return null;
    }
    const doc = documentLookup.get(overlayDocId);
    const alt = snapshot.alt || doc?.title || doc?.original_name || 'Document preview';
    return {
      url: snapshot.url,
      alt,
      canGoPrev: snapshot.canGoPrev,
      canGoNext: snapshot.canGoNext,
      goPrev: snapshot.goPrev,
      goNext: snapshot.goNext,
    };
  }, [overlayDocId, previewSnapshots, documentLookup]);

  const closeOverlay = useCallback(() => {
    setOverlayDocId(null);
    setOverlayOriginRect(null);
    setOverlayOriginTransform(null);
  }, []);

  useEffect(() => {
    if (overlayDocId && !documentLookup.has(overlayDocId)) {
      setOverlayDocId(null);
      setOverlayOriginRect(null);
      setOverlayOriginTransform(null);
    }
  }, [overlayDocId, documentLookup]);

  const resolveBaseMetrics = useCallback(
    (doc, cardWidth, cardHeight) => {
      const previewDims = doc ? resolvePreviewDimensions(doc) : null;
      if (previewDims?.width && previewDims?.height) {
        const baseWidth = Math.max(previewDims.width, cardWidth);
        const baseHeight = Math.max(previewDims.height, cardHeight);
        const scaleX = cardWidth / baseWidth;
        const scaleY = cardHeight / baseHeight;
        const baseScale = Math.min(scaleX, scaleY, 1);
        return {
          baseWidth,
          baseHeight,
          baseScale: Number.isFinite(baseScale) && baseScale > 0 ? baseScale : 1,
        };
      }
      return {
        baseWidth: cardWidth,
        baseHeight: cardHeight,
        baseScale: 1,
      };
    },
    [resolvePreviewDimensions],
  );
const recalcVisibleDocIds = useCallback(() => {
    const layoutMap = layoutRef.current;
    const canvasWidth = canvasSize.width || DEFAULT_CANVAS_WIDTH;
    const canvasHeight = canvasSize.height || DEFAULT_CANVAS_HEIGHT;

    if (!layoutMap.size || canvasWidth <= 0 || canvasHeight <= 0) {
      setVisibleDocIds((prev) => (prev.size ? new Set() : prev));
      return;
    }

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

    const entries = [];
    layoutMap.forEach((entry, rawId) => {
      const docKey = rawId != null ? String(rawId) : null;
      if (!docKey) {
        return;
      }
      const doc = documentLookup.get(docKey);
      if (!doc) {
        return;
      }
      const sizeInfo = ensureDocumentSize(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 ?? CANVAS_PADDING + cardWidth / 2;
      const centerY = entry?.centerY ?? 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) {
      setVisibleDocIds((prev) => (prev.size ? new Set() : prev));
      return;
    }

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

    const visiblePolygons = [];
    const result = new Set();

    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);
      }
    });

    setVisibleDocIds((prev) => {
      if (prev.size === result.size) {
        let same = true;
        prev.forEach((id) => {
          if (!result.has(id)) {
            same = false;
          }
        });
        if (same) {
          result.forEach((id) => {
            if (!prev.has(id)) {
              same = false;
            }
          });
        }
        if (same) {
          return prev;
        }
      }
      return result;
    });
  }, [
    canvasSize.width,
    canvasSize.height,
    ensureDocumentSize,
    documentLookup,
  ]);

const syncLayoutSnapshot = useCallback(() => {
    setLayoutSnapshot(new Map(layoutRef.current));
  }, []);

  useLayoutEffect(() => {
    const container = containerRef.current;
    if (!container) return () => {};

    const nodeEnv = typeof globalThis !== 'undefined' ? globalThis.process?.env?.NODE_ENV : undefined;
    if (nodeEnv !== 'production') {
      console.log('[skeuo] canvas element', container);
    }

    const commitSize = () => {
      const rect = container.getBoundingClientRect();
      const width = Math.floor(rect.width) || 0;
      const height = Math.floor(rect.height) || 0;
      setCanvasSize((prev) => {
        if (prev.width === width && prev.height === height) {
          return prev;
        }
        return { width, height };
      });
    };

    commitSize();

    if (typeof ResizeObserver === 'undefined') {
      window.addEventListener('resize', commitSize);
      return () => {
        window.removeEventListener('resize', commitSize);
      };
    }

    const observer = new ResizeObserver(() => {
      commitSize();
    });

    observer.observe(container);
    return () => observer.disconnect();
  }, []);
  useLayoutEffect(() => {
    if (!containerRef.current || !canvasSize.width || !canvasSize.height) {
      return;
    }

    if (!items.length) {
      layoutRef.current = new Map();
      syncLayoutSnapshot();
      return;
    }

    const missingSizes = items.some((doc) => !ensureDocumentSize(doc));
    if (missingSizes) {
      return;
    }

    const previous = layoutRef.current;
    const next = new Map();
    let maxZ = zCounterRef.current;
    const canvasWidth = canvasSize.width || DEFAULT_CANVAS_WIDTH;
    const canvasHeight = canvasSize.height || DEFAULT_CANVAS_HEIGHT;
    const docsNeedingLayout = [];

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

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

      const existing = previous.get(doc.id);
      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(doc.id, { centerX, centerY, rotation, z, width: docWidth, height: docHeight });
        return;
      }

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

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

    layoutRef.current = next;
    zCounterRef.current = Math.max(zCounterRef.current, maxZ);
    syncLayoutSnapshot();
    recalcVisibleDocIds();
  }, [
    items,
    canvasSize.width,
    canvasSize.height,
    docSizeVersion,
    ensureDocumentSize,
    syncLayoutSnapshot,
    recalcVisibleDocIds,
  ]);

  useEffect(() => {
    recalcVisibleDocIds();
  }, [recalcVisibleDocIds, items.length, canvasSize.width, canvasSize.height, docSizeVersion]);

  useEffect(() => {
    if (draggingId && !items.some((doc) => doc.id === draggingId)) {
      setDraggingId(null);
    }
  }, [draggingId, items]);

  const bringToFront = useCallback(
    (docId) => {
      zCounterRef.current += 1;
      const entry = layoutRef.current.get(docId);
      if (!entry) return;
      const updated = { ...entry, z: zCounterRef.current };
      layoutRef.current.set(docId, updated);
      syncLayoutSnapshot();
      recalcVisibleDocIds();
    },
    [syncLayoutSnapshot, recalcVisibleDocIds],
  );

  const openOverlayForDoc = useCallback(
    (docId, originInfo = null) => {
      if (!docId) {
        return;
      }
      const docKey = String(docId);
      const snapshot = previewSnapshots.get(docKey);
      if (!snapshot || !snapshot.url) {
        return;
      }
      const container = itemRefs.current.get(docId);
      const imageNode = container?.querySelector?.('.skeuo-item__card img');
      if (!container || !imageNode) {
        return;
      }
      const rect = imageNode.getBoundingClientRect();
      let originTransform = null;
      if (originInfo) {
        const { rotation = 0, scale = 1, width: originWidth, height: originHeight } = originInfo;
        originTransform = {
          rotation,
          scaleX: scale,
          scaleY: scale,
          baseWidth: originWidth,
          baseHeight: originHeight,
        };
      }
      if (!originTransform) {
        const entry = layoutRef.current.get(docId) || null;
        const doc = documentLookup.get(docKey) || null;
        const sizeInfo = ensureDocumentSize(doc);
        if (!sizeInfo) {
          return;
        }
        const { width: cardWidth, height: cardHeight } = sizeInfo;
        const { baseWidth, baseHeight, baseScale } = resolveBaseMetrics(doc, cardWidth, cardHeight);
        const effectiveWidth = baseWidth * baseScale;
        const effectiveHeight = baseHeight * baseScale;
        originTransform = {
          rotation: entry?.rotation ?? 0,
          scaleX: baseScale,
          scaleY: baseScale,
          baseWidth: Number.isFinite(effectiveWidth) && effectiveWidth > 0 ? effectiveWidth : cardWidth,
          baseHeight: Number.isFinite(effectiveHeight) && effectiveHeight > 0 ? effectiveHeight : cardHeight,
        };
      }
      bringToFront(docId);
      setOverlayOriginRect(rect);
      setOverlayOriginTransform(originTransform);
      setOverlayDocId(docKey);
    },
    [
      bringToFront,
      previewSnapshots,
      itemRefs,
      setOverlayOriginTransform,
      ensureDocumentSize,
      resolveBaseMetrics,
      documentLookup,
      layoutRef,
    ],
  );
  const handleTagDragEnterDoc = useCallback(
    (event, docId) => {
      if (!isTagTransfer(event)) {
        return;
      }
      preventAll(event);
      updateRemovalCursor(false);
      if (tagDropTargetId !== docId) {
        setTagDropTargetId(docId);
      }
    },
    [isTagTransfer, tagDropTargetId, updateRemovalCursor],
  );

  const handleTagDragOverDoc = useCallback(
    (event, docId) => {
      if (!isTagTransfer(event)) {
        return;
      }
      preventAll(event);
      updateRemovalCursor(false);
      const activeDrag = draggingTagRef.current;
      event.dataTransfer.dropEffect = activeDrag?.sourceDocId ? 'move' : 'copy';
      if (tagDropTargetId !== docId) {
        setTagDropTargetId(docId);
      }
    },
    [isTagTransfer, tagDropTargetId, updateRemovalCursor],
  );

  const handleTagDragLeaveDoc = useCallback((event, docId) => {
    if (!isTagTransfer(event)) {
      return;
    }
    preventAll(event);
    if (
      event.currentTarget instanceof HTMLElement &&
      event.relatedTarget instanceof Node &&
      event.currentTarget.contains(event.relatedTarget)
    ) {
      return;
    }
    setTagDropTargetId((current) => (current === docId ? null : current));
    updateRemovalCursor(false);
  }, [isTagTransfer, updateRemovalCursor]);

  const markActiveTagDropHandled = useCallback((tagId, sourceDocId = null) => {
    const state = draggingTagRef.current;
    if (!state) {
      return;
    }
    if (state.tagId !== tagId) {
      return;
    }
    if (sourceDocId && state.sourceDocId !== sourceDocId) {
      return;
    }
    state.dropHandled = true;
  }, []);

  const handleTagDropOnDoc = useCallback(
    async (event, doc) => {
      if (!doc || !isTagTransfer(event) || typeof onAssignTagToDocument !== 'function') {
        if (DEBUG_DROP) {
          console.log('[skeuo] handleTagDropOnDoc: drop ignored', { doc, hasTransfer: isTagTransfer(event) });
        }
        return;
      }
      preventAll(event);
      if (DEBUG_DROP) {
        console.log('[skeuo] handleTagDropOnDoc: drop accepted for doc', doc.id, 'event', event);
      }
      setTagDropTargetId(null);

      const payload = parseTagTransferPayload(event);
      if (!payload && DEBUG_DROP) {
        console.warn('[skeuo] handleTagDropOnDoc: failed to parse payload');
      }

      if (!payload?.id) {
        if (DEBUG_DROP) {
          console.log('[skeuo] handleTagDropOnDoc: missing tag id payload', payload);
        }
        requestCanvasFocus();
        return;
      }

      const tagId = payload.id;
      const sourceDocId = payload.sourceDocId || null;
      if (DEBUG_DROP) {
        console.log('[skeuo] handleTagDropOnDoc: parsed payload', { tagId, sourceDocId });
      }

      if (sourceDocId && sourceDocId === doc.id) {
        markActiveTagDropHandled(tagId, sourceDocId);
        if (DEBUG_DROP) {
          console.log('[skeuo] handleTagDropOnDoc: drop from same doc ignored', tagId);
        }
        requestCanvasFocus();
        return;
      }

      const alreadyAssigned = Array.isArray(doc.tags)
        ? doc.tags.some((tag) => tag.id === tagId)
        : false;
      if (alreadyAssigned) {
        markActiveTagDropHandled(tagId, sourceDocId);
        if (DEBUG_DROP) {
          console.log('[skeuo] handleTagDropOnDoc: tag already assigned', tagId);
        }
        requestCanvasFocus();
        return;
      }

      const movingBetweenDocuments = Boolean(sourceDocId && sourceDocId !== doc.id);
      if (DEBUG_DROP) {
        console.log('[skeuo] handleTagDropOnDoc: movingBetweenDocuments', movingBetweenDocuments);
      }

      setPendingTagDocId(doc.id);
      try {
        await onAssignTagToDocument({ documentId: doc.id, tagId, tag: payload });
        markActiveTagDropHandled(tagId, sourceDocId);
        if (DEBUG_DROP) {
          console.log('[skeuo] handleTagDropOnDoc: assigned tag', tagId, 'to doc', doc.id);
        }
        if (movingBetweenDocuments && typeof onRemoveTagFromDocument === 'function') {
          setPendingRemovalTag({ docId: sourceDocId, tagId });
          try {
            await onRemoveTagFromDocument(sourceDocId, tagId);
            if (DEBUG_DROP) {
              console.log('[skeuo] handleTagDropOnDoc: removed tag from source doc', sourceDocId);
            }
          } finally {
            setPendingRemovalTag(null);
          }
        }
      } finally {
        setPendingTagDocId(null);
        if (DEBUG_DROP) {
          console.log('[skeuo] handleTagDropOnDoc: finalizing drop for tag', tagId);
        }
        requestCanvasFocus();
      }
    },
    [
      isTagTransfer,
      markActiveTagDropHandled,
      onAssignTagToDocument,
      onRemoveTagFromDocument,
      requestCanvasFocus,
    ],
  );

  const handleCanvasDragOver = useCallback(
    (event) => {
      if (!isTagTransfer(event)) {
        return;
      }
      preventAll(event);
      event.dataTransfer.dropEffect = 'move';
      updateRemovalCursor(true);
      setTagDropTargetId(null);
    },
    [isTagTransfer, updateRemovalCursor],
  );

  const handleCanvasDragLeave = useCallback(
    (event) => {
      if (!isTagTransfer(event)) {
        return;
      }
      preventAll(event);
      if (
        event.currentTarget instanceof HTMLElement &&
        event.relatedTarget instanceof Node &&
        event.currentTarget.contains(event.relatedTarget)
      ) {
        return;
      }
      updateRemovalCursor(false);
    },
    [isTagTransfer, updateRemovalCursor],
  );

  const handleCanvasDrop = useCallback(
    async (event) => {
      if (!isTagTransfer(event)) {
        return;
      }
      preventAll(event);
      updateRemovalCursor(false);
      requestCanvasFocus();
      setTagDropTargetId(null);

      const payload = parseTagTransferPayload(event);
      if (!payload?.id) {
        return;
      }

      const tagId = payload.id;
      const sourceDocId = payload.sourceDocId || null;
      markActiveTagDropHandled(tagId, sourceDocId);

      if (!sourceDocId || typeof onRemoveTagFromDocument !== 'function') {
        return;
      }

      setPendingRemovalTag({ docId: sourceDocId, tagId });
      try {
        await onRemoveTagFromDocument(sourceDocId, tagId);
      } catch (error) {
        console.error('Failed to remove tag via canvas drop', error);
      } finally {
        setPendingRemovalTag(null);
      }
    },
    [
      isTagTransfer,
      markActiveTagDropHandled,
      onRemoveTagFromDocument,
      requestCanvasFocus,
      updateRemovalCursor,
    ],
  );

  const handleDocTagPointerDown = useCallback((event, doc, tag) => {
    event.stopPropagation();
    if (!doc || !tag) {
      pendingDocTagDragRef.current = null;
      return;
    }
    const startX = Number.isFinite(event.clientX)
      ? event.clientX
      : Number.isFinite(event.pageX)
      ? event.pageX
      : 0;
    const startY = Number.isFinite(event.clientY)
      ? event.clientY
      : Number.isFinite(event.pageY)
      ? event.pageY
      : 0;
    pendingDocTagDragRef.current = {
      docId: doc.id,
      tagId: tag.id,
      startX,
      startY,
    };
    updateRemovalCursor(false);
  }, [updateRemovalCursor]);

  const handleDocTagDragStart = useCallback(
    (event, doc, tag) => {
      if (!doc || !tag) {
        return;
      }
      event.stopPropagation();
      try {
        if (event.dataTransfer) {
          event.dataTransfer.effectAllowed = 'copyMove';
        }
      } catch (error) {
        console.warn('[skeuo] Failed to set drag effect', error);
      }

      const payload = JSON.stringify({ id: tag.id, label: tag.label, sourceDocId: doc.id });
      try {
        event.dataTransfer?.setData('application/x-papercrate-tag', payload);
        event.dataTransfer?.setData('text/papercrate-tag', payload);
        event.dataTransfer?.setData('text/plain', tag.label || 'Tag');
      } catch (error) {
        console.warn('[skeuo] Failed to populate drag data for tag', error);
      }

      const pending = pendingDocTagDragRef.current;
      const node = event.currentTarget;
      const startX = Number.isFinite(event.clientX)
        ? event.clientX
        : Number.isFinite(event.pageX)
        ? event.pageX
        : 0;
      const startY = Number.isFinite(event.clientY)
        ? event.clientY
        : Number.isFinite(event.pageY)
        ? event.pageY
        : 0;
      const initialX =
        pending && pending.docId === doc.id && pending.tagId === tag.id && Number.isFinite(pending.startX)
          ? pending.startX
          : startX;
      const initialY =
        pending && pending.docId === doc.id && pending.tagId === tag.id && Number.isFinite(pending.startY)
          ? pending.startY
          : startY;
      pendingDocTagDragRef.current = null;
      let preview = null;
      if (node instanceof HTMLElement) {
        preview = createDragPreview(node, event.clientX, event.clientY);
        if (preview && event.dataTransfer) {
          try {
            event.dataTransfer.setDragImage(preview.clone, preview.offsetX, preview.offsetY);
          } catch (error) {
            console.warn('[skeuo] Failed to set drag image', error);
          }
        }
      }
      draggingTagRef.current = {
        sourceDocId: doc.id,
        tagId: tag.id,
        tagLabel: tag.label || 'Tag',
        startX: initialX,
        startY: initialY,
        distance: 0,
        element: node instanceof HTMLElement ? node : null,
        dropHandled: false,
        hasPosition: Number.isFinite(initialX) && Number.isFinite(initialY),
        previewClone: preview?.clone || null,
      };

      const hideNode = () => {
        if (draggingTagRef.current?.element === node) {
          node.classList.add('is-drag-hidden');
        }
      };
      if (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function') {
        window.requestAnimationFrame(hideNode);
      } else {
        setTimeout(hideNode, 0);
      }
    },
    [],
  );

  const handleDocTagDrag = useCallback(
    (event) => {
      const state = draggingTagRef.current;
      if (!state) {
        updateRemovalCursor(false);
        return;
      }
      if (!state.hasPosition && Number.isFinite(event.clientX) && Number.isFinite(event.clientY)) {
        state.startX = event.clientX;
        state.startY = event.clientY;
        state.hasPosition = true;
      }
      const clientX = Number.isFinite(event.clientX) ? event.clientX : state.startX;
      const clientY = Number.isFinite(event.clientY) ? event.clientY : state.startY;
      const deltaX = clientX - state.startX;
      const deltaY = clientY - state.startY;
      const distance = Math.hypot(deltaX, deltaY);
      if (Number.isFinite(distance)) {
        state.distance = distance;
      }
      const removalActive =
        Boolean(state.sourceDocId) &&
        !tagDropTargetId &&
        Number.isFinite(state.distance) &&
        state.distance >= TAG_REMOVE_DISTANCE;
      updateRemovalCursor(removalActive);
    },
    [tagDropTargetId, updateRemovalCursor],
  );

  const handleDocTagDragEnd = useCallback(
    (event) => {
      handleTagDragEnd();
      const state = draggingTagRef.current;
      if (!state) {
        return;
      }
      draggingTagRef.current = null;

      const node = state.element;
      const showNode = () => {
        if (node instanceof HTMLElement) {
          node.classList.remove('is-drag-hidden');
        }
      };
      cleanupPreview(state.previewClone);

      const scheduleShowNode = () => {
        if (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function') {
          window.requestAnimationFrame(showNode);
        } else {
          setTimeout(showNode, 0);
        }
      };

      const dropEffect = event?.dataTransfer?.dropEffect || 'none';
      console.log('[skeuo] dragEnd dropEffect', dropEffect, 'dropHandled', state.dropHandled);
      const shouldRemove =
        !state.dropHandled &&
        dropEffect === 'none' &&
        state.sourceDocId &&
        typeof onRemoveTagFromDocument === 'function' &&
        (state.distance || 0) >= TAG_REMOVE_DISTANCE;

      if (!shouldRemove) {
        console.log('[skeuo] dragEnd -> no removal. distance:', state.distance);
        scheduleShowNode();
        requestCanvasFocus();
        updateRemovalCursor(false);
        return;
      }

      requestCanvasFocus();
      updateRemovalCursor(false);
      setPendingRemovalTag({ docId: state.sourceDocId, tagId: state.tagId });
      void (async () => {
        try {
          await onRemoveTagFromDocument(state.sourceDocId, state.tagId);
          console.log('[skeuo] dragEnd -> removed tag due to fling');
        } catch (error) {
          console.error('Failed to remove tag after drag', error);
          scheduleShowNode();
        } finally {
          setPendingRemovalTag(null);
        }
      })();
    },
    [requestCanvasFocus, handleTagDragEnd, onRemoveTagFromDocument, updateRemovalCursor],
  );

  const contextValue = useMemo(
    () => ({
      layoutRef,
      itemRefs,
      documentLookup,
      ensureDocumentSize,
      resolveBaseMetrics,
      bringToFront,
      setDraggingId,
      syncLayoutSnapshot,
      canvasSize,
      openOverlayForDoc,
      recalcVisibleDocIds,
      settings: {
        canvasPadding: CANVAS_PADDING,
        defaultCanvasWidth: DEFAULT_CANVAS_WIDTH,
        defaultCanvasHeight: DEFAULT_CANVAS_HEIGHT,
        debugDrag: DEBUG_DRAG,
      },
      items,
      containerRef,
      handleCanvasDragOver,
      handleCanvasDragLeave,
      handleCanvasDrop,
      layoutSnapshot,
      docSizeVersion,
      visibleDocIds,
      draggingId,
      tagDropTargetId,
      pendingTagDocId,
      pendingRemovalTag,
      onDocumentOpen,
      ensureAssetUrl,
      getDocumentAsset,
      handleNavigatorSnapshot,
      activeTagSet,
      handleTagDragEnterDoc,
      handleTagDragOverDoc,
      handleTagDragLeaveDoc,
      handleTagDropOnDoc,
      handleDocTagPointerDown,
      handleDocTagDragStart,
      handleDocTagDrag,
      handleDocTagDragEnd,
      overlayDisplay,
      closeOverlay,
      overlayOriginRect,
      overlayOriginTransform,
    }),
    [
      activeTagSet,
      bringToFront,
      canvasSize,
      closeOverlay,
      containerRef,
      draggingId,
      ensureAssetUrl,
      ensureDocumentSize,
      getDocumentAsset,
      handleCanvasDragLeave,
      handleCanvasDragOver,
      handleCanvasDrop,
      handleDocTagDrag,
      handleDocTagDragEnd,
      handleDocTagDragStart,
      handleDocTagPointerDown,
      handleNavigatorSnapshot,
      handleTagDragEnterDoc,
      handleTagDragLeaveDoc,
      handleTagDragOverDoc,
      handleTagDropOnDoc,
      itemRefs,
      items,
      layoutRef,
      layoutSnapshot,
      docSizeVersion,
      onDocumentOpen,
      openOverlayForDoc,
      overlayDisplay,
      overlayOriginRect,
      overlayOriginTransform,
      pendingRemovalTag,
      pendingTagDocId,
      recalcVisibleDocIds,
      resolveBaseMetrics,
      setDraggingId,
      syncLayoutSnapshot,
      tagDropTargetId,
      visibleDocIds,
      documentLookup,
    ],
  );

  return (
    <DesktopProvider value={contextValue}>
      <DesktopWorkspaceView />
    </DesktopProvider>
  );
};

const DesktopWorkspaceView = () => {
  const {
    items,
    containerRef,
    handleCanvasDragOver,
    handleCanvasDragLeave,
    handleCanvasDrop,
    ensureDocumentSize,
    layoutSnapshot,
    layoutRef,
    itemRefs,
    visibleDocIds,
    draggingId,
    tagDropTargetId,
    pendingTagDocId,
    pendingRemovalTag,
    onDocumentOpen,
    ensureAssetUrl,
    getDocumentAsset,
    handleNavigatorSnapshot,
    activeTagSet,
    handleTagDragEnterDoc,
    handleTagDragOverDoc,
    handleTagDragLeaveDoc,
    handleTagDropOnDoc,
    handleDocTagPointerDown,
    handleDocTagDragStart,
    handleDocTagDrag,
    handleDocTagDragEnd,
    overlayDisplay,
    closeOverlay,
    overlayOriginRect,
    overlayOriginTransform,
  } = useDesktopContext();

  const { handlePointerDown, handlePointerMove, handlePointerUp, handlePointerCancel } =
    useDocumentDrag();

  const allSizesReady = items.every((doc) => ensureDocumentSize(doc));

  return (
    <>
      <div className="skeuo-shell">
        <div
          className="skeuo-canvas"
          ref={containerRef}
          onDragOver={handleCanvasDragOver}
          onDragLeave={handleCanvasDragLeave}
          onDrop={handleCanvasDrop}
        >
          {!allSizesReady ? (
            <div className="skeuo-empty">
              <p>Loading previews…</p>
            </div>
          ) : items.length === 0 ? (
            <div className="skeuo-empty">
              <p>No documents to show here yet. Drop files to make this space come alive.</p>
            </div>
          ) : (
            items.map((doc) => {
              const sizeInfo = ensureDocumentSize(doc);
              if (!sizeInfo) {
                return null;
              }
              const { width: cardWidth, height: cardHeight } = sizeInfo;
              const layout = layoutSnapshot.get(doc.id) ?? layoutRef.current.get(doc.id);
              if (
                !layout ||
                !Number.isFinite(layout.centerX) ||
                !Number.isFinite(layout.centerY)
              ) {
                return null;
              }
              const layoutCenterX = layout.centerX;
              const layoutCenterY = layout.centerY;
              const rotation = layout.rotation || 0;
              const originX = layoutCenterX - cardWidth / 2;
              const originY = layoutCenterY - cardHeight / 2;
              const transform = formatTransform(
                Math.round(originX),
                Math.round(originY),
                rotation,
                1,
              );
              const style = {
                transform,
                zIndex: layout.z ?? 1,
                width: Math.round(cardWidth),
                height: Math.round(cardHeight),
              };
              const docKey = doc?.id != null ? String(doc.id) : null;
              const shouldLoad = docKey ? visibleDocIds.has(docKey) : false;
              const title = doc.title || doc.original_name || 'Document';
              const dragging = draggingId === doc.id;
              const tags = Array.isArray(doc.tags) ? doc.tags : [];
              const docTagKeys = tags
                .map((tag) => resolveTagKey(tag))
                .filter(Boolean);
              const matchesFilter =
                activeTagSet.size === 0 || docTagKeys.some((key) => activeTagSet.has(key));
              const dropActive = tagDropTargetId === doc.id;
              const dropPending = pendingTagDocId === doc.id;
              const itemClasses = ['skeuo-item'];
              if (dragging) itemClasses.push('is-dragging');
              if (dropActive) itemClasses.push('is-tag-target');
              if (dropPending) itemClasses.push('is-tag-pending');
              if (!matchesFilter) itemClasses.push('is-filtered-out');
              const docTagTokens = docTagKeys.join(' ');
              return (
                <div
                  key={doc.id}
                  className={itemClasses.join(' ')}
                  style={style}
                  role="button"
                  data-doc-id={doc.id}
                  data-tag-ids={docTagTokens || undefined}
                  aria-hidden={matchesFilter ? undefined : 'true'}
                  ref={(node) => {
                    if (node) {
                      itemRefs.current.set(doc.id, node);
                    } else {
                      itemRefs.current.delete(doc.id);
                    }
                  }}
                  onPointerDown={(event) => handlePointerDown(event, doc.id)}
                  onPointerMove={handlePointerMove}
                  onPointerUp={handlePointerUp}
                  onPointerCancel={handlePointerCancel}
                  onDragEnter={(event) => handleTagDragEnterDoc(event, doc.id)}
                  onDragOver={(event) => handleTagDragOverDoc(event, doc.id)}
                  onDragLeave={(event) => handleTagDragLeaveDoc(event, doc.id)}
                  onDrop={(event) => handleTagDropOnDoc(event, doc)}
                  onKeyDown={(event) => {
                    if (event.key === 'Enter' || event.key === ' ') {
                      preventAll(event);
                      onDocumentOpen?.(doc.id);
                    }
                  }}
                >
                  <div className="skeuo-item__body">
                    <DesktopPreviewCard
                      doc={doc}
                      title={title}
                      ensureAssetUrl={ensureAssetUrl}
                      getDocumentAsset={getDocumentAsset}
                      onNavigatorSnapshot={handleNavigatorSnapshot}
                      shouldLoad={shouldLoad}
                    />
                    {tags.length > 0 && (
                      <div className="skeuo-item__tags" aria-hidden="true">
                        {tags.map((tag) => {
                          const key = tag.id || tag.label || String(tag);
                          if (
                            pendingRemovalTag &&
                            pendingRemovalTag.docId === doc.id &&
                            pendingRemovalTag.tagId === tag.id
                          ) {
                            return null;
                          }
                          const colorStyle = getTagColorStyle(tag.color);
                          const pendingRemoval =
                            pendingRemovalTag &&
                            pendingRemovalTag.docId === doc.id &&
                            pendingRemovalTag.tagId === tag.id;
                          const tagClasses = ['badge', 'tag-chip', 'tag-chip--draggable'];
                          if (pendingRemoval) tagClasses.push('tag-chip--tear-pending');
                          return (
                            <span
                              key={key}
                              className={tagClasses.join(' ')}
                              style={colorStyle || undefined}
                              title={tag.label || 'Tag'}
                              draggable
                              onPointerDown={(event) => handleDocTagPointerDown(event, doc, tag)}
                              onDragStart={(event) => handleDocTagDragStart(event, doc, tag)}
                              onDrag={handleDocTagDrag}
                              onDragEnd={(event) => handleDocTagDragEnd(event)}
                            >
                              <span className="tag-chip__label">{tag.label || 'Tag'}</span>
                            </span>
                          );
                        })}
                      </div>
                    )}
                  </div>
                </div>
              );
            })
          )}
        </div>
      </div>
      <PreviewZoomOverlay
        open={Boolean(overlayDisplay?.url)}
        display={overlayDisplay}
        onClose={closeOverlay}
        originRect={overlayOriginRect}
        originTransform={overlayOriginTransform}
      />
    </>
  );
};

export default DesktopWorkspace;

export const createDesktopSurface = ({ workspaceProps, renderSidebarToggle }) => {
  if (!workspaceProps) {
    return null;
  }

  const { currentFolderName, searchResults, onRefresh, onExit } = workspaceProps;
  const title = Array.isArray(searchResults) ? 'Search results' : currentFolderName;
  const subtitle = Array.isArray(searchResults)
    ? `${searchResults.length} matching document${searchResults.length === 1 ? '' : 's'}`
    : null;

  const sidebarToggle = renderSidebarToggle ? renderSidebarToggle() : null;
  const leading = sidebarToggle ? <>{sidebarToggle}</> : null;

  const actions = (
    <>
      <button
        type="button"
        className="icon-button"
        onClick={onRefresh}
        aria-label="Refresh"
        title="Refresh"
      >
        <RefreshIcon />
      </button>
      <button type="button" className="secondary" onClick={onExit}>
        Back to List
      </button>
    </>
  );

  return {
    key: 'workspace',
    variant: 'workspace',
    header: { title, subtitle, leading, actions },
    content: <DesktopWorkspace {...workspaceProps} />,
    supportsDetail: false,
  };
};
