import {
	back,
} from "qrc:/js/lib/array_util";
import {
	articleUserDatumImpl,
} from "qrc:/js/lib/article_userdata_config";
import {
	DocXImageType,
	DocXTableCellType,
	FileType,
	ProcessType,
	TableType,
	WorkStepType,
} from "qrc:/js/lib/generated/enum";
import {
	computeWcsDimensions,
} from "qrc:/js/lib/geometry_utils";
import {
	computeDieSharingPartitions,
	computeNodeMass,
	getArticleName,
	netTubeConsumptionForVertex,
	sheetCuttingChargeWeightForVertex,
} from "qrc:/js/lib/graph_utils";
import {
	grossTubeConsumptionForVertex,
	nestingTargetBoxForVertex,
	sheetBendingWsNumBendLines,
	sheetCuttingWsContourCount,
	sheetCuttingWsContourLength,
	tubeForVertex,
} from "qrc:/js/lib/node_utils";
import {
	assert,
	bbDimensionX,
	bbDimensionY,
	defaultPngResolution,
	getKeysOfObject,
	isEqual,
	isString,
	renderDefaultPng,
} from "qrc:/js/lib/utils";
import {
	defaultSvgResolution,
	sceneForVertex,
} from "qrc:/js/lib/scene_utils";
import {
	getTable,
} from "qrc:/js/lib/table_utils";
import {
	tubeCuttingLayerPaths,
} from "qrc:/js/lib/tubecutting_util";
import {
	nodeUserDatumOrDefault,
} from "qrc:/js/lib/userdata_utils";

import {
	computeNodeTimes,
} from "./export_calc_times";
import {
	docXImageCell,
	DocXImageContent,
	getArticleMaterialName,
	getLaserSheetCuttingGasName,
	kgToString,
	mmToString,
	secondsToString,
} from "./export_utils";
import {
	computeTubeConsumptionMasses,
} from "./export_calc_costs";

interface PartData {
	articleName: string;
	externalPartNumber: string;
	externalDrawingNumber: string;
	externalRevisionNumber: string;
	material: string;
	mass: string,
	length: string;
	width: string;
	height: string;
	sheetThickness: string;
	multiplicity: string;
}

interface SheetDetails {
	type: "SheetDetails";
	dimensions: string;
	multiplicity: string;
}

interface SheetBendingDetails {
	type: "SheetBendingDetails";
	numBends: string;
	linkedArticles: string;
}

interface SheetCuttingDetails {
	type: "SheetCuttingDetails";
	contourLength: string;
	numPiercings: string;
	laserCuttingGas: string | undefined;
	chargeWeight: string;
	chargeWeightPerPiece: string;
	fixedRotations: string;
}

interface SubArticle {
	name: string;
	multiplicity: string;
}

interface JoiningDetails {
	type: "JoiningDetails";
	subArticles: SubArticle[];
}

interface SheetTappingThread {
	name: string;
	multiplicity: string;
}

interface UserDefinedDetails {
	type: "UserDefinedDetails";
	sheetTappingThreads: SheetTappingThread[];
}

interface TubeCuttingDetails {
	type: "TubeCuttingDetails";
	contourLength: string;
	numPiercings: string;
	extrusionDepth: string;
}

interface TubeDetails {
	type: "TubeDetails";
	count: string;
	consumption: string;
	chargeWeight: string;
	scrap: string;
}

type WorkStepSpecificData = SheetDetails | SheetBendingDetails | SheetCuttingDetails | JoiningDetails | UserDefinedDetails | TubeCuttingDetails | TubeDetails;

interface WorkStepData {
	processName: string;
	setupTime: string;
	unitTime: string;
	unitTimePerPiece: string;
	svg: DocXImageContent | undefined;
	png: DocXImageContent | undefined;
	workStepSpecificData: WorkStepSpecificData | undefined;
}

interface JobCardData {
	partData: PartData;
	image: DocXImageContent | undefined;
	workStepData: WorkStepData[];
}

function createPartData(vertex: Vertex): PartData {
	const dimensions = (() => {
		const assembly = wsi4.node.assembly(vertex) ?? wsi4.node.inputAssembly(vertex);
		if (assembly === undefined) {
			return undefined;
		} else {
			return computeWcsDimensions(assembly);
		}
	})();

	const multiplicity = wsi4.node.multiplicity(vertex);
	// [kg]
	const mass = computeNodeMass(vertex);
	const material = getArticleMaterialName(vertex);
	const sheetThickness = wsi4.node.sheetThickness(vertex);
	const articleUserData = wsi4.node.articleUserData(vertex);
	return {
		articleName: articleUserDatumImpl("name", articleUserData) ?? "",
		externalPartNumber: articleUserDatumImpl("externalPartNumber", articleUserData) ?? "",
		externalDrawingNumber: articleUserDatumImpl("externalDrawingNumber", articleUserData) ?? "",
		externalRevisionNumber: articleUserDatumImpl("externalRevisionNumber", articleUserData) ?? "",
		material: material === undefined ? "" : material,
		mass: mass === undefined ? "N/A" : kgToString(mass),
		length: dimensions === undefined ? "N/A" : mmToString(dimensions.x, 2),
		width: dimensions === undefined ? "N/A" : mmToString(dimensions.y, 2),
		height: dimensions === undefined ? "N/A" : mmToString(dimensions.z, 2),
		sheetThickness: sheetThickness === undefined ? "N/A" : mmToString(sheetThickness, 2),
		multiplicity: multiplicity.toFixed(0),
	};
}

function createSheetDetails(vertex: Vertex): SheetDetails {
	assert(wsi4.node.workStepType(vertex) === WorkStepType.sheet);

	const twoDimRep = wsi4.node.twoDimRep(vertex);
	if (twoDimRep === undefined) {
		// Nesting was not possible or failed
		return {
			type: "SheetDetails",
			dimensions: "",
			multiplicity: "",
		};
	}

	const bbox = nestingTargetBoxForVertex(vertex);
	assert(bbox !== undefined, "Expecting valid nesting target box");

	const dimensions: [number, number] = [
		bbDimensionX(bbox),
		bbDimensionY(bbox),
	];

	const thickness = wsi4.node.sheetThickness(vertex);
	assert(thickness !== undefined, "Expecting valid thickness");

	const numSheets = wsi4.cam.nest2.nestingDescriptors(twoDimRep)
		.reduce((acc, nestingDescriptor) => acc + wsi4.cam.nest2.nestingMultiplicity(twoDimRep, nestingDescriptor), 0);
	return {
		type: "SheetDetails",
		dimensions: mmToString(dimensions[0]) + " x " + mmToString(dimensions[1]) + " x " + mmToString(thickness),
		multiplicity: numSheets.toFixed(0),
	};
}

function linkedArticlesText(inputVertex: Vertex): string {
	assert(wsi4.node.processType(inputVertex) === ProcessType.dieBending);
	const partition = (() => {
		const partitions = computeDieSharingPartitions(wsi4.graph.vertices()
			.filter(v => wsi4.node.processType(v) === ProcessType.dieBending));
		if (partitions === undefined) {
			return undefined;
		}
		const result = partitions.find(partition => partition.some(v => isEqual(v, inputVertex)));
		assert(result !== undefined, "Expecting valid partition");
		return result;
	})();
	if (partition === undefined || partition.length === 1) {
		return "";
	}
	assert(partition.length > 1);
	return partition.filter(vertex => !isEqual(vertex, inputVertex))
		.map(vertex => getArticleName(vertex))
		.join("\n");
}

function createSheetBendingDetails(vertex: Vertex): SheetBendingDetails {
	const numBends = sheetBendingWsNumBendLines(vertex);
	return {
		type: "SheetBendingDetails",
		numBends: numBends === undefined ? "" : numBends.toFixed(0),
		linkedArticles: wsi4.node.processType(vertex) === ProcessType.dieBending ? linkedArticlesText(vertex) : "",
	};
}

function createSheetCuttingDetails(vertex: Vertex): SheetCuttingDetails {
	const cuttingLength = sheetCuttingWsContourLength(vertex);
	const numPiercings = sheetCuttingWsContourCount(vertex);
	const cuttingGasName = getLaserSheetCuttingGasName(vertex);
	const overallChargeWeight = sheetCuttingChargeWeightForVertex(vertex);
	const chargeWeightPerPiece = overallChargeWeight === undefined ? undefined : overallChargeWeight / wsi4.node.multiplicity(vertex);
	const fixedRotations = nodeUserDatumOrDefault("fixedRotations", vertex);
	return {
		type: "SheetCuttingDetails",
		contourLength: cuttingLength === undefined ? "N/A" : mmToString(cuttingLength),
		numPiercings: numPiercings === undefined ? "N/A" : numPiercings.toFixed(0),
		laserCuttingGas: cuttingGasName,
		chargeWeight: overallChargeWeight === undefined ? "N/A" : kgToString(overallChargeWeight),
		chargeWeightPerPiece: chargeWeightPerPiece === undefined ? "N/A" : kgToString(chargeWeightPerPiece),
		fixedRotations: fixedRotations.length === 0 ? "--" : fixedRotations.join(", "),
	};
}

function createJoiningDetails(vertex: Vertex): JoiningDetails {
	return {
		type: "JoiningDetails",
		subArticles: wsi4.graph.sources(vertex)
			.map(sourceVertex => ({
				multiplicity: wsi4.graph.sourceMultiplicity(sourceVertex, vertex).toFixed(0) + " x",
				name: getArticleName(sourceVertex),
			})),
	};
}

function sheetTappingThreads(vertex: Vertex): SheetTappingThread[] {
	const screwThreads = getTable(TableType.screwThread);
	const screwCountMap = new Map<string, number>();
	nodeUserDatumOrDefault("sheetTappingData", vertex)
		.forEach(sheetTappingDataEntry => {
			const screwThread = screwThreads.find(row => row.identifier === sheetTappingDataEntry.screwThreadId);
			if (screwThread === undefined) {
				wsi4.util.error("createSheetTappingDetails():  Missing screw thread table entry for id " + sheetTappingDataEntry.screwThreadId);
				return;
			} else {
				const count = screwCountMap.get(screwThread.identifier) ?? 0;
				screwCountMap.set(screwThread.identifier, count + 1);
			}
		});
	return Array.from(screwCountMap.entries())
		.map(([
			screwId,
			count,
		]): SheetTappingThread => {
			const screw = screwThreads.find(row => row.identifier === screwId);
			assert(screw !== undefined);
			return {
				name: screw.name,
				multiplicity: count.toFixed(0) + " x",
			};
		});
}

function createUserDefinedDetails(vertex: Vertex): UserDefinedDetails {
	return {
		type: "UserDefinedDetails",
		sheetTappingThreads: wsi4.node.processType(vertex) === ProcessType.sheetTapping ? sheetTappingThreads(vertex) : [],
	};
}

function createTubeCuttingDetails(vertex: Vertex): TubeCuttingDetails {
	const extrusionLength = wsi4.node.profileExtrusionLength(vertex);
	const layered = wsi4.node.layered(vertex);
	const cuttingPaths = layered === undefined ? undefined : [
		...tubeCuttingLayerPaths("cuttingOuterContour", layered),
		...tubeCuttingLayerPaths("cuttingInnerContour", layered),
	];
	const numPiercings = cuttingPaths?.length;
	const cuttingLength = cuttingPaths?.reduce((acc, path) => acc + wsi4.geo.util.pathLength(path), 0.);
	return {
		type: "TubeCuttingDetails",
		contourLength: cuttingLength === undefined ? "N/A" : mmToString(cuttingLength),
		numPiercings: numPiercings === undefined ? "N/A" : numPiercings.toFixed(0),
		extrusionDepth: mmToString(extrusionLength),
	};
}
function createTubeDetails(tubeVertex: Vertex): TubeDetails {
	const tubeCuttingVertex = (() => {
		const vertices = wsi4.graph.reachable(tubeVertex)
			.filter(vertex => wsi4.node.workStepType(vertex) === WorkStepType.tubeCutting);
		assert(vertices.length === 1, "Expecting exactly one reachable tubeCutting node");
		return vertices[0]!;
	})();

	const profileGeometry = wsi4.node.tubeProfileGeometry(tubeCuttingVertex);
	const tube = tubeForVertex(tubeCuttingVertex);
	const grossConsumption = tube === undefined ? undefined : grossTubeConsumptionForVertex(tubeCuttingVertex, tube);
	const netConsumption = tube === undefined ? undefined : netTubeConsumptionForVertex(tubeCuttingVertex, tube);
	const consumptionMasses = (() => {
		if (tube === undefined
			|| profileGeometry === undefined
			|| grossConsumption === undefined
			|| netConsumption === undefined) {
			return undefined;
		} else {
			return computeTubeConsumptionMasses(tube, profileGeometry, {
				gross: grossConsumption,
				net: netConsumption,
			});
		}
	})();
	return {
		type: "TubeDetails",
		count: grossConsumption === undefined ? "N/A" : Math.ceil(grossConsumption).toFixed(0),
		consumption: grossConsumption === undefined ? "N/A" : grossConsumption.toFixed(2),
		chargeWeight: consumptionMasses === undefined ? "N/A" : kgToString(consumptionMasses.gross),
		scrap: consumptionMasses === undefined ? "N/A" : kgToString(consumptionMasses.scrap),
	};
}

function computeWorkStepSpecificData(vertex: Vertex): WorkStepSpecificData | undefined {
	switch (wsi4.node.workStepType(vertex)) {
		case WorkStepType.sheet: return createSheetDetails(vertex);
		case WorkStepType.sheetCutting: return createSheetCuttingDetails(vertex);
		case WorkStepType.sheetBending: return createSheetBendingDetails(vertex);
		case WorkStepType.joining: return createJoiningDetails(vertex);
		case WorkStepType.userDefined: return createUserDefinedDetails(vertex);
		case WorkStepType.tubeCutting: return createTubeCuttingDetails(vertex);
		case WorkStepType.tube: return createTubeDetails(vertex);
		case WorkStepType.userDefinedBase: return undefined;
		case WorkStepType.packaging: return undefined;
		case WorkStepType.transform: return undefined;
		case WorkStepType.undefined: return undefined;
	}
}

function createWorkStepData(vertex: Vertex): WorkStepData {
	const processName = (() => {
		const processId = wsi4.node.processId(vertex);
		const process = getTable(TableType.process)
			.find(row => row.identifier === processId);
		return process === undefined ? "N/A" : process.name;
	})();

	const workStepData: WorkStepData = {
		processName: processName,
		setupTime: "",
		unitTime: "",
		unitTimePerPiece: "",
		svg: undefined,
		png: undefined,
		workStepSpecificData: computeWorkStepSpecificData(vertex),
	};
	const workStepType = wsi4.node.workStepType(vertex);
	const addTimes = workStepType === "sheetCutting" || workStepType === "sheetBending" || workStepType === "userDefined" || WorkStepType.joining;
	if (addTimes) {
		const times = computeNodeTimes(vertex);
		if (times === undefined) {
			return wsi4.throwError("Times not available");
		}
		const multiplicity = wsi4.node.multiplicity(vertex);
		// On customer demand also exporting the value in minutes only so there is a consistent unit for submitting data to thirdparty applications.
		const timeToString = (seconds: number): string => {
			const fixedUnitString = secondsToString(seconds, 2, "min");
			const variableUnitString = secondsToString(seconds, 2);
			if (fixedUnitString === variableUnitString) {
				return variableUnitString;
			} else {
				return `${variableUnitString} (${fixedUnitString})`;
			}
		};
		workStepData.setupTime = timeToString(times.setup);
		workStepData.unitTime = timeToString(times.unit);
		workStepData.unitTimePerPiece = timeToString(times.unit / multiplicity);
	}
	const assembly = (() => {
		const inputAssembly = wsi4.node.inputAssembly(vertex);
		const outputAssembly = wsi4.node.assembly(vertex);
		return outputAssembly !== undefined ? outputAssembly : inputAssembly;
	})();
	if (assembly !== undefined) {
		workStepData.png = {
			content: renderDefaultPng(assembly, defaultPngResolution()),
			imageType: DocXImageType.png,
		};
	}
	if (wsi4.node.twoDimRep(vertex) !== undefined || wsi4.node.layered(vertex) !== undefined) {
		const scene = sceneForVertex(
			vertex,
			{
				cuttingContours: true,
				engravings: true,
				bendLineShowLabels: true,
				bendLineShowAffectedSegments: true,
				tubeCuttingShowTubeContours: false,
				fontSize: 52,
			},
		);
		workStepData.svg = {
			content: wsi4.geo.util.renderScene(scene, FileType.png, {resolution: defaultSvgResolution()}),
			imageType: DocXImageType.png,
		};
	}
	return workStepData;
}

export function createJobCardData(article: Vertex[]): JobCardData | undefined {
	if (article.length === 0) {
		return undefined;
	}
	const interestingVertex = back(article);
	const assembly = (() => {
		const inputAssembly = wsi4.node.inputAssembly(interestingVertex);
		const outputAssembly = wsi4.node.assembly(interestingVertex);
		return outputAssembly !== undefined ? outputAssembly : inputAssembly;
	})();
	return {
		partData: createPartData(interestingVertex),
		workStepData: article.map(vertex => createWorkStepData(vertex)),
		image: assembly === undefined ? undefined : {
			content: renderDefaultPng(assembly, defaultPngResolution()),
			imageType: DocXImageType.png,
		},
	};
}

const partDataPlaceholder: {[index in keyof PartData]: string} = {
	articleName: "articlename",
	externalPartNumber: "articlenumber",
	externalDrawingNumber: "drawingnumber",
	externalRevisionNumber: "revisionnumber",
	material: "material",
	mass: "mass",
	length: "length",
	height: "height",
	width: "width",
	sheetThickness: "sheetthickness",
	multiplicity: "quantity",
};

const partDataTranslationKey: {[index in keyof PartData]: string} = {
	articleName: "Name",
	externalPartNumber: "part_number",
	externalDrawingNumber: "drawing_number",
	externalRevisionNumber: "revision_number",
	material: "Material",
	mass: "Mass",
	length: "Length",
	width: "Width",
	height: "Height",
	sheetThickness: "SheetThickness",
	multiplicity: "Multiplicity",
};

function createPartDataTables(partData: PartData): DocXTableCell[][][] {
	const tables: DocXTableCell[][][] = [
		[
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$partdata$",
						text: wsi4.util.translate("PartData"),
					},
				},
			],
		],
	];
	for (const key of getKeysOfObject(partData)) {
		tables.push([
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$" + partDataPlaceholder[key] + "text$",
						text: wsi4.util.translate(partDataTranslationKey[key]),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$" + partDataPlaceholder[key] + "$",
						text: partData[key],
					},
				},
			],
		]);
	}
	return tables;
}

const workStepDataPlaceholder: {[index in keyof WorkStepData]: string} = {
	processName: "processname",
	png: "png",
	svg: "svg",
	setupTime: "setuptime",
	unitTime: "unittime",
	unitTimePerPiece: "unittimeperpiece",
	workStepSpecificData: "", // not used
};

function workStepDataTranslationKey(key: keyof WorkStepData): string {
	const map: {[index in keyof WorkStepData]: string} = {
		processName: "", // not used
		png: "", // not used
		svg: "", // not used
		setupTime: wsi4.util.translate("SetupTimeAbbr"),
		unitTime: wsi4.util.translate("UnitTimeAbbr"),
		unitTimePerPiece: wsi4.util.translate("UnitTimeAbbr") + " / " + wsi4.util.translate("piece"),
		workStepSpecificData: "", // not used
	};
	return map[key];
}

function dataTables<T extends object>(data: T, tableMap: {[index in keyof T]: (value: T[index]) => DocXTableCell[][]}): DocXTableCell[][][] {
	const tables: DocXTableCell[][][] = [];
	for (const key of getKeysOfObject(data)) {
		if (key === "type") {
			continue;
		}
		const value = data[key];
		if (isString(value) && value === "") {
			// entry empty so don't show it
			continue;
		}
		tables.push(tableMap[key](value));
	}
	return tables;
}

function sheetDetailsTables(data: SheetDetails): DocXTableCell[][][] {
	const sheetDetailsTableMap: {[index in keyof SheetDetails]: (value: SheetDetails[index]) => DocXTableCell[][]} = {
		type: () => [],
		dimensions: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$sheetdimensionstext$",
						text: wsi4.util.translate("Dimensions"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$sheetdimensions$",
						text: value,
					},
				},
			],
		],
		multiplicity: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$sheetquantitytext$",
						text: wsi4.util.translate("Multiplicity"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$sheetquantity$",
						text: value,
					},
				},
			],
		],
	};
	return dataTables(data, sheetDetailsTableMap);
}

function sheetBendingDetailsTables(data: SheetBendingDetails): DocXTableCell[][][] {
	const sheetBendingDetailsTableMap: {[index in keyof SheetBendingDetails]: (value: SheetBendingDetails[index]) => DocXTableCell[][]} = {
		type: () => [],
		numBends: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$numbendstext$",
						text: wsi4.util.translate("NumBends"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$numbends$",
						text: value,
					},
				},
			],
		],
		linkedArticles: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$diebendinglinkedarticlestext$",
						text: wsi4.util.translate("linked_articles"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$diebendinglinkedarticles$",
						text: value,
					},
				},
			],
		],
	};
	return dataTables(data, sheetBendingDetailsTableMap);
}

function sheetCuttingDetailsTables(data: SheetCuttingDetails): DocXTableCell[][][] {
	const sheetCuttingDetailsTableMap: {[index in keyof SheetCuttingDetails]: (value: SheetCuttingDetails[index]) => DocXTableCell[][]} = {
		type: () => [],
		contourLength: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$contourlengthtext$",
						text: wsi4.util.translate("ContourLength"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$contourlength$",
						text: value,
					},
				},
			],
		],
		numPiercings: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$numpiercingstext$",
						text: wsi4.util.translate("NumPiercings"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$numpiercings$",
						text: value,
					},
				},
			],
		],
		laserCuttingGas: (value: string | undefined) => {
			if (value === undefined) {
				return [];
			}
			return [
				[
					{
						type: DocXTableCellType.text,
						content: {
							placeholder: "$lasersheetcuttinggastext$",
							text: wsi4.util.translate("CuttingGas"),
						},
					},
					{
						type: DocXTableCellType.text,
						content: {
							placeholder: "$lasersheetcuttinggas$",
							text: value,
						},
					},
				],
			];
		},
		chargeWeight: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$sheetcuttingchargeweighttext$",
						text: wsi4.util.translate("charge_weight_in_kg"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$sheetcuttingchargeweight$",
						text: value,
					},
				},
			],
		],
		chargeWeightPerPiece: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$sheetcuttingchargeweightperpiecetext$",
						text: wsi4.util.translate("charge_weight_in_kg_per_piece"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$sheetcuttingchargeweightperpiece$",
						text: value,
					},
				},
			],
		],
		fixedRotations: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$sheetcuttingfixedrotationstext$",
						text: wsi4.util.translate("fixed_rotations") + " [°]",
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$sheetcuttingfixedrotations$",
						text: value,
					},
				},
			],
		],
	};
	return dataTables(data, sheetCuttingDetailsTableMap);
}

function joiningDetailsTables(data: JoiningDetails): DocXTableCell[][][] {
	const joiningDetailsTableMap: {[index in keyof JoiningDetails]: (value: JoiningDetails[index]) => DocXTableCell[][]} = {
		type: () => [],
		subArticles: (articles: SubArticle[]) => articles.length === 0 ? [] : [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$subarticlestext$",
						text: wsi4.util.translate("subarticles"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$subarticles$",
						text: articles.map(article => article.multiplicity + " " + article.name).join("\n"),

					},
				},
			],
		],
	};
	return dataTables(data, joiningDetailsTableMap);
}

function userDefinedDetailsTables(data: UserDefinedDetails): DocXTableCell[][][] {
	const userDefinedDetailsTableMap: {[index in keyof UserDefinedDetails]: (value: UserDefinedDetails[index]) => DocXTableCell[][]} = {
		type: () => [],
		sheetTappingThreads: (articles: SheetTappingThread[]) => articles.length === 0 ? [] : [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$sheettappingthreadstext$",
						text: wsi4.util.translate("screw_threads"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$sheettappingthreads$",
						text: articles.map(article => article.multiplicity + " " + article.name).join("\n"),

					},
				},
			],
		],

	};
	return dataTables(data, userDefinedDetailsTableMap);
}

function tubeCuttingDetailsTables(data: TubeCuttingDetails): DocXTableCell[][][] {
	const tubeCuttingDetailsTableMap: {[index in keyof TubeCuttingDetails]: (value: TubeCuttingDetails[index]) => DocXTableCell[][]} = {
		type: () => [],
		contourLength: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$contourlengthtext$",
						text: wsi4.util.translate("ContourLength"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$contourlength$",
						text: value,
					},
				},
			],
		],
		numPiercings: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$numpiercingstext$",
						text: wsi4.util.translate("NumPiercings"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$numpiercings$",
						text: value,
					},
				},
			],
		],
		extrusionDepth: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$extrusiondepthtext$",
						text: wsi4.util.translate("extrusion_depth"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$extrusiondepth$",
						text: value,
					},
				},
			],
		],
	};
	return dataTables(data, tubeCuttingDetailsTableMap);
}

function tubeDetailsTables(data: TubeDetails): DocXTableCell[][][] {
	const tubeDetailsTableMap: {[index in keyof TubeDetails]: (value: TubeDetails[index]) => DocXTableCell[][]} = {
		type: () => [],
		count: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$tubequantitytext$",
						text: wsi4.util.translate("count"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$tubequantity$",
						text: value,
					},
				},
			],
		],
		consumption: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$tubeconsumptiontext$",
						text: wsi4.util.translate("Consumption"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$tubeconsumption$",
						text: value,
					},
				},
			],
		],
		chargeWeight: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$tubechargeweighttext$",
						text: wsi4.util.translate("charge_weight_in_kg"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$tubechargeweight$",
						text: value,
					},
				},
			],
		],
		scrap: (value: string) => [
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$tubescraptext$",
						text: wsi4.util.translate("scrap_in_kg"),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$tubescrap$",
						text: value,
					},
				},
			],
		],
	};
	return dataTables(data, tubeDetailsTableMap);
}

function workStepSpecificDataTables(data: WorkStepSpecificData | undefined): DocXTableCell[][][] {
	if (data === undefined) {
		return [];
	}
	switch (data.type) {
		case "SheetDetails": return sheetDetailsTables(data);
		case "SheetBendingDetails": return sheetBendingDetailsTables(data);
		case "SheetCuttingDetails": return sheetCuttingDetailsTables(data);
		case "JoiningDetails": return joiningDetailsTables(data);
		case "UserDefinedDetails": return userDefinedDetailsTables(data);
		case "TubeCuttingDetails": return tubeCuttingDetailsTables(data);
		case "TubeDetails": return tubeDetailsTables(data);
	}
}

function createWorkStepTables(workStep: WorkStepData): DocXTableCell[][][] {
	const tables: DocXTableCell[][][] = [];
	for (const key of getKeysOfObject(workStep)) {
		if (key === "processName") {
			tables.push([
				[
					{
						type: DocXTableCellType.text,
						content: {
							placeholder: "$" + workStepDataPlaceholder["processName"] + "$",
							text: workStep.processName,
						},
					},
				],
			]);
		} else if (key === "svg" || key === "png") {
			tables.push([ [ docXImageCell("$" + workStepDataPlaceholder[key] + "$", workStep[key]) ] ]);
		} else if (key === "workStepSpecificData") {
			for (const table of workStepSpecificDataTables(workStep[key])) {
				tables.push(table);
			}
		} else {
			assert(isString(workStep[key]));
			tables.push([
				[
					{
						type: DocXTableCellType.text,
						content: {
							placeholder: "$" + workStepDataPlaceholder[key] + "text$",
							text: workStepDataTranslationKey(key),
						},
					},
					{
						type: DocXTableCellType.text,
						content: {
							placeholder: "$" + workStepDataPlaceholder[key] + "$",
							text: workStep[key],
						},
					},
				],
			]);
		}
	}
	return tables;
}

function createWorkStepDataTable(workStepData: WorkStepData[]): DocXTableCell[][] {
	return workStepData.map(workStep => [
		{
			type: DocXTableCellType.tables,
			content: {
				tables: createWorkStepTables(workStep),
			},
		},
	]);
}

export function createJobCardDocx(templ: ArrayBuffer, jobCardData: JobCardData): ArrayBuffer | undefined {
	const d = wsi4.documentCreator.generateDocX(templ,
		[
			[
				[
					{
						type: DocXTableCellType.text,
						content: {
							placeholder: "$jobcard$",
							text: wsi4.util.translate("JobCard"),
						},
					},
				],
			],
			[ [ docXImageCell("$partimage$", jobCardData.image) ] ],
			...createPartDataTables(jobCardData.partData),
			createWorkStepDataTable(jobCardData.workStepData),
		]);
	return d.byteLength === 0 ? undefined : d;
}
