// Svelte action — the single source of truth for tap gestures on a media element.
// Fires a `doubletap` CustomEvent when two pointerup events occur within `interval`
// ms on roughly the same spot (Instagram-style "double-tap to like"), and a
// `singletap` CustomEvent once `interval` has elapsed with no second tap. Owning
// both gestures in one place means a component no longer juggles its own debounce
// timer alongside a separate onclick handler.
//
// Native `dblclick` exists, but on iOS Safari it also zooms the page; gating on
// pointer events lets us preventDefault selectively and avoid the zoom. Keyboard
// activation still arrives as a normal `click` (detail === 0) — handle that on the
// element itself for an immediate, latency-free open.

import type { ActionReturn } from 'svelte/action';

const INTERVAL_MS = 300;
const MOVE_THRESHOLD = 12;

export interface DoubletapOptions {
	interval?: number;
}

interface DoubletapAttributes {
	ondoubletap?: (event: CustomEvent<void>) => void;
	onsingletap?: (event: CustomEvent<void>) => void;
}

export function doubletap(
	node: HTMLElement,
	options: DoubletapOptions = {}
): ActionReturn<DoubletapOptions, DoubletapAttributes> {
	let interval = options.interval ?? INTERVAL_MS;
	let lastTime = 0;
	let lastX = 0;
	let lastY = 0;
	let singleTimer: ReturnType<typeof setTimeout> | null = null;

	const clearSingle = () => {
		if (singleTimer) {
			clearTimeout(singleTimer);
			singleTimer = null;
		}
	};

	const onPointerUp = (e: PointerEvent) => {
		const now = performance.now();
		const dx = Math.abs(e.clientX - lastX);
		const dy = Math.abs(e.clientY - lastY);
		if (now - lastTime < interval && dx < MOVE_THRESHOLD && dy < MOVE_THRESHOLD) {
			clearSingle(); // the pending single-tap was actually the first of a double
			e.preventDefault();
			node.dispatchEvent(new CustomEvent('doubletap'));
			lastTime = 0; // reset so a triple-tap doesn't re-fire
			return;
		}
		lastTime = now;
		lastX = e.clientX;
		lastY = e.clientY;
		// Defer the single-tap action until we're sure no second tap follows.
		clearSingle();
		singleTimer = setTimeout(() => {
			singleTimer = null;
			node.dispatchEvent(new CustomEvent('singletap'));
		}, interval);
	};

	node.addEventListener('pointerup', onPointerUp);

	return {
		update(newOptions) {
			interval = newOptions.interval ?? INTERVAL_MS;
		},
		destroy() {
			clearSingle();
			node.removeEventListener('pointerup', onPointerUp);
		}
	};
}
