import {
	TableType,
	TubeProfileGeometryType,
	WorkStepType,
} from "qrc:/js/lib/generated/enum";
import {
	isTubeProfileGeometryCircular,
	isTubeProfileGeometryRectangular,
} from "qrc:/js/lib/generated/typeguard";
import {
	addConstAssemblyWithProcess,
	changeNodeProcessId,
	changeNodesUserData,
	changeNodeUserData,
} from "./graph_manipulator";
import {
	createCircTube,
	createRectTube,
} from "./part_creation";
import {
	computeOrderedProcesses,
} from "./process";
import {
	getTable,
	parseTubeProfileGeometry,
} from "./table_utils";
import {
	createUpdatedNodeUserData,
	isCompatibleToNodeUserDataEntry,
} from "./userdata_utils";
import {
	assert,
	isEqual,
} from "./utils";

/**
 * Change material of all nodes that contain this UserDataEntry
 *
 * @param sheetMaterial The new material
 * @returns true if successful
 */
export function changeMaterialGlobally(sheetMaterial: SheetMaterial): boolean {
	return changeNodesUserData(wsi4.graph.vertices()
		.filter(vertex => isCompatibleToNodeUserDataEntry("sheetMaterial", vertex))
		.map(vertex => ({
			vertex: vertex,
			userData: createUpdatedNodeUserData("sheetMaterial", vertex, sheetMaterial),
		})));
}

/**
 * Add a user-defined tube node to the current graph
 *
 * Pre-condition:  tubeId is valid
 * Pre-condition:  tube's profile-id is valid
 * Pre-condition:  There is an active process of type tubeCutting
 *
 * @param tubeId Id of the associated tube
 * @param dimX Target length of the underlying tube
 * @param name Target name of the underlying assembly
 * @param processId Id of the target process (if any)
 * @returns True if (1) node has been added successfully and (2) user data have been updated successfully
 */
export function addUserDefinedTubeNode(tubeId: string, dimX: number, name: string, processId?: string): boolean {
	const tube = getTable(TableType.tube)
		.find(row => row.identifier === tubeId);
	assert(tube !== undefined, "Tube identifier invalid:  " + tubeId);

	const assembly = (() => {
		const profile = getTable(TableType.tubeProfile)
			.find(row => row.identifier === tube.tubeProfileId);
		assert(profile !== undefined, "Expecting matching profile");

		const [
			type,
			geometry,
		] = parseTubeProfileGeometry(profile.geometryJson);

		switch (type) {
			case TubeProfileGeometryType.circular: {
				assert(isTubeProfileGeometryCircular(geometry), "Tube profile geometry inconsistent");
				return createCircTube(geometry, dimX, name);

			}
			case TubeProfileGeometryType.rectangular: {
				assert(isTubeProfileGeometryRectangular(geometry), "Tube profile geometry inconsistent");
				return createRectTube(geometry, dimX, name);

			}
		}
	})();

	const tubeMaterial = getTable(TableType.tubeMaterial)
		.find(row => row.identifier === tube.tubeMaterialId);
	assert(tubeMaterial !== undefined, "Tables inconsistent");

	const tubeSpecification = getTable(TableType.tubeSpecification)
		.find(row => row.identifier === tube.tubeSpecificationId);
	assert(tubeSpecification !== undefined, "Tables inconsistent");

	const processMapping = getTable(TableType.tubeCuttingProcessMapping)
		.find(row => row.tubeMaterialId === tubeMaterial.identifier && (processId === undefined || row.processId === processId));
	assert(processMapping !== undefined, "Expecting at least one valid tube cutting process mapping");

	const process = getTable(TableType.process)
		.find(row => row.identifier === processMapping.processId);
	assert(process !== undefined, "Expecting associated process");

	const vertex = addConstAssemblyWithProcess(
		assembly,
		process.type,
		process.identifier,
	);
	if (vertex === undefined) {
		return false;
	}
	assert(wsi4.node.workStepType(vertex) === WorkStepType.tubeCutting, "Expecting tubeCutting for generated tube node");

	let userData = wsi4.node.userData(vertex);
	userData = createUpdatedNodeUserData("tubeMaterial", vertex, tubeMaterial, userData);
	userData = createUpdatedNodeUserData("tubeSpecification", vertex, tubeSpecification, userData);

	return changeNodeUserData(vertex, userData);
}

export function enforceSheetParts(vertices: Vertex[]): void {
	vertices.map(vertex => {
		const availableWsts = wsi4.node.checkWorkStepAvailability(vertex, [
			WorkStepType.sheetBending,
			WorkStepType.sheetCutting,
		]);
		if (availableWsts.length === 0) {
			return wsi4.throwError("Workstep must be convertible to sheetBending or sheetCutting");
		}
		return {
			rootId: wsi4.node.rootId(vertex),
			workStepType: availableWsts[0]!,
		};
	})
		.forEach((datum): void => {
			const vertex = wsi4.node.vertexFromRootId(datum.rootId);
			if (vertex === undefined) {
				return wsi4.throwError("Vertex invalid");
			}
			const pid = computeOrderedProcesses(datum.workStepType)
				.filter(p => p.active);
			if (pid.length === 0) {
				return wsi4.throwError("No process available for workStepType " + datum.workStepType);
			}
			changeNodeProcessId(vertex, pid[0]!.identifier, true);
		});
}

export interface ChangeNodeProcessAndUserDataArg {
	vertex: Vertex
	processId: string;
	userData: StringIndexedInterface;
	forced?: boolean;
}

export function changeNodesProcessAndUserData(vertexArgs: readonly Readonly<ChangeNodeProcessAndUserDataArg>[]): boolean {
	const rootIdArgs = vertexArgs.map(arg => ({
		rootId: wsi4.node.rootId(arg.vertex),
		processId: arg.processId,
		userData: arg.userData,
		forced: arg.forced,
	}));

	const rootIdsWithChangedProcess: GraphNodeRootId[] = [];
	for (const arg of rootIdArgs) {
		const vertex = wsi4.node.vertexFromRootId(arg.rootId);
		assert(vertex !== undefined, "Expecting valid vertex");
		const forced = arg.forced ?? false;
		const success = changeNodeProcessId(vertex, arg.processId, forced);
		if (success) {
			rootIdsWithChangedProcess.push(arg.rootId);
		}
	}
	const success0 = rootIdsWithChangedProcess.length === vertexArgs.length;

	const verticesWithUserData = rootIdArgs.filter(arg => rootIdsWithChangedProcess.some(rootId => isEqual(arg.rootId, rootId)))
		.map(arg => {
			const vertex = wsi4.node.vertexFromRootId(arg.rootId);
			assert(vertex !== undefined, "Expecting valid vertex");
			return {
				vertex: vertex,
				userData: arg.userData,
			};
		});
	const success1 = changeNodesUserData(verticesWithUserData);

	return success0 && success1;
}
