/**
 * SLA Bridge — bidirectional conversion between Automerge CRDT state and Scribus bridge commands.
 *
 * designDocToScribusCommands(doc) → array of bridge commands (for replaying state into Scribus)
 * scribusStateToDesignDocPatch(state, doc) → diff patches (for updating CRDT from Scribus changes)
 */

import type { DesignDoc, DesignFrame, DesignPage } from './schemas';

// ── Types from the bridge ──

interface BridgeCommand {
	action: string;
	args: Record<string, any>;
}

interface ScribusFrameState {
	name: string;
	type: string; // TextFrame, ImageFrame, Rectangle, Ellipse, etc.
	x: number;
	y: number;
	width: number;
	height: number;
	text?: string;
	fontSize?: number;
	fontName?: string;
}

interface ScribusPageState {
	number: number;
	width: number;
	height: number;
}

interface ScribusState {
	pages: ScribusPageState[];
	frames: ScribusFrameState[];
}

// ── CRDT → Scribus commands ──

/**
 * Convert a full DesignDoc into an ordered list of bridge commands
 * to recreate the design in Scribus from scratch.
 */
export function designDocToScribusCommands(doc: DesignDoc): BridgeCommand[] {
	const commands: BridgeCommand[] = [];
	const pages = Object.values(doc.document.pages).sort((a, b) => a.number - b.number);

	// Create document with first page dimensions (or A4 default)
	const firstPage = pages[0];
	commands.push({
		action: 'new_document',
		args: {
			width: firstPage?.width || 210,
			height: firstPage?.height || 297,
			margins: firstPage?.margins || 10,
			pages: pages.length || 1,
		},
	});

	// Add frames sorted by page, then by creation time
	const frames = Object.values(doc.document.frames).sort((a, b) => {
		if (a.page !== b.page) return a.page - b.page;
		return (a.createdAt || 0) - (b.createdAt || 0);
	});

	for (const frame of frames) {
		switch (frame.type) {
			case 'text':
				commands.push({
					action: 'add_text_frame',
					args: {
						x: frame.x,
						y: frame.y,
						width: frame.width,
						height: frame.height,
						text: frame.text || '',
						fontSize: frame.fontSize || 12,
						fontName: frame.fontName || 'Liberation Sans',
						name: frame.id,
					},
				});
				break;

			case 'image':
				commands.push({
					action: 'add_image_frame',
					args: {
						x: frame.x,
						y: frame.y,
						width: frame.width,
						height: frame.height,
						imagePath: frame.imagePath || '',
						name: frame.id,
					},
				});
				break;

			case 'rect':
			case 'ellipse':
				commands.push({
					action: 'add_shape',
					args: {
						shapeType: frame.type === 'ellipse' ? 'ellipse' : 'rect',
						x: frame.x,
						y: frame.y,
						width: frame.width,
						height: frame.height,
						fill: frame.fill,
						name: frame.id,
					},
				});
				break;
		}
	}

	return commands;
}

/**
 * Convert a subset of changed commands (for incremental updates).
 * Only generates commands for frames that differ from existing state.
 */
export function designDocToIncrementalCommands(
	doc: DesignDoc,
	changedFrameIds: string[],
): BridgeCommand[] {
	const commands: BridgeCommand[] = [];

	for (const frameId of changedFrameIds) {
		const frame = doc.document.frames[frameId];
		if (!frame) {
			// Frame was deleted
			commands.push({ action: 'delete_frame', args: { name: frameId } });
			continue;
		}

		// For simplicity, delete and recreate the frame
		commands.push({ action: 'delete_frame', args: { name: frameId } });

		switch (frame.type) {
			case 'text':
				commands.push({
					action: 'add_text_frame',
					args: {
						x: frame.x, y: frame.y, width: frame.width, height: frame.height,
						text: frame.text || '', fontSize: frame.fontSize || 12,
						fontName: frame.fontName || 'Liberation Sans', name: frame.id,
					},
				});
				break;
			case 'image':
				commands.push({
					action: 'add_image_frame',
					args: {
						x: frame.x, y: frame.y, width: frame.width, height: frame.height,
						imagePath: frame.imagePath || '', name: frame.id,
					},
				});
				break;
			case 'rect':
			case 'ellipse':
				commands.push({
					action: 'add_shape',
					args: {
						shapeType: frame.type === 'ellipse' ? 'ellipse' : 'rect',
						x: frame.x, y: frame.y, width: frame.width, height: frame.height,
						fill: frame.fill, name: frame.id,
					},
				});
				break;
		}
	}

	return commands;
}

// ── Scribus → CRDT patches ──

/** Map Scribus object type strings to our frame types. */
function mapScribusType(scribusType: string): DesignFrame['type'] {
	switch (scribusType) {
		case 'TextFrame': return 'text';
		case 'ImageFrame': return 'image';
		case 'Rectangle': return 'rect';
		case 'Ellipse': return 'ellipse';
		default: return 'rect'; // fallback
	}
}

/**
 * Diff Scribus state against CRDT doc and produce patches.
 * Returns arrays of frames to add, update, and delete in the CRDT.
 */
export function scribusStateToDesignDocPatch(
	scribusState: ScribusState,
	existingDoc: DesignDoc,
): {
	pagesToUpdate: DesignPage[];
	framesToAdd: DesignFrame[];
	framesToUpdate: Array<{ id: string; updates: Partial<DesignFrame> }>;
	framesToDelete: string[];
} {
	const result = {
		pagesToUpdate: [] as DesignPage[],
		framesToAdd: [] as DesignFrame[],
		framesToUpdate: [] as Array<{ id: string; updates: Partial<DesignFrame> }>,
		framesToDelete: [] as string[],
	};

	// Pages
	for (const sp of scribusState.pages) {
		const pageId = `page_${sp.number}`;
		result.pagesToUpdate.push({
			id: pageId,
			number: sp.number,
			width: sp.width,
			height: sp.height,
			margins: 10, // Scribus doesn't report margins in getAllObjects; keep default
		});
	}

	// Frames — build a set of Scribus frame names
	const scribusFrameMap = new Map<string, ScribusFrameState>();
	for (const sf of scribusState.frames) {
		scribusFrameMap.set(sf.name, sf);
	}

	const existingFrameIds = new Set(Object.keys(existingDoc.document.frames));

	// Check for new or updated frames from Scribus
	for (const [name, sf] of scribusFrameMap) {
		const existing = existingDoc.document.frames[name];
		const frameType = mapScribusType(sf.type);

		if (!existing) {
			// New frame in Scribus not in CRDT
			result.framesToAdd.push({
				id: name,
				type: frameType,
				page: 1, // Scribus getAllObjects doesn't report per-frame page easily
				x: sf.x,
				y: sf.y,
				width: sf.width,
				height: sf.height,
				text: sf.text,
				fontSize: sf.fontSize,
				fontName: sf.fontName,
			});
		} else {
			// Check for position/size/text changes
			const updates: Partial<DesignFrame> = {};
			const tolerance = 0.5; // mm tolerance for position changes

			if (Math.abs(existing.x - sf.x) > tolerance) updates.x = sf.x;
			if (Math.abs(existing.y - sf.y) > tolerance) updates.y = sf.y;
			if (Math.abs(existing.width - sf.width) > tolerance) updates.width = sf.width;
			if (Math.abs(existing.height - sf.height) > tolerance) updates.height = sf.height;
			if (sf.text !== undefined && sf.text !== existing.text) updates.text = sf.text;
			if (sf.fontSize !== undefined && sf.fontSize !== existing.fontSize) updates.fontSize = sf.fontSize;

			if (Object.keys(updates).length > 0) {
				result.framesToUpdate.push({ id: name, updates });
			}
		}
	}

	// Check for frames deleted in Scribus
	for (const frameId of existingFrameIds) {
		if (!scribusFrameMap.has(frameId)) {
			result.framesToDelete.push(frameId);
		}
	}

	return result;
}
