import type { AnyPass, RenderPass, RenderPassInputSlot, RenderPassOutputSlot } from './types.js';

/**
 * Resolved render-pass step with defaults applied.
 */
export interface RenderGraphStep {
	/**
	 * Step kind. 'render' for post-scene render passes, 'compute' for pre-scene compute passes.
	 */
	kind: 'render' | 'compute';
	/**
	 * User pass instance.
	 */
	pass: AnyPass;
	/**
	 * Resolved input slot. Ignored for compute steps.
	 */
	input: RenderPassInputSlot;
	/**
	 * Resolved output slot. Ignored for compute steps.
	 */
	output: RenderPassOutputSlot;
	/**
	 * Whether ping-pong swap should be performed after render.
	 */
	needsSwap: boolean;
	/**
	 * Whether pass should clear output before drawing.
	 */
	clear: boolean;
	/**
	 * Effective clear color.
	 */
	clearColor: [number, number, number, number];
	/**
	 * Whether output should be preserved after pass ends.
	 */
	preserve: boolean;
}

/**
 * Immutable render-graph execution plan for one frame.
 */
export interface RenderGraphPlan {
	/**
	 * Resolved enabled steps in declaration order.
	 */
	steps: RenderGraphStep[];
	/**
	 * Enabled compute steps. These always execute before the base scene render.
	 */
	computeSteps: RenderGraphStep[];
	/**
	 * Enabled render steps. These always execute after the base scene render.
	 */
	renderSteps: RenderGraphStep[];
	/**
	 * Output slot holding final post-scene render result before presentation.
	 * Remains 'canvas' when there are no render steps.
	 */
	finalOutput: RenderPassOutputSlot;
}

/**
 * Creates a copy of RGBA clear color.
 */
function cloneClearColor(
	color: [number, number, number, number]
): [number, number, number, number] {
	return [color[0], color[1], color[2], color[3]];
}

/**
 * Builds validated render graph plan from runtime pass list.
 *
 * @param passes - Runtime passes.
 * @param defaultClearColor - Global clear color fallback.
 * @returns Resolved render graph plan.
 */
export function planRenderGraph(
	passes: AnyPass[] | undefined,
	defaultClearColor: [number, number, number, number],
	renderTargetSlots?: Iterable<string>
): RenderGraphPlan {
	const steps: RenderGraphStep[] = [];
	const computeSteps: RenderGraphStep[] = [];
	const renderSteps: RenderGraphStep[] = [];
	const declaredTargets = new Set(renderTargetSlots ?? []);
	const availableSlots = new Set<RenderPassInputSlot | RenderPassOutputSlot>(['source']);
	let finalOutput: RenderPassOutputSlot = 'canvas';
	let enabledIndex = 0;

	for (const pass of passes ?? []) {
		if (pass.enabled === false) {
			continue;
		}

		// Compute passes don't participate in slot routing
		const isCompute = 'isCompute' in pass && (pass as { isCompute?: boolean }).isCompute === true;
		if (isCompute) {
			const step: RenderGraphStep = {
				kind: 'compute',
				pass,
				input: 'source',
				output: 'source',
				needsSwap: false,
				clear: false,
				clearColor: cloneClearColor(defaultClearColor),
				preserve: true
			};
			steps.push(step);
			computeSteps.push(step);
			continue;
		}

		// After compute guard, pass is a render pass
		const rp = pass as RenderPass;
		const needsSwap = rp.needsSwap ?? true;
		const input: RenderPassInputSlot = rp.input ?? 'source';
		const output: RenderPassOutputSlot = rp.output ?? (needsSwap ? 'target' : 'source');

		if (input === 'canvas') {
			throw new Error(`Render pass #${enabledIndex} cannot read from "canvas".`);
		}

		const inputIsNamed = input !== 'source' && input !== 'target';
		if (inputIsNamed && !declaredTargets.has(input)) {
			throw new Error(`Render pass #${enabledIndex} reads unknown target "${input}".`);
		}

		const outputIsNamed = output !== 'source' && output !== 'target' && output !== 'canvas';
		if (outputIsNamed && !declaredTargets.has(output)) {
			throw new Error(`Render pass #${enabledIndex} writes unknown target "${output}".`);
		}

		if (needsSwap && (input !== 'source' || output !== 'target')) {
			throw new Error(
				`Render pass #${enabledIndex} uses needsSwap=true but does not follow source->target flow.`
			);
		}

		if (!availableSlots.has(input)) {
			throw new Error(`Render pass #${enabledIndex} reads "${input}" before it is written.`);
		}

		const clear = rp.clear ?? false;
		const clearColor = cloneClearColor(rp.clearColor ?? defaultClearColor);
		const preserve = rp.preserve ?? true;

		const step: RenderGraphStep = {
			kind: 'render',
			pass,
			input,
			output,
			needsSwap,
			clear,
			clearColor,
			preserve
		};
		steps.push(step);
		renderSteps.push(step);

		if (needsSwap) {
			availableSlots.add('target');
			availableSlots.add('source');
			finalOutput = 'source';
		} else {
			if (output !== 'canvas') {
				availableSlots.add(output);
			}
			finalOutput = output;
		}

		enabledIndex += 1;
	}

	return {
		steps,
		computeSteps,
		renderSteps,
		finalOutput
	};
}
