import type {
	INodeExecutionData,
	IPairedItemData,
	IRunExecutionData,
	ITaskData,
} from 'n8n-workflow';

/*
 * These functions do not cover all possible edge cases for backtracking through workflow run data.
 * They are designed to work for a simple and linear workflow execution.
 * If the workflow has branches or complex execution paths, additional logic may be needed.
 * We should follow up on this and improve the logic in the future.
 */

/*
 * If we cannot backtrack correctly, we return undefined to fallback to the current paired item behavior
 * failing in these functions will cause the parent workflow to fail
 */

/**
 * This function retrieves the previous task data for a given task in the workflow run data.
 * Until there is no more source set
 */
export function previousTaskData(
	runData: IRunExecutionData['resultData']['runData'],
	currentRunData: ITaskData,
): ITaskData | undefined {
	const nextNodeName = currentRunData.source?.[0]?.previousNode;
	if (!nextNodeName) {
		return undefined; // No next node
	}

	const nextRunData = runData[nextNodeName];
	if (!nextRunData || nextRunData.length === 0) {
		// We don't expect this case to happen in practice, but if for some reason it happens, we fallback to undefined
		return undefined; // No run data for the next node
	}

	const nextRunIndex = currentRunData.source?.[0]?.previousNodeRun ?? 0;

	return nextRunData[nextRunIndex]; // Return the first run data for the next node
}

export function findPairedItemThroughWorkflowData(
	workflowRunData: IRunExecutionData,
	item: INodeExecutionData,
	itemIndex: number,
): IPairedItemData | IPairedItemData[] | number | undefined {
	// The provided item is already the item of the last node executed in this workflow run
	// So the item.pairedItem is the paired item of the last node executed and is therefore referencing
	// a node in the previous task data

	const currentNodeName = workflowRunData.resultData.lastNodeExecuted;
	if (!currentNodeName) {
		// If no node name is available, then we don't know where to start backtracking
		return undefined;
	}

	// This is the run data of the last node executed in the workflow run
	const runData = workflowRunData.resultData.runData[currentNodeName];

	if (!runData) {
		// No run data available for the last node executed
		return undefined;
	}

	// Since we are backtracking through the workflow, we start with the last run data
	const runIndex = runData.length - 1;

	const taskData = runData[runIndex];

	if (!taskData) {
		// If no run data is available, then the workflow did not run at all
		return undefined;
	}

	// Now we are getting the second last task data, because our initial pairedItem points to this.
	let runDataItem = previousTaskData(workflowRunData.resultData.runData, taskData);

	let pairedItem = item.pairedItem;

	// move the runDataItem to the previous node in the in the workflow execution data
	// and find the paired item of the current item in the previous task data
	// We do this until we reach the first task data of the workflow run

	while (runDataItem !== undefined) {
		// We find the output items for the current run data item
		const nodeInformationArray = runDataItem.data?.['main'];

		// We find and fallback to 0 for the input index and item index
		// The input index is the run the node was executed in case it was executed multiple times
		// The item index is the index of the paired item we are looking for
		let inputIndex = 0;
		let nodeIndex = itemIndex;
		if (typeof pairedItem === 'object') {
			inputIndex = (pairedItem as IPairedItemData).input ?? 0;
			nodeIndex = (pairedItem as IPairedItemData).item ?? itemIndex;
		} else if (typeof pairedItem === 'number') {
			// If the paired item is a number, we use it as the node index
			nodeIndex = pairedItem;
			// and fallback to 0 for the input index
			inputIndex = 0;
		}

		// We found the paired item of the current run data item, this points to the node in the previous task data
		pairedItem = nodeInformationArray?.[inputIndex]?.[nodeIndex]?.pairedItem;

		// We move the runDataItem to the previous task data
		runDataItem = previousTaskData(workflowRunData.resultData.runData, runDataItem);
	}

	// This is the paired item that was in the first task data when the workflow was executed
	return pairedItem;
}
