import { CanvasSmbl } from './canvas-smbl.js';
import { ProcessedSmbl } from './move-evt-proc.js';
import { listen, listenDel } from './util.js';

/**
 * Get point in canvas given the scale and position of the canvas
 * @param {{position:{x:number, y:number}, scale:number}} canvasData
 * @param {number} x, @param {number} y
 */
export const pointInCanvas = (canvasData, x, y) => ({
	x: (x - canvasData.position.x) / canvasData.scale,
	y: (y - canvasData.position.y) / canvasData.scale
});

/**
 * @param {Point} point
 * @param {number} cell
 */
export function placeToCell(point, cell) {
	const cellSizeHalf = cell / 2;
	function placeToCell(coordinate) {
		const coor = (Math.round(coordinate / cell) * cell);
		return (coordinate - coor > 0) ? coor + cellSizeHalf : coor - cellSizeHalf;
	}

	point.x = placeToCell(point.x);
	point.y = placeToCell(point.y);
}

/** @param { CanvasElement } canvas */
export function moveScaleApplay(canvas) {
	const canvasData = canvas[CanvasSmbl].data;

	const gripUpdate = applayGrid(canvas.ownerSVGElement, canvasData);

	function transform() {
		canvas.style.transform = `matrix(${canvasData.scale}, 0, 0, ${canvasData.scale}, ${canvasData.position.x}, ${canvasData.position.y})`;
		gripUpdate();
	}

	/**
	 * @param {number} nextScale
	 * @param {Point} originPoint
	 */
	function scale(nextScale, originPoint) {
		if (nextScale < 0.25 || nextScale > 4) { return; }

		const divis = nextScale / canvasData.scale;
		canvasData.scale = nextScale;

		canvasData.position.x = divis * (canvasData.position.x - originPoint.x) + originPoint.x;
		canvasData.position.y = divis * (canvasData.position.y - originPoint.y) + originPoint.y;

		transform();
	}

	// move, scale with fingers
	applayFingers(canvas.ownerSVGElement, canvasData, scale, transform);

	// scale with mouse wheel
	canvas.ownerSVGElement.addEventListener('wheel', /** @param {WheelEvent} evt */ evt => {
		evt.preventDefault();
		const delta = evt.deltaY || evt.deltaX;
		const scaleStep = Math.abs(delta) < 50
			? 0.05 // trackpad pitch
			: 0.25; // mouse wheel

		scale(
			canvasData.scale + (delta < 0 ? scaleStep : -scaleStep),
			evtPoint(evt));
	});

	canvas[CanvasSmbl].move = function (x, y, scale) {
		canvasData.position.x = x;
		canvasData.position.y = y;
		canvasData.scale = scale;
		transform();
	};
}

/**
 * @param { SVGSVGElement } svg
 * @param { {position:Point, scale:number} } canvasData
 * @param { {(nextScale:number, originPoint:Point):void} } scaleFn
 * @param { {():void} } transformFn
 * @return
 */
function applayFingers(svg, canvasData, scaleFn, transformFn) {
	/** @type { Pointer } */
	let firstPointer;

	/** @type { Pointer} */
	let secondPointer;

	/** @type {number} */
	let distance;

	/** @type {Point} */
	let center;

	/** @param {PointerEvent} evt */
	function cancel(evt) {
		distance = null;
		center = null;
		if (firstPointer?.id === evt.pointerId) { firstPointer = null; }
		if (secondPointer?.id === evt.pointerId) { secondPointer = null; }

		if (!firstPointer && !secondPointer) {
			listenDel(svg, 'pointermove', move);
			listenDel(svg, 'pointercancel', cancel);
			listenDel(svg, 'pointerup', cancel);
		}
	};

	/** @param {PointerEvent} evt */
	function move(evt) {
		if (evt[ProcessedSmbl]) { return; }

		if ((firstPointer && !secondPointer) || (!firstPointer && secondPointer)) {
			// move with one pointer
			canvasData.position.x = evt.clientX + (firstPointer || secondPointer).shift.x;
			canvasData.position.y = evt.clientY + (firstPointer || secondPointer).shift.y;
			transformFn();
			return;
		}

		if (!secondPointer || !firstPointer || (secondPointer?.id !== evt.pointerId && firstPointer?.id !== evt.pointerId)) { return; }

		const distanceNew = Math.hypot(firstPointer.pos.x - secondPointer.pos.x, firstPointer.pos.y - secondPointer.pos.y);
		const centerNew = {
			x: (firstPointer.pos.x + secondPointer.pos.x) / 2,
			y: (firstPointer.pos.y + secondPointer.pos.y) / 2
		};

		// not first move
		if (distance) {
			canvasData.position.x = canvasData.position.x + centerNew.x - center.x;
			canvasData.position.y = canvasData.position.y + centerNew.y - center.y;

			scaleFn(
				canvasData.scale / distance * distanceNew,
				centerNew);
		}

		distance = distanceNew;
		center = centerNew;

		if (firstPointer.id === evt.pointerId) { firstPointer = evtPointer(evt, canvasData); }
		if (secondPointer.id === evt.pointerId) { secondPointer = evtPointer(evt, canvasData); }
	}

	listen(svg, 'pointerdown', /** @param {PointerEvent} evt */ evt => {
		if (evt[ProcessedSmbl] || (!firstPointer && !evt.isPrimary) || (firstPointer && secondPointer)) {
			return;
		}

		svg.setPointerCapture(evt.pointerId);
		if (!firstPointer) {
			listen(svg, 'pointermove', move);
			listen(svg, 'pointercancel', cancel);
			listen(svg, 'pointerup', cancel);
		}

		if (!firstPointer) { firstPointer = evtPointer(evt, canvasData); return; }
		if (!secondPointer) { secondPointer = evtPointer(evt, canvasData); }
	});
}

/**
 * @param { SVGSVGElement } svg
 * @param { import('./canvas-smbl.js').CanvasData } canvasData
 */
function applayGrid(svg, canvasData) {
	let curOpacity;
	/** @param {number} opacity */
	function backImg(opacity) {
		if (curOpacity !== opacity) {
			curOpacity = opacity;
			svg.style.backgroundImage = `radial-gradient(rgb(73 80 87 / ${opacity}) 1px, transparent 0)`;
		}
	}

	backImg(0.7);
	svg.style.backgroundSize = `${canvasData.cell}px ${canvasData.cell}px`;

	return function() {
		const size = canvasData.cell * canvasData.scale;

		if (canvasData.scale < 0.5) { backImg(0); } else
		if (canvasData.scale <= 0.9) { backImg(0.3); } else { backImg(0.7); }

		svg.style.backgroundSize = `${size}px ${size}px`;
		svg.style.backgroundPosition = `${canvasData.position.x}px ${canvasData.position.y}px`;
	};
}

/**
 * @param {PointerEvent | MouseEvent} evt
 * @return {Point}
 */
function evtPoint(evt) { return { x: evt.clientX, y: evt.clientY }; }

/**
 * @param { PointerEvent } evt
 * @param { {position:Point, scale:number} } canvasData
 * @return { Pointer }
 */
function evtPointer(evt, canvasData) {
	return {
		id: evt.pointerId,
		pos: evtPoint(evt),
		shift: {
			x: canvasData.position.x - evt.clientX,
			y: canvasData.position.y - evt.clientY
		}
	};
}

/** @typedef { {x:number, y:number} } Point */
/** @typedef { {id:number, pos:Point, shift:Point} } Pointer */
/** @typedef { import("./move-evt-proc").ProcEvent } DgrmEvent */
/** @typedef { import('./canvas-smbl.js').CanvasData } CanvasData */
/** @typedef { import('./canvas-smbl.js').CanvasElement } CanvasElement */
