// Pure decision helpers for the mobile swipe-between-tabs gesture. The DOM
// wiring lives in components/mobile/SwipeableTabs.tsx; this module is the
// testable core (axis lock, native-scroll conflict, rubber-band, commit).

export type Axis = 'h' | 'v';

/**
 * Lock the gesture axis once the finger has moved past `threshold` on either
 * axis. Returns null while the movement is still too small to classify.
 */
export function lockAxis(dx: number, dy: number, threshold = 8): Axis | null {
  const ax = Math.abs(dx);
  const ay = Math.abs(dy);
  if (ax < threshold && ay < threshold) return null;
  return ax >= ay ? 'h' : 'v';
}

/**
 * Whether a horizontally-scrollable element can still scroll in the direction
 * the FINGER is moving (`dirX`): a finger moving right (dirX > 0) reveals the
 * left side, i.e. needs scrollLeft > 0; moving left needs room on the right.
 * When it can, the native scroll should win over the tab swipe.
 */
export function canScrollFurther(
  el: { scrollLeft: number; scrollWidth: number; clientWidth: number },
  dirX: number,
): boolean {
  if (dirX === 0) return false;
  const maxScroll = el.scrollWidth - el.clientWidth;
  if (maxScroll <= 1) return false; // not actually scrollable
  return dirX > 0 ? el.scrollLeft > 1 : el.scrollLeft < maxScroll - 1;
}

/**
 * Rubber-band the drag offset. When there IS a neighbor in the drag direction
 * the offset is 1:1; when there is none (first/last tab) it resists so the edge
 * feels bounded instead of dragging into empty space.
 */
export function resist(dx: number, hasNeighbor: boolean, width: number): number {
  if (hasNeighbor) return dx;
  const w = Math.max(1, width);
  const sign = Math.sign(dx);
  // Asymptotic resistance capped at ~12% of width.
  return sign * w * 0.12 * (1 - Math.exp(-Math.abs(dx) / w));
}

/**
 * Commit to the neighbor tab when the drag passed the threshold (the larger of
 * 60px or 22% of the container width) AND a neighbor exists in that direction.
 */
export function shouldCommit(dragX: number, width: number, hasNeighbor: boolean): boolean {
  if (!hasNeighbor) return false;
  const threshold = Math.max(60, width * 0.22);
  return Math.abs(dragX) >= threshold;
}

/**
 * Resolve the neighbor index for a drag. dragX < 0 (finger moved left) goes to
 * the NEXT tab; dragX > 0 goes to the PREVIOUS. Returns null when there is no
 * neighbor in that direction.
 */
export function neighborIndex(active: number, dragX: number, count: number): number | null {
  if (dragX < 0) return active < count - 1 ? active + 1 : null;
  if (dragX > 0) return active > 0 ? active - 1 : null;
  return null;
}
