import type { FolkShape } from "./folk-shape";

export type PinPosition =
	| "current"
	| "top-left"
	| "top-center"
	| "top-right"
	| "center-left"
	| "center"
	| "center-right"
	| "bottom-left"
	| "bottom-center"
	| "bottom-right";

interface PinnedShapeState {
	shape: FolkShape;
	position: PinPosition;
	offset: { x: number; y: number };
	originalX: number;
	originalY: number;
}

/**
 * PinnedViewManager - Keeps shapes fixed to viewport positions as the canvas moves/zooms
 *
 * Usage:
 *   const manager = new PinnedViewManager(canvasElement);
 *   manager.pin(shape, 'top-right');
 *   manager.unpin(shape);
 */
export class PinnedViewManager {
	#canvas: HTMLElement;
	#pinnedShapes = new Map<FolkShape, PinnedShapeState>();
	#rafId: number | null = null;
	#lastCanvasTransform = { x: 0, y: 0, scale: 1 };

	constructor(canvas: HTMLElement) {
		this.#canvas = canvas;
		this.#startTracking();
	}

	/**
	 * Pin a shape to a viewport position
	 * @param shape - The shape to pin
	 * @param position - Where to pin it (default: 'current' keeps current screen position)
	 */
	pin(shape: FolkShape, position: PinPosition = "current"): void {
		if (this.#pinnedShapes.has(shape)) {
			// Update position if already pinned
			const state = this.#pinnedShapes.get(shape)!;
			state.position = position;
			this.#updateOffset(state);
			return;
		}

		const state: PinnedShapeState = {
			shape,
			position,
			offset: { x: 0, y: 0 },
			originalX: shape.x,
			originalY: shape.y,
		};

		this.#updateOffset(state);
		this.#pinnedShapes.set(shape, state);

		// Add pinned attribute for styling
		shape.setAttribute("data-pinned", position);

		// Dispatch event
		shape.dispatchEvent(
			new CustomEvent("pin", {
				detail: { pinned: true, position },
				bubbles: true,
			})
		);
	}

	/**
	 * Unpin a shape, returning it to normal canvas behavior
	 */
	unpin(shape: FolkShape): void {
		const state = this.#pinnedShapes.get(shape);
		if (!state) return;

		this.#pinnedShapes.delete(shape);
		shape.removeAttribute("data-pinned");

		// Dispatch event
		shape.dispatchEvent(
			new CustomEvent("pin", {
				detail: { pinned: false },
				bubbles: true,
			})
		);
	}

	/**
	 * Check if a shape is pinned
	 */
	isPinned(shape: FolkShape): boolean {
		return this.#pinnedShapes.has(shape);
	}

	/**
	 * Get the pin position of a shape
	 */
	getPinPosition(shape: FolkShape): PinPosition | null {
		return this.#pinnedShapes.get(shape)?.position || null;
	}

	/**
	 * Toggle pin state of a shape
	 */
	togglePin(shape: FolkShape, position: PinPosition = "current"): void {
		if (this.isPinned(shape)) {
			this.unpin(shape);
		} else {
			this.pin(shape, position);
		}
	}

	/**
	 * Clean up - call when destroying the canvas
	 */
	destroy(): void {
		if (this.#rafId !== null) {
			cancelAnimationFrame(this.#rafId);
		}
		this.#pinnedShapes.clear();
	}

	#updateOffset(state: PinnedShapeState): void {
		const { shape, position } = state;
		const viewport = this.#getViewport();
		const padding = 20;

		switch (position) {
			case "current":
				// Keep current screen position
				state.offset = { x: shape.x, y: shape.y };
				break;
			case "top-left":
				state.offset = { x: padding, y: padding };
				break;
			case "top-center":
				state.offset = {
					x: viewport.width / 2 - shape.width / 2,
					y: padding,
				};
				break;
			case "top-right":
				state.offset = {
					x: viewport.width - shape.width - padding,
					y: padding,
				};
				break;
			case "center-left":
				state.offset = {
					x: padding,
					y: viewport.height / 2 - shape.height / 2,
				};
				break;
			case "center":
				state.offset = {
					x: viewport.width / 2 - shape.width / 2,
					y: viewport.height / 2 - shape.height / 2,
				};
				break;
			case "center-right":
				state.offset = {
					x: viewport.width - shape.width - padding,
					y: viewport.height / 2 - shape.height / 2,
				};
				break;
			case "bottom-left":
				state.offset = {
					x: padding,
					y: viewport.height - shape.height - padding,
				};
				break;
			case "bottom-center":
				state.offset = {
					x: viewport.width / 2 - shape.width / 2,
					y: viewport.height - shape.height - padding,
				};
				break;
			case "bottom-right":
				state.offset = {
					x: viewport.width - shape.width - padding,
					y: viewport.height - shape.height - padding,
				};
				break;
		}
	}

	#getViewport(): { width: number; height: number } {
		return {
			width: window.innerWidth,
			height: window.innerHeight,
		};
	}

	#getCanvasTransform(): { x: number; y: number; scale: number } {
		const transform = this.#canvas.style.transform;
		if (!transform) {
			return { x: 0, y: 0, scale: 1 };
		}

		// Parse scale
		const scaleMatch = transform.match(/scale\(([^)]+)\)/);
		const scale = scaleMatch ? parseFloat(scaleMatch[1]) : 1;

		// Parse translate
		const translateMatch = transform.match(
			/translate\(([^,]+),\s*([^)]+)\)/
		);
		const x = translateMatch ? parseFloat(translateMatch[1]) : 0;
		const y = translateMatch ? parseFloat(translateMatch[2]) : 0;

		return { x, y, scale };
	}

	#startTracking(): void {
		const update = () => {
			const currentTransform = this.#getCanvasTransform();

			// Only update if transform changed
			if (
				currentTransform.x !== this.#lastCanvasTransform.x ||
				currentTransform.y !== this.#lastCanvasTransform.y ||
				currentTransform.scale !== this.#lastCanvasTransform.scale
			) {
				this.#lastCanvasTransform = currentTransform;
				this.#updatePinnedShapes();
			}

			this.#rafId = requestAnimationFrame(update);
		};

		this.#rafId = requestAnimationFrame(update);
	}

	#updatePinnedShapes(): void {
		const { x: canvasX, y: canvasY, scale } = this.#lastCanvasTransform;

		for (const [, state] of this.#pinnedShapes) {
			const { shape, offset, position } = state;

			if (position === "current") {
				// For 'current' position, compensate for canvas movement
				shape.x = (offset.x - canvasX) / scale;
				shape.y = (offset.y - canvasY) / scale;
			} else {
				// For fixed positions, recalculate offset and apply
				this.#updateOffset(state);
				shape.x = (offset.x - canvasX) / scale;
				shape.y = (offset.y - canvasY) / scale;
			}
		}
	}
}

// Singleton instance for convenience
let defaultManager: PinnedViewManager | null = null;

/**
 * Get or create a PinnedViewManager for the given canvas
 */
export function getPinnedViewManager(canvas: HTMLElement): PinnedViewManager {
	if (!defaultManager) {
		defaultManager = new PinnedViewManager(canvas);
	}
	return defaultManager;
}
