import {
  useCallback,
  useEffect,
  useRef,
  type MutableRefObject,
  type RefObject,
} from 'react';
import type { PointerEvent as ReactPointerEvent } from 'react';
import { preventAll, safeInvoke } from './events';
import { clamp } from './math';
import usePointerTap from '../ui/usePointerTap';
import {
  MIN_TIMESTEP,
  MAX_TIMESTEP,
  applyDomTransform,
  type WorkspaceEngine,
} from './workspaceEngine';

type Identifier = string | number;

interface DocumentLike {
  id?: Identifier | null;
  title?: string;
  [key: string]: unknown;
}

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

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

interface DragTransform {
  centerX: number;
  centerY: number;
  rotation: number;
  width?: number;
  height?: number;
  scale?: number;
}

type EngineDragState = Parameters<WorkspaceEngine['finalizeGroupDrag']>[0];
type EngineGroupItem = NonNullable<EngineDragState['groupItems']>[number];

interface DragGroupItemInternal extends EngineGroupItem {
  baseOffsetX?: number;
  baseOffsetY?: number;
  offsetX?: number;
  offsetY?: number;
  targetRotation?: number;
  initialRotation?: number;
}

type EnsureDocumentSizeFn = (doc: DocumentLike | null | undefined) => DocumentSizeInfo | null;

type ResolveBaseMetricsFn = (
  doc: DocumentLike | null | undefined,
  width: number,
  height: number,
) => { baseWidth: number; baseHeight: number; baseScale: number };

interface DragSettings {
  canvasPadding?: number;
  defaultCanvasWidth?: number;
  defaultCanvasHeight?: number;
  debugDrag?: boolean;
}

interface PointerDownOptions {
  stackDocIds?: Array<Identifier | null | undefined>;
  stackSelectionApplied?: boolean;
  wasSelected?: boolean;
  modifierActive?: boolean;
  stackReplace?: boolean;
}

interface UseDocumentDragOptions {
  engine?: WorkspaceEngine | null;
  layoutRef: MutableRefObject<Map<string, LayoutEntry>>;
  dragTransformsRef: MutableRefObject<Map<string, DragTransform>>;
  itemRefs: MutableRefObject<Map<string, HTMLElement | null>>;
  documentLookup: Map<string, DocumentLike>;
  ensureDocumentSize: EnsureDocumentSizeFn;
  resolveBaseMetrics: ResolveBaseMetricsFn;
  bringToFront: (docId: Identifier | null | undefined) => void;
  setDraggingId: (docKey: string | null) => void;
  canvasSize: { width: number; height: number };
  openOverlayForDoc?: (
    docId: Identifier | null | undefined,
    originInfo?: { rotation: number; scale: number; width: number; height: number },
  ) => void;
  recalcVisibleDocIds: () => void;
  settings?: DragSettings;
  containerRef?: RefObject<HTMLElement>;
  onInspectDocument?: (docId: Identifier | null | undefined, event?: PointerEvent | ReactPointerEvent) => void;
  onDocumentStackSelect?: (
    docIds: Identifier[],
    event: PointerEvent | ReactPointerEvent,
    options?: { replace?: boolean },
  ) => void;
  selectedDocumentIds?: Array<Identifier | null | undefined>;
  markLayoutDirty?: () => void;
}

type EngineInertiaState = Parameters<WorkspaceEngine['startInertiaAnimation']>[1];

interface DragStateInternal extends EngineDragState {
  docId: string;
  docKey: string;
  pointerId: number;
  originCenterX: number;
  originCenterY: number;
  currentCenterX: number;
  currentCenterY: number;
  startX: number;
  startY: number;
  rotation: number;
  restRotation: number;
  dynamicRotation: number;
  angularVelocity: number;
  moved: boolean;
  locked: boolean;
  width: number;
  height: number;
  dragScale: number;
  baseScale: number;
  capturedTarget: HTMLElement | null;
  lastClientX: number;
  lastClientY: number;
  lastTimestamp: number;
  localPointerOffsetX: number;
  localPointerOffsetY: number;
  containerRectLeft: number;
  containerRectTop: number;
  isGroup: boolean;
  activeDocIds: string[];
  groupItems: DragGroupItemInternal[];
  groupElevated: boolean;
  stackDocIds: string[] | null;
  stackSelectionApplied: boolean;
  stackReplace: boolean;
}

type PointerEventLike = PointerEvent | ReactPointerEvent<HTMLElement>;

interface DragTapMetadata {
  docId: Identifier | null;
  originInfo?: { rotation: number; scale: number; width: number; height: number };
  docTitle: string;
}

const DRAG_HYSTERESIS_PX = 4;
const DRAG_HYSTERESIS_SQUARED = DRAG_HYSTERESIS_PX * DRAG_HYSTERESIS_PX;
const EDGE_COLLISION_THRESHOLD = 0.5;

const getEventTargetElement = (event?: PointerEventLike | null): Element | null => {
  if (!event) {
    return null;
  }
  const ElementCtor = typeof window !== 'undefined' ? window.Element : null;
  if (!ElementCtor) {
    return null;
  }
  const nativeEvent = 'nativeEvent' in event ? (event as ReactPointerEvent).nativeEvent : null;
  const candidate = (event.target as Element | null) || (nativeEvent ? (nativeEvent.target as Element | null) : null);
  return candidate instanceof ElementCtor ? candidate : null;
};

const useDocumentDrag = (options: UseDocumentDragOptions) => {
  const {
    engine,
    layoutRef,
    dragTransformsRef,
    itemRefs,
    documentLookup,
    ensureDocumentSize,
    resolveBaseMetrics,
    bringToFront,
    setDraggingId,
    canvasSize,
    openOverlayForDoc,
    recalcVisibleDocIds,
    settings,
    containerRef: providedContainerRef,
    onInspectDocument,
    onDocumentStackSelect,
    selectedDocumentIds = [],
    markLayoutDirty,
  } = options;

  const fallbackContainerRef = useRef<HTMLElement | null>(null);
  const containerRef = providedContainerRef ?? fallbackContainerRef;

  const {
    canvasPadding = 24,
    defaultCanvasWidth = 1024,
    defaultCanvasHeight = 680,
    debugDrag = false,
  } = settings || {};

  useEffect(
    () => () => {
      engine?.disposeInertiaAnimations?.();
    },
    [engine],
  );

  const tapHandler = usePointerTap<DragTapMetadata>({
    delay: 220,
    onSingle: () => {},
    onDouble: ({ data, event }) => {
      if (!data?.docId) {
        return;
      }
      if (event?.altKey) {
        openOverlayForDoc?.(data.docId, data.originInfo);
        return;
      }
      onInspectDocument?.(data.docId, event);
    },
  });
  const dragStateRef = useRef<DragStateInternal | null>(null);

  const setDragTransform = useCallback((docKey: Identifier | null | undefined, transform: DragTransform | null) => {
    if (!docKey) {
      return;
    }
    const map = dragTransformsRef?.current;
    if (!map) {
      return;
    }
    if (transform) {
      map.set(String(docKey), transform);
    } else {
      map.delete(String(docKey));
    }
  }, [dragTransformsRef]);

  const clearDragTransforms = useCallback(() => {
    const map = dragTransformsRef?.current;
    if (!map?.clear) {
      return;
    }
    map.clear();
  }, [dragTransformsRef]);

  const commitActiveDragTransforms = useCallback((docIds: Array<Identifier | null | undefined> | null = null) => {
    const map = dragTransformsRef?.current;
    if (!map || !map.size) {
      return;
    }
    const keys = Array.isArray(docIds) && docIds.length
      ? docIds
          .map((id) => (id != null ? String(id) : null))
          .filter((value): value is string => Boolean(value))
      : Array.from(map.keys());
    keys.forEach((key) => {
      const transform = map.get(key);
      if (!transform) {
        return;
      }
      const previous = layoutRef.current.get(key) || {};
      layoutRef.current.set(key, {
        ...previous,
        centerX: transform.centerX,
        centerY: transform.centerY,
        rotation: transform.rotation ?? previous.rotation ?? 0,
      });
    });
    markLayoutDirty?.();
  }, [dragTransformsRef, layoutRef, markLayoutDirty]);

  const finishDrag = useCallback(
    (pointerId: number, { clearTransforms = true }: { clearTransforms?: boolean } = {}) => {
      const state = dragStateRef.current;
      if (state && state.pointerId === pointerId) {
        const capturedTarget = state.capturedTarget;
        if (capturedTarget?.releasePointerCapture) {
          try {
            capturedTarget.releasePointerCapture(pointerId);
          } catch (error) {
            if (debugDrag) {
              void error;
            }
          }
        }
      }
      dragStateRef.current = null;
      setDraggingId(null);
      engine?.endDrag?.();
      if (clearTransforms) {
        clearDragTransforms();
      }
    },
    [clearDragTransforms, debugDrag, engine, setDraggingId],
  );

  const handlePointerDown = useCallback(
    (event: PointerEventLike, docIdInput?: Identifier | null, options: PointerDownOptions = {}) => {
      const targetElement = getEventTargetElement(event);
      if (targetElement?.closest && targetElement.closest('[data-desk-tag-chip="true"]')) {
        return;
      }
      preventAll(event);

      const docId = docIdInput != null ? docIdInput : null;
      const docKey = docId != null ? String(docId) : null;
      if (!docKey) {
        return;
      }

      engine?.cancelInertiaAnimation?.(docKey);

      const doc = documentLookup.get(docKey);
      if (!doc) {
        return;
      }

      const stackDocIdsOptionRaw = options?.stackDocIds;
      const stackDocIdsOption = Array.isArray(stackDocIdsOptionRaw)
        ? stackDocIdsOptionRaw
            .map((value) => (value != null ? String(value) : null))
            .filter((value): value is string => Boolean(value))
        : null;
      const stackSelectionAppliedInitial = Boolean(options?.stackSelectionApplied);
      const wasSelectedAtPointerDown = Boolean(options?.wasSelected);
      const pointerModifierActive =
        options?.modifierActive ?? Boolean(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey);
      const stackReplace = Boolean(options?.stackReplace);

      let selectionIds: string[] = Array.isArray(selectedDocumentIds)
        ? selectedDocumentIds
            .map((id) => (id != null ? String(id) : null))
            .filter((id): id is string => Boolean(id))
        : [];

      if (!stackDocIdsOption && !pointerModifierActive && !wasSelectedAtPointerDown) {
        selectionIds = [docKey];
      }

      if (stackDocIdsOption && stackDocIdsOption.length) {
        const selectionSet = new Set(selectionIds);
        stackDocIdsOption.forEach((value) => {
          if (value != null) {
            selectionSet.add(String(value));
          }
        });
        selectionIds = Array.from(selectionSet);
      }

      const metaOrCtrl = event.metaKey || event.ctrlKey;
      if (!stackDocIdsOption && metaOrCtrl && !selectionIds.includes(docKey)) {
        selectionIds = [...selectionIds, docKey];
      }

      selectionIds = selectionIds.filter((id, index, array) => array.indexOf(id) === index && documentLookup.has(id));

      if (!selectionIds.includes(docKey)) {
        selectionIds.unshift(docKey);
      }

      if (!selectionIds.length) {
        selectionIds = [docKey];
      }

      const isGroupDrag = selectionIds.length > 1;

      if (isGroupDrag) {
        selectionIds.forEach((id) => {
          if (id !== docKey) {
        engine?.cancelInertiaAnimation?.(id);
          }
        });
      }

      const sizeInfo = ensureDocumentSize(doc) || { width: 0, height: 0 };
      const docWidth = sizeInfo.width || 320;
      const docHeight = sizeInfo.height || 240;
      const { baseScale } = resolveBaseMetrics(doc, docWidth, docHeight);
      const normalizedBaseScale =
        Number.isFinite(baseScale) && baseScale > 0 ? baseScale : 1;

      const entry = layoutRef.current.get(docKey) || null;
      const defaultCenterX = canvasPadding + docWidth / 2;
      const defaultCenterY = canvasPadding + docHeight / 2;
      const centerX = Number.isFinite(entry?.centerX) ? entry.centerX : defaultCenterX;
      const centerY = Number.isFinite(entry?.centerY) ? entry.centerY : defaultCenterY;

      const modifierPressed = pointerModifierActive;
      if (!modifierPressed) {
        if (isGroupDrag) {
          const layout = layoutRef.current;
          const ordered = [...selectionIds]
            .filter((id, index, array) => array.indexOf(id) === index)
            .sort((a, b) => {
              const aZ = layout.get(a)?.z ?? 0;
              const bZ = layout.get(b)?.z ?? 0;
              return aZ - bZ;
            });

          ordered.forEach((id) => {
            bringToFront(id === docKey ? docId : id);
          });
        } else {
          bringToFront(docId);
        }
      }

      if (entry && (entry.centerX !== centerX || entry.centerY !== centerY)) {
        layoutRef.current.set(docKey, { ...entry, centerX, centerY });
      }

      const capturedTarget = event.currentTarget instanceof HTMLElement ? event.currentTarget : null;
      if (capturedTarget?.setPointerCapture) {
        try {
          capturedTarget.setPointerCapture(event.pointerId);
        } catch (error) {
          if (debugDrag) {
            void error;
          }
        }
      }

      const containerRect = containerRef.current?.getBoundingClientRect?.() || null;
      const containerLeft = containerRect?.left || 0;
      const containerTop = containerRect?.top || 0;
      const pointerCanvasX = event.clientX - containerLeft;
      const pointerCanvasY = event.clientY - containerTop;
      const pointerOffsetX = pointerCanvasX - centerX;
      const pointerOffsetY = pointerCanvasY - centerY;
      const initialRotationDeg = entry?.rotation ?? 0;
      const initialRotationRad = (initialRotationDeg * Math.PI) / 180;
      const cosInitial = Math.cos(-initialRotationRad);
      const sinInitial = Math.sin(-initialRotationRad);
      const localPointerOffsetX = pointerOffsetX * cosInitial - pointerOffsetY * sinInitial;
      const localPointerOffsetY = pointerOffsetX * sinInitial + pointerOffsetY * cosInitial;

      const groupItems: DragGroupItemInternal[] = selectionIds.map((id) => {
        const itemDoc = documentLookup.get(id);
        const itemSize = ensureDocumentSize(itemDoc) || sizeInfo;
        const itemWidth = itemSize.width || docWidth;
        const itemHeight = itemSize.height || docHeight;
        const itemEntry = layoutRef.current.get(id) || null;
        const itemCenterX =
          Number.isFinite(itemEntry?.centerX) ? itemEntry.centerX : canvasPadding + itemWidth / 2;
        const itemCenterY =
          Number.isFinite(itemEntry?.centerY) ? itemEntry.centerY : canvasPadding + itemHeight / 2;
        const baseOffsetX = itemCenterX - centerX;
        const baseOffsetY = itemCenterY - centerY;
        const initialRotation = itemEntry?.rotation ?? 0;
        return {
          docId: id,
          width: itemWidth,
          height: itemHeight,
          currentCenterX: itemCenterX,
          currentCenterY: itemCenterY,
          baseOffsetX,
          baseOffsetY,
          offsetX: baseOffsetX,
          offsetY: baseOffsetY,
          targetRotation: initialRotation,
          displayRotation: initialRotation,
          
        } satisfies DragGroupItemInternal;
      });

      const eventTimestamp =
        (Number.isFinite(event?.timeStamp))
          ? event.timeStamp
          : performance?.now
            ? performance.now()
            : Date.now();

      const hasStackSource = Array.isArray(stackDocIdsOption) && stackDocIdsOption.length > 1;

      dragStateRef.current = {
        docId: docKey,
        docKey,
        pointerId: event.pointerId,
        originCenterX: centerX,
        originCenterY: centerY,
        currentCenterX: centerX,
        currentCenterY: centerY,
        startX: event.clientX,
        startY: event.clientY,
        rotation: entry?.rotation ?? 0,
        restRotation: entry?.rotation ?? 0,
        dynamicRotation: 0,
        angularVelocity: 0,
        moved: false,
        locked: false,
        width: docWidth,
        height: docHeight,
        dragScale: 1,
        baseScale: normalizedBaseScale,
        capturedTarget,
        lastClientX: event.clientX,
        lastClientY: event.clientY,
        lastTimestamp: eventTimestamp,
        localPointerOffsetX,
        localPointerOffsetY,
        containerRectLeft: containerLeft,
        containerRectTop: containerTop,
        isGroup: isGroupDrag,
        activeDocIds: selectionIds,
        groupItems,
        groupElevated: !isGroupDrag,
        stackDocIds: hasStackSource ? stackDocIdsOption : null,
        stackSelectionApplied: stackSelectionAppliedInitial || !hasStackSource,
        stackReplace,
      } satisfies DragStateInternal;

      const state = dragStateRef.current;
      if (!state) {
        return;
      }

      clearDragTransforms();
      state.groupItems.forEach((item) => {
        if (!item?.docId) {
          return;
        }
        setDragTransform(item.docId, {
          centerX: item.currentCenterX,
          centerY: item.currentCenterY,
          rotation: item.displayRotation ?? item.initialRotation ?? 0,
          width: item.width,
          height: item.height,
          scale: item.docId === state.docKey ? state.dragScale || 1 : 1,
        });
      });

      engine?.beginDrag?.(state.activeDocIds);

      setDraggingId(docKey);

    if (isGroupDrag) {
      groupItems.forEach((item) => {
        if (item.docId === docKey) {
          return;
        }
        const node = itemRefs.current.get(item.docId);
        if (node) {
          item.displayRotation = item.initialRotation;
          const itemEntry = layoutRef.current.get(item.docId) || null;
          applyDomTransform(node, {
            centerX: item.currentCenterX,
            centerY: item.currentCenterY,
            width: item.width,
            height: item.height,
            rotation: item.displayRotation ?? 0,
            scale: 1,
            zIndex: itemEntry?.z,
          });
        }
      });
    }
  }, [
    bringToFront,
    canvasPadding,
    containerRef,
    documentLookup,
    engine,
    ensureDocumentSize,
    layoutRef,
    resolveBaseMetrics,
    selectedDocumentIds,
    setDraggingId,
    debugDrag,
    itemRefs,
    clearDragTransforms,
    setDragTransform,
  ]);

  const handlePointerMove = useCallback(
    (event: PointerEventLike) => {
      const state = dragStateRef.current;
      if (!state) {
        return;
      }
      if (state.pointerId !== event.pointerId) {
        return;
      }
    preventAll(event);

    if (state.isGroup) {
      const containerRect = containerRef.current?.getBoundingClientRect?.();
      if (containerRect) {
        state.containerRectLeft = containerRect.left;
        state.containerRectTop = containerRect.top;
      }

      const pointerCanvasX = event.clientX - state.containerRectLeft;
      const pointerCanvasY = event.clientY - state.containerRectTop;
      const deltaX = event.clientX - state.startX;
      const deltaY = event.clientY - state.startY;

      if (!state.moved) {
        const distanceSquared = deltaX * deltaX + deltaY * deltaY;
        if (distanceSquared < DRAG_HYSTERESIS_SQUARED) {
          return;
        }
        state.moved = true;
        if (
          !state.stackSelectionApplied
          && Array.isArray(state.stackDocIds)
          && state.stackDocIds.length > 0
        ) {
          safeInvoke(onDocumentStackSelect, state.stackDocIds as Identifier[], event, {
            replace: state.stackReplace,
          });
          state.stackSelectionApplied = true;
        }
        if (!state.groupElevated) {
          const layout = layoutRef.current;
          const sortedGroup = state.activeDocIds
            .filter((id) => id !== state.docKey)
            .sort((a, b) => {
              const aZ = layout.get(a)?.z ?? 0;
              const bZ = layout.get(b)?.z ?? 0;
              return aZ - bZ;
            });

          sortedGroup.forEach((id) => bringToFront(id));
          bringToFront(state.docKey);
          state.groupElevated = true;
        }
      }

      const docWidth = state.width;
      const docHeight = state.height;
      const halfWidth = docWidth / 2;
      const halfHeight = docHeight / 2;
      const canvasWidth = canvasSize.width || defaultCanvasWidth;
      const canvasHeight = canvasSize.height || defaultCanvasHeight;
      const minCenterX = canvasPadding + halfWidth;
      const maxCenterX = Math.max(minCenterX, canvasWidth - canvasPadding - halfWidth);
      const minCenterY = canvasPadding + halfHeight;
      const maxCenterY = Math.max(minCenterY, canvasHeight - canvasPadding - halfHeight);

      const desiredCenterX = pointerCanvasX - state.localPointerOffsetX;
      const desiredCenterY = pointerCanvasY - state.localPointerOffsetY;
      const centerX = clamp(desiredCenterX, minCenterX, maxCenterX);
      const centerY = clamp(desiredCenterY, minCenterY, maxCenterY);

      state.currentCenterX = centerX;
      state.currentCenterY = centerY;

      state.groupItems.forEach((item) => {
        const isPrimary = item.docId === state.docKey;

        if (isPrimary) {
          item.currentCenterX = centerX;
          item.currentCenterY = centerY;
          item.offsetX = item.baseOffsetX ?? 0;
          item.offsetY = item.baseOffsetY ?? 0;
          item.displayRotation = state.rotation ?? item.displayRotation ?? 0;
        } else {
          const decay = 0.82;
          const currentOffsetX = (item.offsetX ?? item.baseOffsetX ?? 0) * decay;
          const currentOffsetY = (item.offsetY ?? item.baseOffsetY ?? 0) * decay;
          item.offsetX = Math.abs(currentOffsetX) < 0.5 ? 0 : currentOffsetX;
          item.offsetY = Math.abs(currentOffsetY) < 0.5 ? 0 : currentOffsetY;

          const targetX = centerX + item.offsetX;
          const targetY = centerY + item.offsetY;
          const smoothing = 0.18;
          item.currentCenterX += (targetX - item.currentCenterX) * smoothing;
          item.currentCenterY += (targetY - item.currentCenterY) * smoothing;

          const halfW = item.width / 2;
          const halfH = item.height / 2;
          const minX = canvasPadding + halfW;
          const maxX = Math.max(minX, canvasWidth - canvasPadding - halfW);
          const minY = canvasPadding + halfH;
          const maxY = Math.max(minY, canvasHeight - canvasPadding - halfH);
          item.currentCenterX = clamp(item.currentCenterX, minX, maxX);
          item.currentCenterY = clamp(item.currentCenterY, minY, maxY);

          const rotationBlend = 0.16;
          item.displayRotation += (item.targetRotation - item.displayRotation) * rotationBlend;
        }

        const entry = layoutRef.current.get(item.docId) || null;
        const payload = {
          centerX: item.currentCenterX,
          centerY: item.currentCenterY,
          rotation: item.displayRotation ?? 0,
          width: item.width,
          height: item.height,
          scale: isPrimary ? state.dragScale || 1 : 1,
          zIndex: entry?.z,
        };

        setDragTransform(item.docId, payload);
        const node = itemRefs.current.get(item.docId);
        applyDomTransform(node, payload);
      });

      state.lastClientX = event.clientX;
      state.lastClientY = event.clientY;
      state.lastTimestamp =
        (Number.isFinite(event?.timeStamp))
          ? event.timeStamp
          : performance?.now
            ? performance.now()
            : Date.now();

      return;
    }
      if (state.locked) {
        return;
      }

      const deltaX = event.clientX - state.startX;
      const deltaY = event.clientY - state.startY;

      const docWidth = state.width;
      const docHeight = state.height;
      const halfWidth = docWidth / 2;
      const halfHeight = docHeight / 2;

      const containerRect = containerRef.current?.getBoundingClientRect?.();
      if (containerRect) {
        state.containerRectLeft = containerRect.left;
        state.containerRectTop = containerRect.top;
      }

      const containerLeft = state.containerRectLeft;
      const containerTop = state.containerRectTop;
      const pointerCanvasX = event.clientX - containerLeft;
      const pointerCanvasY = event.clientY - containerTop;

      const entry = layoutRef.current.get(state.docKey) || {};
      const rotationDeg = state.rotation ?? entry.rotation ?? 0;
      const rotationRad = (rotationDeg * Math.PI) / 180;
      const cosRot = Math.cos(rotationRad);
      const sinRot = Math.sin(rotationRad);
      const rotatedOffsetX =
        state.localPointerOffsetX * cosRot - state.localPointerOffsetY * sinRot;
      const rotatedOffsetY =
        state.localPointerOffsetX * sinRot + state.localPointerOffsetY * cosRot;

      const previousCenterX = Number.isFinite(state.currentCenterX)
        ? state.currentCenterX
        : state.originCenterX;
      const previousCenterY = Number.isFinite(state.currentCenterY)
        ? state.currentCenterY
        : state.originCenterY;

      const absCos = Math.abs(cosRot);
      const absSin = Math.abs(sinRot);
      const rotatedHalfWidth = absCos * halfWidth + absSin * halfHeight;
      const rotatedHalfHeight = absSin * halfWidth + absCos * halfHeight;
      const canvasWidth = canvasSize.width || defaultCanvasWidth;
      const canvasHeight = canvasSize.height || defaultCanvasHeight;
      const minCenterX = canvasPadding + rotatedHalfWidth;
      const maxCenterX = Math.max(minCenterX, canvasWidth - canvasPadding - rotatedHalfWidth);
      const minCenterY = canvasPadding + rotatedHalfHeight;
      const maxCenterY = Math.max(minCenterY, canvasHeight - canvasPadding - rotatedHalfHeight);

      const pointerRelativePrevX = pointerCanvasX - previousCenterX;
      const pointerRelativePrevY = pointerCanvasY - previousCenterY;
      const cosInversePrev = Math.cos(-rotationRad);
      const sinInversePrev = Math.sin(-rotationRad);
      const pointerLocalPrevX = pointerRelativePrevX * cosInversePrev - pointerRelativePrevY * sinInversePrev;
      const pointerLocalPrevY = pointerRelativePrevX * sinInversePrev + pointerRelativePrevY * cosInversePrev;
      const pointerInsideRelativeToPrev =
        Math.abs(pointerLocalPrevX) <= halfWidth && Math.abs(pointerLocalPrevY) <= halfHeight;

      const desiredCenterX = pointerCanvasX - rotatedOffsetX;
      const desiredCenterY = pointerCanvasY - rotatedOffsetY;
      const clampedCenterX = clamp(desiredCenterX, minCenterX, maxCenterX);
      const clampedCenterY = clamp(desiredCenterY, minCenterY, maxCenterY);

      if (!state.moved) {
        const distanceSquared = deltaX * deltaX + deltaY * deltaY;
        if (distanceSquared < DRAG_HYSTERESIS_SQUARED) {
          return;
        }
        bringToFront(state.docKey);
        state.moved = true;
      }

      const collidedWithHorizontalEdge =
        Math.abs(desiredCenterY - clampedCenterY) > EDGE_COLLISION_THRESHOLD;
      const collidedWithVerticalEdge =
        Math.abs(desiredCenterX - clampedCenterX) > EDGE_COLLISION_THRESHOLD;
      const collidedWithEdge = collidedWithHorizontalEdge || collidedWithVerticalEdge;

      let currentCenterX = clampedCenterX;
      let currentCenterY = clampedCenterY;
      if (collidedWithEdge && !pointerInsideRelativeToPrev) {
        currentCenterX = previousCenterX;
        currentCenterY = previousCenterY;
      }

      state.currentCenterX = currentCenterX;
      state.currentCenterY = currentCenterY;

      const layoutEntry = layoutRef.current.get(state.docKey) || null;
      const transformPayload = {
        centerX: currentCenterX,
        centerY: currentCenterY,
        rotation: rotationDeg,
        width: state.width,
        height: state.height,
        scale: state.dragScale || 1,
        zIndex: layoutEntry?.z,
      };

      setDragTransform(state.docKey, transformPayload);
      const primaryNode = itemRefs.current.get(state.docKey);
      applyDomTransform(primaryNode, transformPayload);

      const offsetX = pointerCanvasX - currentCenterX;
      const offsetY = pointerCanvasY - currentCenterY;
      const cosInverseCurrent = Math.cos(-rotationRad);
      const sinInverseCurrent = Math.sin(-rotationRad);
      const pointerLocalX = offsetX * cosInverseCurrent - offsetY * sinInverseCurrent;
      const pointerLocalY = offsetX * sinInverseCurrent + offsetY * cosInverseCurrent;
      const pointerInsideCard =
        Math.abs(pointerLocalX) <= halfWidth && Math.abs(pointerLocalY) <= halfHeight;

      const currentTimestamp =
        (Number.isFinite(event?.timeStamp))
          ? event.timeStamp
          : performance?.now
            ? performance.now()
            : Date.now();
      const previousTimestamp = state.lastTimestamp ?? currentTimestamp;
      let dt = (currentTimestamp - previousTimestamp) / 1000;
      if (!Number.isFinite(dt) || dt <= 0) {
        dt = MIN_TIMESTEP;
      }
      dt = clamp(dt, MIN_TIMESTEP, MAX_TIMESTEP);

      state.lastClientX = event.clientX;
      state.lastClientY = event.clientY;
      state.lastTimestamp = currentTimestamp;

      const rotationForOffsetDeg = state.rotation || 0;
      const rotationForOffsetRad = (rotationForOffsetDeg * Math.PI) / 180;
      const cosInverse = Math.cos(-rotationForOffsetRad);
      const sinInverse = Math.sin(-rotationForOffsetRad);
      const pointerRelativeX = pointerCanvasX - currentCenterX;
      const pointerRelativeY = pointerCanvasY - currentCenterY;
      const updatedLocalOffsetX = pointerRelativeX * cosInverse - pointerRelativeY * sinInverse;
      const updatedLocalOffsetY = pointerRelativeX * sinInverse + pointerRelativeY * cosInverse;
      if (!collidedWithEdge || pointerInsideCard) {
        state.localPointerOffsetX = updatedLocalOffsetX;
        state.localPointerOffsetY = updatedLocalOffsetY;
      }

      void debugDrag;
    },
    [
      bringToFront,
      canvasPadding,
      canvasSize.height,
      canvasSize.width,
      defaultCanvasHeight,
      defaultCanvasWidth,
      containerRef,
      layoutRef,
      itemRefs,
      debugDrag,
      onDocumentStackSelect,
      setDragTransform,
    ],
  );

  const handlePointerUp = useCallback(
    (event: PointerEventLike) => {
      const state = dragStateRef.current;
      if (!state || state.pointerId !== event.pointerId) {
        finishDrag(event.pointerId);
        return;
      }

      if (state.isGroup) {
        engine?.finalizeGroupDrag?.(state);
        commitActiveDragTransforms(state.activeDocIds);
        finishDrag(event.pointerId);
        recalcVisibleDocIds();
        return;
      }

      if (state.moved) {
        commitActiveDragTransforms([state.docKey]);
        const inertiaState: EngineInertiaState = {
          docId: state.docKey,
          restRotation: state.restRotation,
          dynamicRotation: state.dynamicRotation,
          angularVelocity: state.angularVelocity,
          rotation: state.rotation,
          width: state.width,
          height: state.height,
          dragScale: state.dragScale || 1,
          lastTimestamp: state.lastTimestamp,
        };
        const docId = state.docKey;
        finishDrag(event.pointerId);
        engine?.startInertiaAnimation?.(docId, inertiaState);
        return;
      }

      const docId = state.docKey;
      const metaPressed = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
      if (!metaPressed) {
        bringToFront(docId);
      }
      const originInfo = {
        rotation: state.rotation || 0,
        scale: state.baseScale || 1,
        width: state.width,
        height: state.height,
      };
      const docKey = docId != null ? String(docId) : null;
      const doc = docKey ? documentLookup.get(docKey) : null;
      tapHandler(event, {
        docId,
        originInfo,
        docTitle: doc?.title || 'document',
      });
      finishDrag(event.pointerId);
    },
    [
      bringToFront,
      commitActiveDragTransforms,
      documentLookup,
      engine,
      finishDrag,
      recalcVisibleDocIds,
      tapHandler,
    ],
  );

  const handlePointerCancel = useCallback(
    (event: PointerEventLike) => {
      const state = dragStateRef.current;
      if (state && state.pointerId === event.pointerId && state.moved) {
        if (state.isGroup) {
          engine?.finalizeGroupDrag?.(state);
          commitActiveDragTransforms(state.activeDocIds);
          finishDrag(event.pointerId);
          recalcVisibleDocIds();
          return;
        }

        commitActiveDragTransforms([state.docKey]);
        const inertiaState: EngineInertiaState = {
          docId: state.docKey,
          restRotation: state.restRotation,
          dynamicRotation: state.dynamicRotation,
          angularVelocity: state.angularVelocity,
          rotation: state.rotation,
          width: state.width,
          height: state.height,
          dragScale: state.dragScale || 1,
          lastTimestamp: state.lastTimestamp,
        };
        const docId = state.docKey;
        finishDrag(event.pointerId);
        engine?.startInertiaAnimation?.(docId, inertiaState);
        return;
      }
      finishDrag(event.pointerId);
    },
    [commitActiveDragTransforms, engine, finishDrag, recalcVisibleDocIds],
  );

  return {
    handlePointerDown,
    handlePointerMove,
    handlePointerUp,
    handlePointerCancel,
  };
};

export default useDocumentDrag;
