import { safeInvoke } from '../events';

export const CLICK_ACTIONS = {
  selectSingle: 'selectSingle',
  openDetail: 'openDetail',
  addCard: 'addCard',
  addStack: 'addStack',
  none: 'none',
} as const;

export const DRAG_ACTIONS = {
  dragSelectSingle: 'dragSelectSingle',
  dragSelection: 'dragSelection',
  dragSelectStack: 'dragSelectStack',
  none: 'none',
} as const;

export type ClickAction = (typeof CLICK_ACTIONS)[keyof typeof CLICK_ACTIONS];
export type DragAction = (typeof DRAG_ACTIONS)[keyof typeof DRAG_ACTIONS];

export const STACK_HIT_EPSILON = 4;
export const POINTER_DRAG_THRESHOLD_SQUARED = 16;
export const LONG_PRESS_DURATION_MS = 450;

export const withinThreshold = (dx: number, dy: number, thresholdSquared: number): boolean => (dx * dx + dy * dy) <= thresholdSquared;

interface PointerIntentArgs {
  doc: { id: string | number };
  entryDescriptor: unknown;
  selectedDocumentIds: Array<string | number>;
  metaKey: boolean;
  pointerButton?: number;
  pointerType?: string;
  stackHits?: string[] | null;
}

export interface PointerIntent {
  docId: string | number;
  entryDescriptor: unknown;
  pointerType?: string;
  pointerButton?: number;
  selectedAtDown: boolean;
  selectionCountAtDown: number;
  metaKey: boolean;
  clickAction: ClickAction;
  dragAction: DragAction;
  stackDocIdsForDrag: string[] | null;
  stackDocIdsForClick: string[] | null;
  stackReplaceOnClick: boolean;
  stackReplaceOnDrag: boolean;
  clickSelectionApplied: boolean;
  stackSelectionApplied: boolean;
  longPressTriggered: boolean;
}

export const createPointerIntent = ({
  doc,
  entryDescriptor,
  selectedDocumentIds,
  metaKey,
  pointerButton,
  pointerType,
  stackHits,
}: PointerIntentArgs): PointerIntent => {
  const alreadySelected = selectedDocumentIds.includes(doc.id);
  const selectionCount = Array.isArray(selectedDocumentIds) ? selectedDocumentIds.length : 0;

  let clickAction: ClickAction = CLICK_ACTIONS.none;
  let dragAction: DragAction = DRAG_ACTIONS.none;

  if (metaKey) {
    clickAction = CLICK_ACTIONS.addStack;
    dragAction = DRAG_ACTIONS.dragSelectStack;
  } else if (alreadySelected) {
    clickAction = CLICK_ACTIONS.selectSingle;
    dragAction = selectionCount > 1 ? DRAG_ACTIONS.dragSelection : DRAG_ACTIONS.dragSelectSingle;
  } else {
    clickAction = CLICK_ACTIONS.selectSingle;
    dragAction = DRAG_ACTIONS.dragSelectSingle;
  }

  const stackList: string[] = Array.isArray(stackHits) && stackHits.length > 0
    ? stackHits.map((value) => String(value))
    : [String(doc.id)];

  const stackDocIdsForDrag = dragAction === DRAG_ACTIONS.dragSelectStack ? stackList : null;
  const stackDocIdsForClick = clickAction === CLICK_ACTIONS.addStack ? stackList : null;

  return {
    docId: doc.id,
    entryDescriptor,
    pointerType,
    pointerButton,
    selectedAtDown: alreadySelected,
    selectionCountAtDown: selectionCount,
    metaKey,
    clickAction,
    dragAction,
    stackDocIdsForDrag,
    stackDocIdsForClick,
    stackReplaceOnClick: clickAction === CLICK_ACTIONS.addStack,
    stackReplaceOnDrag: dragAction === DRAG_ACTIONS.dragSelectStack,
    clickSelectionApplied: false,
    stackSelectionApplied: false,
    longPressTriggered: false,
  };
};

export const applyClickPlanImmediately = ({ intent, event, onEntryPointer, onDocumentStackSelect }: {
  intent: PointerIntent;
  event?: unknown;
  onEntryPointer?: (descriptor: unknown, event?: unknown) => void;
  onDocumentStackSelect?: (docIds: string[], event?: unknown, options?: { replace?: boolean }) => void;
}) => {
  switch (intent.clickAction) {
    case CLICK_ACTIONS.selectSingle:
    case CLICK_ACTIONS.addCard:
      safeInvoke(onEntryPointer, intent.entryDescriptor, event);
      intent.clickSelectionApplied = true;
      break;
    case CLICK_ACTIONS.addStack:
      if (Array.isArray(intent.stackDocIdsForClick) && intent.stackDocIdsForClick.length > 0) {
        safeInvoke(
          onDocumentStackSelect,
          intent.stackDocIdsForClick,
          event,
          { replace: intent.stackReplaceOnClick },
        );
        intent.clickSelectionApplied = true;
        intent.stackSelectionApplied = true;
      }
      break;
    case CLICK_ACTIONS.openDetail:
    default:
      intent.clickSelectionApplied = true;
      break;
  }
};

export const finalizeClickSelection = ({ intent, event, onEntryPointer, onDocumentStackSelect }: {
  intent: PointerIntent;
  event?: unknown;
  onEntryPointer?: (descriptor: unknown, event?: unknown) => void;
  onDocumentStackSelect?: (docIds: string[], event?: unknown, options?: { replace?: boolean }) => void;
}) => {
  if (!intent || intent.clickSelectionApplied) {
    return;
  }

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

export const applyLongPressSelection = ({ intent, stackDocIds, syntheticEvent, onDocumentStackSelect }: {
  intent: PointerIntent;
  stackDocIds?: string[] | null;
  syntheticEvent?: unknown;
  onDocumentStackSelect?: (docIds: string[], event?: unknown, options?: { replace?: boolean }) => void;
}) => {
  if (!intent) {
    return;
  }

  const stackCopy: string[] = Array.isArray(stackDocIds) && stackDocIds.length > 0
    ? stackDocIds.map((value) => String(value))
    : [String(intent.docId)];

  safeInvoke(onDocumentStackSelect, stackCopy, syntheticEvent, { replace: true });

  intent.clickAction = CLICK_ACTIONS.addStack;
  intent.dragAction = DRAG_ACTIONS.dragSelectStack;
  intent.stackDocIdsForClick = stackCopy;
  intent.stackDocIdsForDrag = stackCopy;
  intent.stackReplaceOnClick = true;
  intent.stackReplaceOnDrag = true;
  intent.clickSelectionApplied = true;
  intent.stackSelectionApplied = true;
  intent.longPressTriggered = true;
};
