type Options<T extends { id: string }> = {
	getSortedBlocks: () => T[];
	onReorder: (blockIds: string[]) => void;
};

export function createBlockDragDrop<T extends { id: string }>({
	getSortedBlocks,
	onReorder
}: Options<T>) {
	let draggedBlockId = $state<string | null>(null);
	let dropTargetIdx = $state<number | null>(null);
	let dragOffsetY = $state(0);

	// Internal mutable refs — not reactive
	let dragStartY = 0;
	let capturedEl: HTMLElement | null = null;
	let listEl: HTMLElement | null = null;

	function setListElement(el: HTMLElement | null): void {
		listEl = el;
	}

	function handleGripDown(e: PointerEvent, blockId: string): void {
		if (!(e.target as HTMLElement).closest('[data-drag-handle]')) return;
		e.preventDefault();
		draggedBlockId = blockId;
		dragStartY = e.clientY;
		dragOffsetY = 0;
		capturedEl = (e.target as HTMLElement).closest('[data-block-wrapper]') as HTMLElement;
		capturedEl?.setPointerCapture(e.pointerId);
	}

	function handlePointerMove(e: PointerEvent): void {
		if (!draggedBlockId || !listEl) return;
		dragOffsetY = e.clientY - dragStartY;

		const sortedBlocks = getSortedBlocks();
		const wrappers = Array.from(listEl.querySelectorAll('[data-block-wrapper]'));
		const dragIdx = sortedBlocks.findIndex((b) => b.id === draggedBlockId);
		let target: number | null = null;

		for (let i = 0; i < wrappers.length; i++) {
			const rect = wrappers[i].getBoundingClientRect();
			if (e.clientY < rect.top + rect.height / 2) {
				target = i;
				break;
			}
		}
		if (target === null) target = wrappers.length;
		if (target === dragIdx || target === dragIdx + 1) target = null;
		dropTargetIdx = target;
	}

	function handlePointerUp(): void {
		if (!draggedBlockId) return;

		if (dropTargetIdx !== null) {
			const sorted = [...getSortedBlocks()];
			const fromIdx = sorted.findIndex((b) => b.id === draggedBlockId);
			if (fromIdx >= 0) {
				const [moved] = sorted.splice(fromIdx, 1);
				const insertAt = dropTargetIdx > fromIdx ? dropTargetIdx - 1 : dropTargetIdx;
				sorted.splice(insertAt, 0, moved);
				onReorder(sorted.map((b) => b.id));
			}
		}

		draggedBlockId = null;
		dropTargetIdx = null;
		dragOffsetY = 0;
		capturedEl = null;
	}

	return {
		get draggedBlockId() {
			return draggedBlockId;
		},
		get dropTargetIdx() {
			return dropTargetIdx;
		},
		get dragOffsetY() {
			return dragOffsetY;
		},
		setListElement,
		handleGripDown,
		handlePointerMove,
		handlePointerUp
	};
}
