/**
 * Shared drag-and-drop utilities for cross-module item dragging.
 *
 * Any module card/list-item can become draggable by calling
 * `makeDraggable(el, payload)` — the calendar and reminders widget
 * will accept the drop via `application/rspace-item`.
 *
 * Uses native HTML5 drag for mouse, and a PointerEvent-based fallback
 * for touch/pen that synthesizes real DragEvents with a constructed
 * DataTransfer — drop receivers don't need to change.
 */

export interface RSpaceItemPayload {
	title: string;
	module: string;       // e.g. "rnotes", "rtasks", "rfiles"
	entityId: string;
	label?: string;       // human-readable source, e.g. "Note", "Task"
	color?: string;       // module accent color
}

/** Module accent colors for drag ghost + calendar indicators */
export const MODULE_COLORS: Record<string, string> = {
	rnotes:  "#f59e0b",  // amber
	rtasks:  "#3b82f6",  // blue
	rfiles:  "#10b981",  // emerald
	rsplat:  "#818cf8",  // indigo
	rphotos: "#ec4899",  // pink
	rbooks:  "#f97316",  // orange
	rforum:  "#8b5cf6",  // violet
	rinbox:  "#06b6d4",  // cyan
	rvote:   "#ef4444",  // red
	rtube:   "#a855f7",  // purple
};

const TOUCH_DRAG_THRESHOLD = 8;  // px before a touch becomes a drag
const TOUCH_DRAG_LONGPRESS_MS = 350;

/**
 * Make an element draggable with the rspace-item protocol.
 * Adds draggable attribute and dragstart handler for mouse, plus a
 * pointer-based touch/pen fallback that fires synthetic DragEvents.
 */
export function makeDraggable(el: HTMLElement, payload: RSpaceItemPayload) {
	el.draggable = true;
	el.style.cursor = "grab";

	// Native HTML5 drag (mouse / desktop trackpad)
	el.addEventListener("dragstart", (e) => {
		if (!e.dataTransfer) return;
		e.dataTransfer.setData("application/rspace-item", JSON.stringify(payload));
		e.dataTransfer.setData("text/plain", payload.title);
		e.dataTransfer.effectAllowed = "copyMove";
		el.style.opacity = "0.6";
	});
	el.addEventListener("dragend", () => {
		el.style.opacity = "";
	});

	// Pointer-based fallback for touch + pen (iOS/iPadOS/Android)
	attachPointerDragFallback(el, payload);
}

/**
 * Attach drag handlers to all matching elements within a root.
 * `selector` matches the card elements.
 * `payloadFn` extracts the payload from each matched element.
 */
export function makeDraggableAll(
	root: Element | ShadowRoot,
	selector: string,
	payloadFn: (el: HTMLElement) => RSpaceItemPayload | null,
) {
	root.querySelectorAll<HTMLElement>(selector).forEach((el) => {
		const payload = payloadFn(el);
		if (payload) makeDraggable(el, payload);
	});
}

// ── Pointer-based drag fallback ──────────────────────────────────────

interface TouchDragState {
	el: HTMLElement;
	payload: RSpaceItemPayload;
	startX: number;
	startY: number;
	started: boolean;
	pointerId: number;
	dataTransfer: DataTransfer | null;
	ghost: HTMLElement | null;
	lastTarget: Element | null;
	longPressTimer: ReturnType<typeof setTimeout> | null;
}

function attachPointerDragFallback(el: HTMLElement, payload: RSpaceItemPayload) {
	let state: TouchDragState | null = null;

	el.addEventListener("pointerdown", (e) => {
		// Mouse goes through native HTML5 drag path — skip.
		if (e.pointerType === "mouse") return;
		// Only primary button
		if (e.button !== 0) return;
		// If inside an editable surface, don't hijack
		const target = e.target as HTMLElement | null;
		if (target?.closest("input, textarea, [contenteditable='true']")) return;

		state = {
			el,
			payload,
			startX: e.clientX,
			startY: e.clientY,
			started: false,
			pointerId: e.pointerId,
			dataTransfer: null,
			ghost: null,
			lastTarget: null,
			longPressTimer: null,
		};

		// Long-press alternative: start drag after 350ms even without movement
		state.longPressTimer = setTimeout(() => {
			if (state && !state.started) beginPointerDrag(state, e.clientX, e.clientY);
		}, TOUCH_DRAG_LONGPRESS_MS);
	});

	window.addEventListener("pointermove", (e) => {
		if (!state || e.pointerId !== state.pointerId) return;
		if (!state.started) {
			const dx = e.clientX - state.startX;
			const dy = e.clientY - state.startY;
			if (Math.hypot(dx, dy) < TOUCH_DRAG_THRESHOLD) return;
			beginPointerDrag(state, e.clientX, e.clientY);
		}
		if (!state.started) return;

		// Prevent scrolling while dragging
		e.preventDefault();

		// Move ghost
		if (state.ghost) {
			state.ghost.style.transform = `translate(${e.clientX}px, ${e.clientY}px)`;
		}

		// Find current drop target (hide ghost briefly so it doesn't block elementFromPoint)
		const ghost = state.ghost;
		if (ghost) ghost.style.display = "none";
		const under = document.elementFromPoint(e.clientX, e.clientY);
		if (ghost) ghost.style.display = "";

		if (under !== state.lastTarget) {
			// dragleave on previous
			if (state.lastTarget && state.dataTransfer) {
				dispatchDragEvent(state.lastTarget, "dragleave", state.dataTransfer, e.clientX, e.clientY);
			}
			state.lastTarget = under;
			if (under && state.dataTransfer) {
				dispatchDragEvent(under, "dragenter", state.dataTransfer, e.clientX, e.clientY);
			}
		}
		if (state.lastTarget && state.dataTransfer) {
			dispatchDragEvent(state.lastTarget, "dragover", state.dataTransfer, e.clientX, e.clientY);
		}
	}, { passive: false });

	const finish = (e: PointerEvent) => {
		if (!state || e.pointerId !== state.pointerId) return;
		if (state.longPressTimer) clearTimeout(state.longPressTimer);

		if (state.started) {
			// Dispatch drop on current target
			if (state.lastTarget && state.dataTransfer) {
				dispatchDragEvent(state.lastTarget, "drop", state.dataTransfer, e.clientX, e.clientY);
			}
			// Dispatch dragend on source
			if (state.dataTransfer) {
				dispatchDragEvent(state.el, "dragend", state.dataTransfer, e.clientX, e.clientY);
			}
			state.el.style.opacity = "";
			if (state.ghost) state.ghost.remove();
		}
		state = null;
	};

	window.addEventListener("pointerup", finish);
	window.addEventListener("pointercancel", finish);
}

function beginPointerDrag(state: TouchDragState, x: number, y: number) {
	state.started = true;
	state.el.style.opacity = "0.4";

	// Build a real DataTransfer so existing dragover/drop listeners that
	// call `e.dataTransfer.getData('application/rspace-item')` work unchanged.
	let dt: DataTransfer;
	try {
		dt = new DataTransfer();
		dt.setData("application/rspace-item", JSON.stringify(state.payload));
		dt.setData("text/plain", state.payload.title);
		dt.effectAllowed = "copyMove";
	} catch {
		// Older Safari fallback — we'll still dispatch events, but dataTransfer.getData
		// may not work. Consumers can read from (event as any).rspaceItemPayload.
		dt = new DataTransfer();
	}
	state.dataTransfer = dt;

	// Let source's existing `dragstart` listeners set extra payloads
	// (e.g. rdocs `application/x-rdocs-move`).
	dispatchDragEvent(state.el, "dragstart", dt, x, y);

	// Build drag ghost (floating preview of the source element)
	const ghost = document.createElement("div");
	ghost.textContent = state.payload.title;
	ghost.style.cssText = `
		position: fixed;
		top: 0;
		left: 0;
		transform: translate(${x}px, ${y}px);
		z-index: 100000;
		pointer-events: none;
		padding: 8px 12px;
		background: ${state.payload.color || "#3b82f6"};
		color: #fff;
		border-radius: 8px;
		font-size: 13px;
		font-weight: 600;
		box-shadow: 0 4px 16px rgba(0,0,0,0.25);
		opacity: 0.92;
		max-width: 260px;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	`;
	document.body.appendChild(ghost);
	state.ghost = ghost;
}

function dispatchDragEvent(
	target: Element | EventTarget,
	type: string,
	dataTransfer: DataTransfer,
	clientX: number,
	clientY: number,
): void {
	let ev: DragEvent;
	try {
		ev = new DragEvent(type, {
			bubbles: true,
			cancelable: true,
			clientX,
			clientY,
			dataTransfer,
		});
	} catch {
		// Older browsers: fall back to CustomEvent with dataTransfer attached
		const fallback = new Event(type, { bubbles: true, cancelable: true });
		Object.defineProperty(fallback, "dataTransfer", { value: dataTransfer, enumerable: true });
		Object.defineProperty(fallback, "clientX", { value: clientX, enumerable: true });
		Object.defineProperty(fallback, "clientY", { value: clientY, enumerable: true });
		target.dispatchEvent(fallback);
		return;
	}
	target.dispatchEvent(ev);
}
