import {
	WorkStepType,
} from "qrc:/js/lib/generated/enum";
import {
	changeArticleUserData,
	changeImportMultiplicities,
	changeNodesUserData,
	changeNodeThickness,
	changeProcessIds,
	SheetMetalPartUpdate,
	toggleUpperSide,
	TubePartUpdate,
	updateSheetMetalParts,
	updateTubeParts,
} from "qrc:/js/lib/graph_manipulator";
import {
	findActiveProcess,
	workStepTypeMap,
} from "qrc:/js/lib/process";
import {
	rootIdToVertex,
} from "qrc:/js/lib/utils";
import {
	createUpdatedNodeUserData,
} from "qrc:/js/lib/userdata_utils";

import {
	createUpdatedArticleUserData,
} from "qrc:/js/lib/article_userdata_config";
import {
	RootIdWithGraphNodeWritableAttributes,
	TubeCuttingEditParams,
} from "./cli_shop_interface";

function applyBottomSideUsageChange(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const d = data.filter(da => da.attributes.flipSide !== undefined);
	if (isFilterEmptyOrNodeMissing(d)) {
		return true;
	}
	const v = rootIdToVertex(d[0]!.rootId);
	const flipSide = d[0]!.attributes.flipSide;
	if (flipSide === undefined) {
		return wsi4.throwError("Wrong type");
	}
	toggleUpperSide([ v ]);
	return true;
}

function applyTestReportChanges(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const userDataData = data.filter(d => d.attributes.testReportRequired !== undefined)
		.map((d: RootIdWithGraphNodeWritableAttributes) => {
			if (d.attributes.testReportRequired === undefined) {
				return wsi4.throwError("Filter went wrong");
			}
			return {
				vertex: rootIdToVertex(d.rootId),
				userData: createUpdatedNodeUserData("testReportRequired", rootIdToVertex(d.rootId), d.attributes.testReportRequired),
			};
		});
	if (userDataData.length > 0 && !changeNodesUserData(userDataData)) {
		wsi4.util.error("Error changing userdata");
		return false;
	}
	return true;
}

function applySheetMetalPartChanges(data: readonly Readonly<RootIdWithGraphNodeWritableAttributes>[]): boolean {
	const updates: SheetMetalPartUpdate[] = data.filter(d => d.attributes.sheetMetalPartEditParams !== undefined || d.attributes.fixedRotations !== undefined)
		.map(d => {
			const s: SheetMetalPartUpdate = {
				vertex: rootIdToVertex(d.rootId),
			};

			if (d.attributes.sheetMetalPartEditParams !== undefined) {
				s.sheetMaterialId = d.attributes.sheetMetalPartEditParams.sheetMaterialId;
				s.processIdSheetCutting = d.attributes.sheetMetalPartEditParams.processIdSheetCutting;
			}

			if (d.attributes.fixedRotations !== undefined) {
				s.fixedRotations = d.attributes.fixedRotations;
			}

			return s;
		});
	if (updates.length > 0) {
		updateSheetMetalParts(updates);
	}
	return true;
}

function applyThicknessChanges(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const thicknessData = data.filter(d => d.attributes.thickness !== undefined);
	return !thicknessData.reduce((res: boolean, t) => {
		const vertex = wsi4.node.vertexFromRootId(t.rootId);
		if (vertex !== undefined) {
			if (t.attributes.thickness === undefined) {
				return wsi4.throwError("Thickness undefined");
			}
			changeNodeThickness(vertex, t.attributes.thickness);
		}
		return res || false;
	}, false);
}

function isFilterEmptyOrNodeMissing(d: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	if (d.length > 1) {
		return wsi4.throwError("Attributes only changeable for one node at a time");
	}
	if (d.length === 0) {
		return true;
	}
	if (wsi4.node.vertexFromRootId(d[0]!.rootId) === undefined) {
		// vertex not in graph anymore
		return true;
	}
	return false;
}

function applyImportAttributesChange(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const importAttributesData = data.reduce((u: Array<VertexWithMultiplicity>, d: RootIdWithGraphNodeWritableAttributes) => {
		if (d.attributes.importMultiplicity !== undefined) {
			u.push({
				vertex: rootIdToVertex(d.rootId),
				multiplicity: d.attributes.importMultiplicity,
			});
		}
		return u;
	}, []);
	if (importAttributesData.length > 0 && !changeImportMultiplicities(importAttributesData)) {
		wsi4.util.error("Error changing importAttributes");
		return false;
	}
	return true;
}

function applyArticleDenominationChanges(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const userDataData = data.filter(d => d.attributes.articleName !== undefined
		|| d.attributes.articleExternalPartNumber !== undefined
		|| d.attributes.articleExternalDrawingNumber !== undefined
		|| d.attributes.articleExternalRevisionNumber !== undefined)
		.map((d: RootIdWithGraphNodeWritableAttributes) => {
			let articleUserData = wsi4.node.articleUserData(rootIdToVertex(d.rootId));
			if (d.attributes.articleName !== undefined) {
				articleUserData = createUpdatedArticleUserData("name", d.attributes.articleName, articleUserData);
			}
			if (d.attributes.articleExternalPartNumber !== undefined) {
				articleUserData = createUpdatedArticleUserData("externalPartNumber", d.attributes.articleExternalPartNumber, articleUserData);
			}
			if (d.attributes.articleExternalDrawingNumber !== undefined) {
				articleUserData = createUpdatedArticleUserData("externalDrawingNumber", d.attributes.articleExternalDrawingNumber, articleUserData);
			}
			if (d.attributes.articleExternalRevisionNumber !== undefined) {
				articleUserData = createUpdatedArticleUserData("externalRevisionNumber", d.attributes.articleExternalRevisionNumber, articleUserData);
			}
			return {
				vertex: rootIdToVertex(d.rootId),
				userData: articleUserData,
			};
		});

	if (userDataData.length === 0) {
		return true;
	}

	changeArticleUserData(userDataData);
	return true;
}

function applyTubeCuttingChanges(allData: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const relevantData = allData.filter(data => data.attributes.tubeCuttingEditParams !== undefined);
	if (relevantData.length === 0) {
		return true;
	}

	const args = relevantData.map((data): TubePartUpdate => {
		const vertex = rootIdToVertex(data.rootId);
		const attributes = data.attributes.tubeCuttingEditParams as TubeCuttingEditParams;
		return {
			vertex: vertex,
			processId: attributes.processId,
			tubeMaterialId: attributes.tubeMaterialId,
			tubeSpecificationId: attributes.tubeSpecificationId,
		};
	});
	updateTubeParts(args);
	return true;
}

function applyCommentChanges(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const userDataData = data.reduce((u: Array<VertexWithUserData>, d: RootIdWithGraphNodeWritableAttributes) => {
		if (d.attributes.comment !== undefined) {
			u.push({
				vertex: rootIdToVertex(d.rootId),
				userData: createUpdatedNodeUserData("comment", rootIdToVertex(d.rootId), d.attributes.comment),
			});
		}
		return u;
	}, []);
	if (userDataData.length > 0 && !changeNodesUserData(userDataData)) {
		wsi4.util.error("Error changing userdata");
		return false;
	}
	return true;
}

function extractDataForVerticesInGraph(data: Array<RootIdWithGraphNodeWritableAttributes>): Array<RootIdWithGraphNodeWritableAttributes> {
	return data.filter(d => wsi4.node.vertexFromRootId(d.rootId) !== undefined);
}

function applyAttachmentChanges(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const userDataData = data.reduce((u: Array<VertexWithUserData>, d: RootIdWithGraphNodeWritableAttributes) => {
		if (d.attributes.attachments !== undefined) {
			u.push({
				vertex: rootIdToVertex(d.rootId),
				userData: createUpdatedNodeUserData("attachments", rootIdToVertex(d.rootId), d.attributes.attachments),
			});
		}
		return u;
	}, []);
	if (userDataData.length > 0 && !changeNodesUserData(userDataData)) {
		wsi4.util.error("Error changing userdata");
		return false;
	}
	return true;
}

function applyForceSheetChange(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	type Tuple = {
		vertex: Vertex;
		processId: string | null,
	};
	const vwptds: Tuple[] = data.filter(d => d.attributes.forceSheetMetalPart !== undefined)
		.map((d): Tuple | undefined => {
			const vertex = rootIdToVertex(d.rootId);
			if (d.attributes.forceSheetMetalPart) {
				const availableWsts = wsi4.node.checkWorkStepAvailability(vertex, [
					WorkStepType.sheetBending,
					WorkStepType.sheetCutting,
				]);
				if (availableWsts.length === 0) {
					// Not expected but handled gracefully
					return undefined;
				}
				const process = findActiveProcess(p => workStepTypeMap[p.type] === availableWsts[0]);
				if (process === undefined) {
					// Not expected but handled gracefully
					return undefined;
				}
				return {
					vertex: vertex,
					processId: process.identifier,
				};
			} else {
				return {
					vertex: vertex,
					processId: null,
				};
			}
		})
		.filter((obj): obj is Tuple => obj !== undefined);
	if (vwptds.length > 0) {
		return changeProcessIds(vwptds);
	}
	return true;
}

export function applyGraphNodeWritableAttributesToRootIds(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	// FIXME return which vertices didn't work instead just using one flag (see #1649)
	// NOTE: Changing the order of these functions could break the shop
	const changeFunctions = [
		applyCommentChanges,
		applyArticleDenominationChanges,
		applyAttachmentChanges,
		applyTestReportChanges,
		applyThicknessChanges,
		applySheetMetalPartChanges,
		applyBottomSideUsageChange,
		applyTubeCuttingChanges,
		applyImportAttributesChange,
		applyForceSheetChange,
	];
	return changeFunctions.reduce((success: boolean, f) => {
		success = success && f(extractDataForVerticesInGraph(data));
		return success;
	}, true);
}
