/**
 * MiActionExecutor — Executes parsed MI actions against the live canvas.
 *
 * Relies on `window.__canvasApi` exposed by canvas.html, which provides:
 *   { newShape, findFreePosition, SHAPE_DEFAULTS, setupShapeEventListeners, sync, canvasContent }
 */

import type { MiAction } from "./mi-actions";

export interface ExecutionResult {
	action: MiAction;
	ok: boolean;
	shapeId?: string;
	error?: string;
}

interface CanvasApi {
	newShape: (tagName: string, props?: Record<string, any>) => any;
	findFreePosition: (width: number, height: number) => { x: number; y: number };
	SHAPE_DEFAULTS: Record<string, { width: number; height: number }>;
	setupShapeEventListeners: (shape: any) => void;
	sync: any;
	canvasContent: HTMLElement;
}

function getCanvasApi(): CanvasApi | null {
	return (window as any).__canvasApi || null;
}

/** Resolve `$N` backreferences in an id string. */
function resolveRef(id: string, refMap: Map<string, string>): string {
	if (id.startsWith("$")) {
		return refMap.get(id) || id;
	}
	return id;
}

export class MiActionExecutor {
	static instance: MiActionExecutor | null = null;

	constructor() {
		if (MiActionExecutor.instance) return MiActionExecutor.instance;
		MiActionExecutor.instance = this;
	}

	execute(actions: MiAction[]): ExecutionResult[] {
		const api = getCanvasApi();
		if (!api) {
			return actions.map((a) => ({ action: a, ok: false, error: "Canvas API not available" }));
		}

		const results: ExecutionResult[] = [];
		const refMap = new Map<string, string>();

		for (const action of actions) {
			try {
				const result = this.#executeOne(action, api, refMap);
				results.push(result);
			} catch (e: any) {
				results.push({ action, ok: false, error: e.message });
			}
		}
		return results;
	}

	#executeOne(action: MiAction, api: CanvasApi, refMap: Map<string, string>): ExecutionResult {
		switch (action.type) {
			case "create-shape": {
				const shape = api.newShape(action.tagName, action.props || {});
				if (!shape) {
					return { action, ok: false, error: `Failed to create ${action.tagName}` };
				}
				if (action.ref) {
					refMap.set(action.ref, shape.id);
				}
				return { action, ok: true, shapeId: shape.id };
			}

			case "update-shape": {
				const el = api.canvasContent.querySelector(`#${CSS.escape(action.shapeId)}`);
				if (!el) {
					return { action, ok: false, error: `Shape ${action.shapeId} not found` };
				}
				for (const [key, value] of Object.entries(action.fields)) {
					(el as any)[key] = value;
				}
				api.sync.updateShape?.(action.shapeId);
				return { action, ok: true, shapeId: action.shapeId };
			}

			case "delete-shape": {
				const el = api.canvasContent.querySelector(`#${CSS.escape(action.shapeId)}`);
				if (!el) {
					return { action, ok: false, error: `Shape ${action.shapeId} not found` };
				}
				api.sync.deleteShape(action.shapeId);
				el.remove();
				return { action, ok: true, shapeId: action.shapeId };
			}

			case "connect": {
				const sourceId = resolveRef(action.sourceId, refMap);
				const targetId = resolveRef(action.targetId, refMap);

				const arrow = document.createElement("folk-arrow");
				const arrowId = `arrow-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
				arrow.id = arrowId;
				(arrow as any).sourceId = sourceId;
				(arrow as any).targetId = targetId;
				if (action.color) (arrow as any).color = action.color;

				api.canvasContent.appendChild(arrow);
				api.sync.registerShape(arrow);
				return { action, ok: true, shapeId: arrowId };
			}

			case "move-shape": {
				const el = api.canvasContent.querySelector(`#${CSS.escape(action.shapeId)}`);
				if (!el) {
					return { action, ok: false, error: `Shape ${action.shapeId} not found` };
				}
				(el as any).x = action.x;
				(el as any).y = action.y;
				api.sync.updateShape?.(action.shapeId);
				return { action, ok: true, shapeId: action.shapeId };
			}

			case "transform": {
				// Delegate to mi-selection-transforms if available
				const transforms = (window as any).__miSelectionTransforms;
				if (transforms && transforms[action.transform]) {
					const shapeEls = action.shapeIds
						.map((id) => api.canvasContent.querySelector(`#${CSS.escape(id)}`))
						.filter(Boolean) as HTMLElement[];
					if (shapeEls.length === 0) {
						return { action, ok: false, error: "No matching shapes found" };
					}
					transforms[action.transform](shapeEls);
					// Persist positions
					for (const el of shapeEls) {
						api.sync.updateShape?.(el.id);
					}
					return { action, ok: true };
				}
				return { action, ok: false, error: `Unknown transform: ${action.transform}` };
			}

			case "navigate": {
				window.location.href = action.path;
				return { action, ok: true };
			}

			default:
				return { action, ok: false, error: `Unknown action type` };
		}
	}
}
