// Note: All functions in this module must conform to DocumentGraphHandler's script engine API.
import {
	tubeProfileForVertex,
} from "qrc:/js/lib/node_utils";
import {
	InitialValueNodeUserDataContext,
	insertInitialValuesIfMissingImpl,
} from "qrc:/js/lib/userdata_initial_values";
import {
	NodeUserDataContext,
} from "qrc:/js/lib/userdata_config";
import {
	ProcessType,
	TableType,
	WorkStepType,
} from "qrc:/js/lib/generated/enum";
import {
	assert, assertDebug,
} from "qrc:/js/lib/utils";
import {
	computeInitialDeducedData,
} from "qrc:/js/lib/deduceddata_utils";
import {
	// Use of this function in this context is considered safe.
	// The graph related assumptions are explained on the call site.
	nodeUserDatum,
} from "qrc:/js/lib/userdata_utils";
import {
	computeOrderedProcesses,
	isAvailableProcess,
} from "qrc:/js/lib/process";
import {
	getTable,
	tubeProfileForGeometry,
} from "qrc:/js/lib/table_utils";

/**
 * For now the process selection is limited to find a process that matches
 * all constraints w.r.t. tube availability and tube cutting parameter
 * consistency.  In case there is no process that matches all constraints
 * a default value is returned with the assumption, that certain constraints
 * will be violated later on.
 */
function computeInitialTubeCuttingProcess(vertex: Vertex): Process {
	const processTable = getTable(TableType.process);
	const tubeCuttingProcesses = processTable.filter(row => row.type === ProcessType.tubeCutting
								&& isAvailableProcess(row, processTable))
		.sort((lhs, rhs) => lhs.identifier < rhs.identifier ? -1 : lhs.identifier === rhs.identifier ? 0 : 1);

	assert(tubeCuttingProcesses.length !== 0, "Expecting at least one available tube cutting process at this point");
	const fallBackProcess = tubeCuttingProcesses[0]!;

	const profileGeometry = wsi4.node.tubeProfileGeometry(vertex);
	if (profileGeometry === undefined) {
		return fallBackProcess;
	}

	const profileExtrusionLength = wsi4.node.profileExtrusionLength(vertex);
	const profile = tubeProfileForGeometry(profileGeometry);
	const matchingTubes = getTable(TableType.tube)
		.filter(row => profileExtrusionLength <= row.dimX && (profile === undefined || row.tubeProfileId === profile.identifier));

	// Note: There might be more than one Process per TubeCuttingProcess
	// so applying sort as long as there is no logic to further refine the selection.
	const tubeCuttingProcessMappings = getTable(TableType.tubeCuttingProcessMapping)
		.filter(mapping => tubeCuttingProcesses.some(process => mapping.processId === process.identifier))
		.filter(mapping => matchingTubes.some(tube => tube.tubeMaterialId === mapping.tubeMaterialId))
		.sort((lhs, rhs) => lhs.processId < rhs.processId ? -1 : lhs.processId === rhs.processId ? 0 : 1);
	if (tubeCuttingProcessMappings.length === 0) {
		return fallBackProcess;
	}

	const process = tubeCuttingProcesses.find(row => row.identifier === tubeCuttingProcessMappings[0]!.processId);
	assert(process !== undefined);

	return process;
}

// General case:
// For now this function resembles the behaviour of the old implementation in DocumentGraphHandler.
// => returning the most general active Processes that matches the underlying WorkStepType.
// This can be refined in the future e.g. by taking certain process constraints into account.
//
// Special case forced WorkStepType / Process:
// Process is pre-defined.  Using this process
function computeInitialProcess(vertex: Vertex): Process | undefined {
	if (wsi4.node.processType(vertex) === ProcessType.undefined) {
		// This is assumed to be called in the *post* processing hook of createWorkStep.
		// Hence, the WorkStepType must be up to date at this point.
		const workStepType = wsi4.node.workStepType(vertex);

		// For WST userDefined the "automatic" process look-up logic does not apply.
		// Instead, the user *must* pre-define the Process.
		// This special case is required as long as "automatic" process detection is
		// an available option in the Process selection dialog for nodes of type
		// `userDefined`.
		if (workStepType === WorkStepType.userDefined) {
			return undefined;
		} else if (workStepType === WorkStepType.tubeCutting) {
			return computeInitialTubeCuttingProcess(vertex);
		} else {
			const processes = computeOrderedProcesses(workStepType)
				.filter(p => p.active);

			// E.g. in case of an inactive branch in the Process tree, the associated WorkStepType
			// should not have been considered an option prior to this script hook in the first place.
			assert(processes.length > 0, "Expecting at least one valid process.");
			return processes[0];
		}
	} else {
		assertDebug(() => wsi4.node.processId(vertex).length > 0, "Expecting valid process id");
		const result = getTable(TableType.process)
			.find(row => row.identifier === wsi4.node.processId(vertex));
		assert(result !== undefined, "Expecting valid associated process for forced process id");
		return result;
	}
}

function initialValueContextForVertex(vertex: Vertex, initialProcess?: Readonly<Process>): InitialValueNodeUserDataContext {
	switch (wsi4.node.workStepType(vertex)) {
		case WorkStepType.sheetBending: {
			return {
				type: "sheetBending",
				sheetThickness: () => {
					const thickness = wsi4.node.sheetThickness(vertex);
					assert(thickness !== undefined, "Expecting valid sheet thickness");
					return thickness;
				},
			};
		}
		case WorkStepType.sheetCutting: {
			assert(initialProcess !== undefined, "Expecting valid Process at this point");
			return {
				type: "sheetCutting",
				processId: () => initialProcess.identifier,
				multiplicity: () => wsi4.node.multiplicity(vertex),
				sheetThickness: () => {
					const sheetThickness = wsi4.node.sheetThickness(vertex);
					assert(sheetThickness !== undefined, "Expecting valid sheet thickness");
					return sheetThickness;
				},
				twoDimRep: () => {
					const twoDimRep = wsi4.node.twoDimRep(vertex);
					assert(twoDimRep !== undefined, "Expecting valid twoDimRep");
					return twoDimRep;
				},
				sheetMaterialId: () => {
					const targets = wsi4.graph.targets(vertex);
					if (targets.length === 1 && wsi4.node.workStepType(targets[0]!) === WorkStepType.sheetBending) {
						// In this case the respective target node's createWorkStep post processing step
						// has already been completed.  Hence, the respective default material has already
						// been computed and querrying this value here should work in any case.
						return nodeUserDatum("sheetMaterialId", targets[0]!);
					} else {
						return undefined;
					}
				},
			};
		}
		case WorkStepType.tubeCutting: {
			return {
				type: "tubeCutting",
				tubeProfile: () => tubeProfileForVertex(vertex),
				profileExtrusionLength: () => wsi4.node.profileExtrusionLength(vertex),
			};
		}
		case WorkStepType.packaging:
		case WorkStepType.joining:
		case WorkStepType.userDefined:
		case WorkStepType.userDefinedBase:
		case WorkStepType.transform:
		case WorkStepType.sheet:
		case WorkStepType.tube:
		case WorkStepType.undefined: {
			return {
				type: "default",
			};
		}
	}
}

export function createWorkStep(vertex: Vertex): PrivatePostProcessingResultCreateWorkStep {
	const initialProcess = computeInitialProcess(vertex);
	const nodeUserDataContext: Readonly<NodeUserDataContext> = {
		workStepType: wsi4.node.workStepType(vertex),
		processType: initialProcess === undefined ? ProcessType.undefined : initialProcess.type,
		isInitialNode: wsi4.node.isInitial(vertex),
	};
	const initialValueContext = initialValueContextForVertex(vertex, initialProcess);
	const currentNodeUserData = wsi4.node.userData(vertex);

	// XXX
	// When setting articleUserData here be aware that this might wipe an existing
	// node's article UserData in case a workStep is reset to "createWorkStep".
	// (E.g. when changing the thickness of a flat sheet metal part.)
	return {
		processType: initialProcess === undefined ? ProcessType.undefined : initialProcess.type,
		processId: initialProcess === undefined ? "" : initialProcess.identifier,
		nodeUserData: insertInitialValuesIfMissingImpl(
			nodeUserDataContext,
			initialValueContext,
			currentNodeUserData,
		),
	};
}

export function updateWorkStep(): undefined {
	return undefined;
}

export function createSources(vertex: Vertex): PrivatePostProcessingResultCreateSourcesSheetCutting|undefined {
	if (wsi4.node.workStepType(vertex) === WorkStepType.sheetCutting) {
		// fill deduced data of sheet source of node
		const sources = wsi4.graph.sources(vertex);
		assert(sources.length === 1, "Wrong number of sources");
		assertDebug(() => wsi4.node.workStepType(sources[0]!) === WorkStepType.sheet, "Wrong workstepType");
		return {
			source: sources[0]!,
			deducedDataOfSource: computeInitialDeducedData(sources[0]!, vertex),
		};

	}
	return undefined;
}
