import { lineLen, Pos } from "@/utils/math.ts";
import { mergeFuns } from "@/utils/shared.ts";

export const listener = <T extends HTMLElement | Window, K extends keyof HTMLElementEventMap>(
	target: T,
	eventName: K,
	callback: (this: T, ev: HTMLElementEventMap[K]) => any
) => {
	target.addEventListener(eventName, callback as any)
	return () => {
		target.removeEventListener(eventName, callback as any)
	}
}

export const clickListener = (dom: HTMLDivElement, callback: (position: Pos, ev: PointerEvent) => void) => {
	let downTime = 0;
	let move = false
	return dragListener(dom, {
		down(_, ev) {
			if (ev.button !== 0) return;
			downTime = Date.now();
		},
		up(position, ev) {
			const prevMove = move
			move = false
			if (prevMove || !downTime) return;
			if (Date.now() - downTime <= 300) {
				callback(position, ev)
			}
			downTime = 0
		},
	});
};

export const selectOnlyPosition = (dom: HTMLDivElement) => {
	let stopListener: any, reject: any;
	const promise = new Promise<Pos>(function(resolve, _reject) {
		reject = _reject
		stopListener = clickListener(dom, (pos) => {
			stopListener()
			resolve(pos)
		})
	}) as Promise<Pos> & { stop: () => void }

	promise.stop = () => {
		stopListener()
		reject()
	}

	return promise
}


type DragProps = {
	move?: (info: Record<'start' | 'prev' | 'end', Pos> & {ev: PointerEvent}) => void,
	down?: (pos: Pos, ev: PointerEvent) => void,
	up?: (pos: Pos, ev: PointerEvent) => void,
	notPrevent?: boolean
}
export const dragListener = (dom: HTMLElement, props: DragProps | DragProps['move'] = {}) => {
	if (typeof props === 'function') {
		props = { move: props }
	}
	const { move, up, down } = props
	const mount = document.documentElement

	if (!move && !up && !down) return () => {}

	let moveHandler: any, endHandler: any
	const downHandler = (ev: PointerEvent) => {
		const start = getOffset(ev, dom)
		let prev = start
		down && down(start, ev)
		props.notPrevent || ev.preventDefault();

		moveHandler = (ev: PointerEvent) => {
			const end = getOffset(ev, dom)
			move!({start, end, prev, ev})
			prev = end

			props.notPrevent || ev.preventDefault();
		}
		endHandler = (ev: PointerEvent) => {
			up && up(getOffset(ev, dom), ev)
			mount.removeEventListener('pointermove', moveHandler);
			mount.removeEventListener('pointerup', endHandler);
			props.notPrevent || ev.preventDefault();
		}
	
		
		move && mount.addEventListener('pointermove', moveHandler, { passive: false })
		mount.addEventListener('pointerup', endHandler, { passive: false })
	}

	dom.addEventListener('pointerdown', downHandler, { passive: false });
	return () => {
		dom.removeEventListener('pointerdown', downHandler);
		moveHandler && mount.removeEventListener('pointermove', moveHandler);
		endHandler && mount.removeEventListener('pointerup', endHandler);
	}
}




export const getTouchScaleProps = (ev: TouchEvent, dom = ev.target! as HTMLElement) => {
	const start = getOffset(ev,  dom, 0);
	const end = getOffset(ev, dom, 1);
	const center = {
		x: (end.x + start.x) / 2,
		y: (end.y + start.y) / 2,
	};
	const initDist = lineLen(start, end);
	return {
		center,
		dist: initDist,
	};
};

export const getOffset = (ev: MouseEvent | TouchEvent, dom = ev.target! as HTMLElement, ndx = 0) => {
	const event = ev instanceof TouchEvent ? ev.changedTouches[ndx] : ev
	const rect = dom.getBoundingClientRect();
	const offsetX = event.clientX - rect.left;
	const offsetY = event.clientY - rect.top;
	return {
		x: offsetX,
		y: offsetY,
	};
};


export const touchScaleListener = (
	dom: HTMLElement,
	cb: (props: { center: Pos; scale: number }) => void
) => {
	const mount = document.documentElement
	let moveHandler: (ev: TouchEvent) => void
	let endHandler: (ev: TouchEvent) => void
	const startHandler = (ev: TouchEvent) => {
		if (ev.changedTouches.length <= 1) return;
		let prevScale = getTouchScaleProps(ev, dom);
		ev.preventDefault();

		moveHandler = (ev: TouchEvent) => {
			if (ev.changedTouches.length <= 1) return;
			const curScale = getTouchScaleProps(ev, dom);
			cb({center: prevScale.center, scale: curScale.dist / prevScale.dist});
			prevScale = curScale;
			ev.preventDefault();
		};
		endHandler = (ev: TouchEvent) => {
			mount.removeEventListener("touchmove", moveHandler);
			mount.removeEventListener("touchend", endHandler);
			ev.preventDefault();
		};

		mount.addEventListener("touchmove", moveHandler, {
			passive: false,
		});
		mount.addEventListener("touchend", endHandler, {
			passive: false,
		});
	};

	dom.addEventListener("touchstart", startHandler, {passive: false});

	return () => {
		dom.removeEventListener("touchstart", startHandler);
		mount.removeEventListener("touchmove", moveHandler);
		mount.removeEventListener("touchend", endHandler);
	};
};


export const wheelListener = (
	dom: HTMLElement,
	cb: (props: { center: Pos; scale: number }) => void
) => {
	const wheelHandler = (ev: WheelEvent) => {
		const scale = 1 - ev.deltaY / 1000;
		const center = {x: ev.offsetX, y: ev.offsetY};
		cb({center, scale});
		ev.preventDefault();
	};

	dom.addEventListener("wheel", wheelHandler);
	return () => {
		dom.removeEventListener("wheel", wheelHandler);
	};
};

export const scaleListener = (
	dom: HTMLElement,
	cb: (props: { center: Pos; scale: number }) => void
) => mergeFuns(touchScaleListener(dom, cb), wheelListener(dom, cb));
