import {
  useCallback,
  useEffect,
  useRef,
} from 'react';
import {
  CLICK_ACTIONS,
  LONG_PRESS_DURATION_MS,
  POINTER_DRAG_THRESHOLD_SQUARED,
  STACK_HIT_EPSILON,
  applyClickPlanImmediately,
  applyLongPressSelection,
  createPointerIntent,
  finalizeClickSelection,
  withinThreshold,
} from './pointerUtils';
import { getPointerPosition, safeInvoke } from '../events';

const buildEntryDescriptor = (docId) => ({
  type: 'document',
  id: docId,
  key: `document:${docId}`,
});

export const useDeskPointer = ({
  containerRef,
  items,
  layoutRef,
  ensureDocumentSize,
  activeTagSet,
  handlePointerDown,
  handlePointerMove,
  handlePointerUp,
  handlePointerCancel,
  onEntryPointer,
  onDocumentStackSelect,
  onPromoteSelection,
  onInspectDocument,
  selectedDocumentIds,
  detailPanelOpen,
  onCloseDetailPanel,
  openOverlayForDoc = null,
}) => {
  const pointerIntentRef = useRef(null);
  const pointerStartRef = useRef({ x: 0, y: 0 });
  const pointerMovedRef = useRef(false);
  const longPressTimerRef = useRef(null);
  const longPressActiveRef = useRef(false);
  const resetLongPressState = useCallback(() => {
    if (longPressTimerRef.current) {
      clearTimeout(longPressTimerRef.current);
      longPressTimerRef.current = null;
    }
    longPressActiveRef.current = false;
  }, []);

  const resolveStackDocIds = useCallback(
    (event, targetDocId = null) => {
      const container = containerRef.current;
      if (!container || !event) {
        return [];
      }

      const rect = container.getBoundingClientRect();
      const pointerCanvasX = event.clientX - rect.left;
      const pointerCanvasY = event.clientY - rect.top;

      if (!Number.isFinite(pointerCanvasX) || !Number.isFinite(pointerCanvasY)) {
        return [];
      }

      const candidates = [];

      items.forEach((doc) => {
        if (!doc?.id) {
          return;
        }
        const docKey = String(doc.id);
        const layout = layoutRef.current.get(docKey);
        if (!layout) {
          return;
        }

        const sizeInfo = ensureDocumentSize(doc);
        if (!sizeInfo) {
          return;
        }
        const { width, height } = sizeInfo;
        if (!width || !height) {
          return;
        }

        if (activeTagSet.size) {
          const docTagKeys = Array.isArray(doc.tags)
            ? doc.tags.map((tag) => (tag ? tag.id : null)).filter(Boolean)
            : [];
          if (!docTagKeys.some((key) => activeTagSet.has(key))) {
            return;
          }
        }

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

        const rotationDeg = Number(layout.rotation) || 0;
        const rotationRad = (rotationDeg * Math.PI) / 180;
        const dx = pointerCanvasX - centerX;
        const dy = pointerCanvasY - centerY;
        const cosRotation = Math.cos(-rotationRad);
        const sinRotation = Math.sin(-rotationRad);
        const localX = dx * cosRotation - dy * sinRotation;
        const localY = dx * sinRotation + dy * cosRotation;
        const halfWidth = width / 2;
        const halfHeight = height / 2;

        const containsPointer =
          Math.abs(localX) <= halfWidth + STACK_HIT_EPSILON
          && Math.abs(localY) <= halfHeight + STACK_HIT_EPSILON;

        candidates.push({
          id: docKey,
          z: Number.isFinite(layout.z) ? layout.z : 0,
          centerX,
          centerY,
          width,
          height,
          halfWidth,
          halfHeight,
          containsPointer,
        });
      });

      const pointerCandidates = candidates.filter((candidate) => candidate.containsPointer);
      if (!pointerCandidates.length) {
        return [];
      }

      const sortedByZ = [...pointerCandidates].sort((a, b) => (b.z ?? 0) - (a.z ?? 0));
      const targetKey = targetDocId != null ? String(targetDocId) : sortedByZ[0].id;

      const primary = sortedByZ.find((candidate) => candidate.id === targetKey) || sortedByZ[0];
      if (!primary) {
        return [];
      }

      const radius = Math.max(Math.min(primary.halfWidth, primary.halfHeight) * 1.2, 6);
      const radiusSquared = radius * radius;

      const selected = candidates
        .filter((candidate) => {
          if (!candidate?.id) {
            return false;
          }
          const dx = candidate.centerX - primary.centerX;
          const dy = candidate.centerY - primary.centerY;
          return dx * dx + dy * dy <= radiusSquared + 1e-4;
        })
        .sort((a, b) => (b.z ?? 0) - (a.z ?? 0));

      if (targetKey) {
        const targetIndex = selected.findIndex((entry) => entry.id === targetKey);
        if (targetIndex > 0) {
          const [targetEntry] = selected.splice(targetIndex, 1);
          selected.unshift(targetEntry);
        }
      }

      return selected
        .map((candidate) => candidate.id)
        .filter((id, index, array) => array.indexOf(id) === index);
    },
    [activeTagSet, containerRef, ensureDocumentSize, items, layoutRef],
  );

  const scheduleLongPress = useCallback(
    ({ doc, modifierActive, pointerType }) => {
      if (modifierActive || pointerType !== 'touch') {
        longPressActiveRef.current = false;
        return;
      }

      longPressActiveRef.current = true;

      longPressTimerRef.current = window.setTimeout(() => {
        if (!longPressActiveRef.current || pointerMovedRef.current) {
          resetLongPressState();
          return;
        }

        const intent = pointerIntentRef.current;
        if (!intent || intent.docId !== doc.id) {
          resetLongPressState();
          return;
        }

        const syntheticEvent = {
          clientX: pointerStartRef.current.x,
          clientY: pointerStartRef.current.y,
        };
        const stackHits = resolveStackDocIds(syntheticEvent, doc.id);
        applyLongPressSelection({
          intent,
          stackDocIds: stackHits,
          syntheticEvent,
          onDocumentStackSelect,
        });
        pointerIntentRef.current = intent;
        resetLongPressState();
      }, LONG_PRESS_DURATION_MS);
    },
    [onDocumentStackSelect, resolveStackDocIds, resetLongPressState],
  );

  useEffect(() => () => resetLongPressState(), [resetLongPressState]);

  const handleCardPointerDown = useCallback(
    (event, doc) => {
      if (!doc?.id) {
        return;
      }

      pointerStartRef.current = getPointerPosition(event, { fallbackToPage: false });
      pointerMovedRef.current = false;
      resetLongPressState();

      const pointerButton = Number.isFinite(event?.button) ? event.button : 0;
      const pointerType = String(event?.pointerType ?? '');
      const metaKey = (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey;
      const modifierActive = Boolean(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey);

      const entryDescriptor = buildEntryDescriptor(doc.id);
      const stackHits = metaKey ? resolveStackDocIds(event, doc.id) : null;

      const intent = createPointerIntent({
        doc,
        entryDescriptor,
        selectedDocumentIds,
        metaKey,
        pointerButton,
        pointerType,
        stackHits,
      });

      if (intent.selectedAtDown) {
        safeInvoke(onPromoteSelection, doc.id, event);
      }

      applyClickPlanImmediately({
        intent,
        event,
        onEntryPointer,
        onDocumentStackSelect,
      });

      pointerIntentRef.current = intent;

      handlePointerDown(event, doc.id, {
        stackDocIds: intent.stackDocIdsForDrag,
        stackSelectionApplied: intent.stackSelectionApplied,
        wasSelected: intent.selectedAtDown,
        modifierActive,
        stackReplace: intent.stackReplaceOnDrag,
      });

      scheduleLongPress({
        doc,
        modifierActive,
        pointerType,
      });
    },
    [
      handlePointerDown,
      onPromoteSelection,
      onEntryPointer,
      onDocumentStackSelect,
      resolveStackDocIds,
      resetLongPressState,
      scheduleLongPress,
      selectedDocumentIds,
    ],
  );

  const handleCardPointerMove = useCallback(
    (event) => {
      const start = pointerStartRef.current;
      const { x, y } = getPointerPosition(event, { fallbackToPage: false });
      const dx = x - start.x;
      const dy = y - start.y;
      if (!withinThreshold(dx, dy, POINTER_DRAG_THRESHOLD_SQUARED)) {
        pointerMovedRef.current = true;
        resetLongPressState();
      }
      handlePointerMove(event);
    },
    [handlePointerMove, resetLongPressState],
  );

  const handleCardPointerUp = useCallback(
    (event, doc) => {
      const pointerState = pointerIntentRef.current;
      const pointerMoved = pointerMovedRef.current;

      resetLongPressState();
      handlePointerUp(event);

      if (!pointerMoved && pointerState) {
        finalizeClickSelection({
          intent: pointerState,
          event,
          onEntryPointer,
          onDocumentStackSelect,
        });

        if (
          pointerState.clickAction === CLICK_ACTIONS.openDetail
          && !pointerState.longPressTriggered
          && pointerState.docId === doc.id
        ) {
          const expectedButton = Number.isFinite(pointerState?.pointerButton)
            ? pointerState.pointerButton
            : 0;
          const releasedButton = Number.isFinite(event?.button) ? event.button : expectedButton;
          const isPrimaryRelease = expectedButton === 0 && releasedButton === 0;
          const stillSelected = Array.isArray(selectedDocumentIds)
            && selectedDocumentIds.includes(doc.id);
          if (isPrimaryRelease && stillSelected) {
            safeInvoke(onInspectDocument, doc.id);
          }
        }
      }

      pointerIntentRef.current = null;
      pointerMovedRef.current = false;
    },
    [
      handlePointerUp,
      onInspectDocument,
      onDocumentStackSelect,
      onEntryPointer,
      resetLongPressState,
      selectedDocumentIds,
    ],
  );

  const handleCardPointerCancel = useCallback(
    (event) => {
      pointerMovedRef.current = false;
      resetLongPressState();
      pointerIntentRef.current = null;
      handlePointerCancel(event);
    },
    [handlePointerCancel, resetLongPressState],
  );

  const getCardPointerHandlers = useCallback(
    (doc) => ({
      onPointerDown: (event) => handleCardPointerDown(event, doc),
      onPointerMove: handleCardPointerMove,
      onPointerUp: (event) => handleCardPointerUp(event, doc),
      onPointerCancel: handleCardPointerCancel,
    }),
    [
      handleCardPointerCancel,
      handleCardPointerDown,
      handleCardPointerMove,
      handleCardPointerUp,
    ],
  );

  const handleShellKeyDown = useCallback(
    (event) => {
      if (!event || event.defaultPrevented) {
        return;
      }

      const { key } = event;
      if (key !== ' ' && key !== 'Space' && key !== 'Spacebar') {
        return;
      }

      const target = event.target;
      if (target instanceof HTMLElement) {
        const tagName = target.tagName ? target.tagName.toLowerCase() : '';
        if (
          target.isContentEditable
          || tagName === 'input'
          || tagName === 'textarea'
          || tagName === 'select'
          || tagName === 'button'
        ) {
          return;
        }
      }

      if (openOverlayForDoc && Array.isArray(selectedDocumentIds) && selectedDocumentIds.length > 0) {
        event.preventDefault();
        const targetId = selectedDocumentIds[selectedDocumentIds.length - 1];
        if (targetId) {
          openOverlayForDoc(targetId);
        }
        return;
      }

      if (detailPanelOpen) {
        event.preventDefault();
        safeInvoke(onCloseDetailPanel);
      }
    },
    [detailPanelOpen, onCloseDetailPanel, openOverlayForDoc, selectedDocumentIds],
  );

  return {
    getCardPointerHandlers,
    handleShellKeyDown,
    focusShell: () => {
      const shell = containerRef.current;
      shell?.focus?.({ preventScroll: true });
    },
  };
};

export default useDeskPointer;
