/**
 * AppletTemplateManager — save/instantiate/list/delete applet templates.
 *
 * Templates capture a selection of shapes + their inter-connecting arrows,
 * storing relative positions in CommunityDoc.templates. Instantiation
 * generates new IDs, remaps arrow refs, and places at cursor position.
 */

import type { CommunitySync, CommunityDoc, ShapeData } from "./community-sync";
import type { AppletTemplateRecord, AppletTemplateShape, AppletTemplateArrow } from "../shared/applet-types";
import * as Automerge from "@automerge/automerge";

export class AppletTemplateManager {
	#sync: CommunitySync;

	constructor(sync: CommunitySync) {
		this.#sync = sync;
	}

	/** Get templates map from doc. */
	#getTemplates(): Record<string, AppletTemplateRecord> {
		return (this.#sync.doc as any).templates || {};
	}

	/** Batch-mutate the Automerge doc. */
	#change(msg: string, fn: (doc: any) => void): void {
		const oldDoc = this.#sync.doc;
		const newDoc = Automerge.change(oldDoc, msg, (d: any) => {
			if (!d.templates) d.templates = {};
			fn(d);
		});
		(this.#sync as any)._applyDocChange(newDoc);
	}

	// ── Save ──

	/**
	 * Save selected shapes + their internal arrows as a template.
	 * Only captures arrows where both source AND target are in the selection.
	 */
	saveTemplate(
		selectedIds: string[],
		meta: { name: string; description?: string; icon?: string; color?: string; createdBy?: string },
	): AppletTemplateRecord | null {
		const shapes = this.#sync.doc.shapes || {};
		const selectedSet = new Set(selectedIds);

		// Filter to existing shapes
		const validIds = selectedIds.filter(id => shapes[id]);
		if (validIds.length === 0) return null;

		// Compute bounding box
		let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
		for (const id of validIds) {
			const s = shapes[id];
			minX = Math.min(minX, s.x);
			minY = Math.min(minY, s.y);
			maxX = Math.max(maxX, s.x + s.width);
			maxY = Math.max(maxY, s.y + s.height);
		}

		// Build relative-ID map: shapeId → relativeId
		const idMap = new Map<string, string>();
		let relIdx = 0;

		// Separate non-arrow shapes and arrows
		const templateShapes: AppletTemplateShape[] = [];
		const templateArrows: AppletTemplateArrow[] = [];

		for (const id of validIds) {
			const s = shapes[id];
			if (s.type === "folk-arrow") continue; // handle arrows separately

			const relId = `rel-${relIdx++}`;
			idMap.set(id, relId);

			const { id: _id, x, y, width, height, rotation, type, ...rest } = s;
			templateShapes.push({
				relativeId: relId,
				type,
				relX: x - minX,
				relY: y - minY,
				width,
				height,
				rotation: rotation || 0,
				props: rest as Record<string, unknown>,
			});
		}

		// Find arrows connecting shapes within the selection
		for (const [id, s] of Object.entries(shapes)) {
			if (s.type !== "folk-arrow") continue;
			if (!s.sourceId || !s.targetId) continue;
			if (!selectedSet.has(s.sourceId) || !selectedSet.has(s.targetId)) continue;

			const sourceRelId = idMap.get(s.sourceId);
			const targetRelId = idMap.get(s.targetId);
			if (!sourceRelId || !targetRelId) continue;

			const relId = `rel-${relIdx++}`;
			templateArrows.push({
				relativeId: relId,
				sourceRelId,
				targetRelId,
				sourcePort: s.sourcePort,
				targetPort: s.targetPort,
			});
		}

		const template: AppletTemplateRecord = {
			id: `tpl-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
			name: meta.name,
			description: meta.description || "",
			icon: meta.icon || "📋",
			color: meta.color || "#6366f1",
			createdAt: Date.now(),
			createdBy: meta.createdBy || "unknown",
			shapes: templateShapes,
			arrows: templateArrows,
			boundingWidth: maxX - minX,
			boundingHeight: maxY - minY,
		};

		this.#change(`Save template "${meta.name}"`, (d) => {
			d.templates[template.id] = template;
		});

		return template;
	}

	// ── Instantiate ──

	/**
	 * Create new shapes + arrows from a template at the given position.
	 * Returns array of new shape IDs (for optional group creation).
	 */
	instantiateTemplate(templateId: string, x: number, y: number): string[] {
		const template = this.#getTemplates()[templateId];
		if (!template) return [];

		// Map relativeId → new real ID
		const relToNew = new Map<string, string>();
		const newShapeIds: string[] = [];

		// Create shapes
		for (const tplShape of template.shapes) {
			const newId = `shape-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
			relToNew.set(tplShape.relativeId, newId);

			const shapeData: ShapeData = {
				type: tplShape.type,
				id: newId,
				x: x + tplShape.relX,
				y: y + tplShape.relY,
				width: tplShape.width,
				height: tplShape.height,
				rotation: tplShape.rotation,
				...tplShape.props,
			};

			this.#sync.addShapeData(shapeData);
			newShapeIds.push(newId);
		}

		// Create arrows with remapped source/target
		for (const tplArrow of template.arrows) {
			const sourceId = relToNew.get(tplArrow.sourceRelId);
			const targetId = relToNew.get(tplArrow.targetRelId);
			if (!sourceId || !targetId) continue;

			const arrowId = `arrow-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
			const arrowData: ShapeData = {
				type: "folk-arrow",
				id: arrowId,
				x: 0,
				y: 0,
				width: 0,
				height: 0,
				rotation: 0,
				sourceId,
				targetId,
				sourcePort: tplArrow.sourcePort,
				targetPort: tplArrow.targetPort,
			};

			this.#sync.addShapeData(arrowData);
			newShapeIds.push(arrowId);
		}

		return newShapeIds;
	}

	// ── List / Get / Delete ──

	listTemplates(): AppletTemplateRecord[] {
		return Object.values(this.#getTemplates())
			.sort((a, b) => b.createdAt - a.createdAt);
	}

	getTemplate(id: string): AppletTemplateRecord | undefined {
		return this.#getTemplates()[id];
	}

	deleteTemplate(id: string): void {
		this.#change(`Delete template "${id}"`, (d) => {
			delete d.templates[id];
		});
	}
}
