import {
	DocumentAlignment,
	DocumentImageType,
	DocumentItemType,
	ProcessType,
	TableType,
	WorkStepType,
} from "qrc:/js/lib/generated/enum";
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,
	isEqual,
} from "qrc:/js/lib/utils";
import {
	nodeUserDatumOrDefault,
} from "qrc:/js/lib/userdata_utils";
import {
	computeWcsDimensions,
} from "qrc:/js/lib/geometry_utils";
import {
	getTable,
} from "qrc:/js/lib/table_utils";
import {
	tubeCuttingLayerPaths,
} from "qrc:/js/lib/tubecutting_util";
import {
	articleUserDatumImpl,
} from "qrc:/js/lib/article_userdata_config";
import {back} from "../lib/array_util";
import {
	computeTubeConsumptionMasses,
} from "./export_calc_costs";

import {
	computeNodeTimes,
} from "./export_calc_times";
import {
	createCommentOnDemand,
	createHeading,
	createImageItem,
	createSeparator,
	createSpacerItem,
	createTitle,
	getArticleSheetMaterialName,
	getLaserSheetCuttingGasName,
	kgToString,
	mmToString,
	secondsToString,
} from "./export_utils";

function createArticleOverview(vertex: Vertex): Array<Array<DocumentItem>> {
	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 = getArticleSheetMaterialName(vertex);
	const sheetThickness = wsi4.node.sheetThickness(vertex);
	const articleUserData = wsi4.node.articleUserData(vertex);
	const tableItem = {
		type: DocumentItemType.table,
		content: {
			width: 5,
			alignment: DocumentAlignment.left,
			columnWidths: [
				50,
				50,
			],
			columnHeaders: [],
			rows: [
				[
					{
						text: wsi4.util.translate("Name"),
						alignment: DocumentAlignment.left,
					},
					{
						text: articleUserDatumImpl("name", articleUserData) ?? "",
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("part_number"),
						alignment: DocumentAlignment.left,
					},
					{
						text: articleUserDatumImpl("externalPartNumber", articleUserData) ?? "",
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("drawing_number"),
						alignment: DocumentAlignment.left,
					},
					{
						text: articleUserDatumImpl("externalDrawingNumber", articleUserData) ?? "",
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("revision_number"),
						alignment: DocumentAlignment.left,
					},
					{
						text: articleUserDatumImpl("externalRevisionNumber", articleUserData) ?? "",
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("Material"),
						alignment: DocumentAlignment.left,
					},
					{
						text: material === undefined ? wsi4.util.translate("Unknown") : material,
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("Mass"),
						alignment: DocumentAlignment.left,
					},
					{
						text: mass === undefined ? wsi4.util.translate("Unknown") : kgToString(mass),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("Length"),
						alignment: DocumentAlignment.left,
					},
					{
						text: dimensions === undefined ? "N/A" : mmToString(dimensions.x, 2),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("Width"),
						alignment: DocumentAlignment.left,
					},
					{
						text: dimensions === undefined ? "N/A" : mmToString(dimensions.y, 2),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("Height"),
						alignment: DocumentAlignment.left,
					},
					{
						text: dimensions === undefined ? "N/A" : mmToString(dimensions.z, 2),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("SheetThickness"),
						alignment: DocumentAlignment.left,
					},
					{
						text: sheetThickness === undefined ? "N/A" : mmToString(sheetThickness, 2),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("Multiplicity"),
						alignment: DocumentAlignment.left,
					},
					{
						text: String(multiplicity),
						alignment: DocumentAlignment.right,
					},
				],
			],
		},
	};

	return [
		createHeading(wsi4.util.translate("PartData"), 3),
		[
			tableItem,
			createSpacerItem(3),
			createImageItem(vertex, DocumentImageType.png, 4),
		],
	];
}

function createSheetWsDetails(vertex: Vertex): Array<DocumentTableRow> {
	const twoDimRep = wsi4.node.twoDimRep(vertex);
	if (twoDimRep === undefined) {
		// Nesting was not possible or failed
		return [];
	}

	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 [
		[
			{
				text: wsi4.util.translate(wsi4.node.processType(vertex)),
				alignment: DocumentAlignment.left,
			},
			{
				text: /* spacer */ "",
				alignment: DocumentAlignment.right,
			},
		],
		[
			{
				text: wsi4.util.translate("Dimensions"),
				alignment: DocumentAlignment.left,
			},
			{
				text: mmToString(dimensions[0]) + " x " + mmToString(dimensions[1]) + " x " + mmToString(thickness),
				alignment: DocumentAlignment.right,
			},
		],
		[
			{
				text: wsi4.util.translate("Multiplicity"),
				alignment: DocumentAlignment.left,
			},
			{
				text: numSheets.toFixed(0),
				alignment: DocumentAlignment.right,
			},
		],
		[
			{
				text: /* spacer */ "",
				alignment: DocumentAlignment.left,
			},
			{
				text: /* spacer */ "",
				alignment: DocumentAlignment.left,
			},
		],
	];
}

function createLaserSheetCuttingDetails(vertex: Vertex): DocumentTableRow {
	const cuttingGasName = getLaserSheetCuttingGasName(vertex);
	return [
		{
			text: wsi4.util.translate("CuttingGas"),
			alignment: DocumentAlignment.left,
		},
		{
			text: cuttingGasName,
			alignment: DocumentAlignment.right,
		},
	];
}

function createSheetCuttingWsDetails(vertex: Vertex): Array<DocumentTableRow> {
	const cuttingLength = sheetCuttingWsContourLength(vertex);
	const numPiercings = sheetCuttingWsContourCount(vertex);
	const overallChargeWeight = sheetCuttingChargeWeightForVertex(vertex);
	const chargeWeightPerPiece = overallChargeWeight === undefined ? undefined : overallChargeWeight / wsi4.node.multiplicity(vertex);
	const fixedRotations = nodeUserDatumOrDefault("fixedRotations", vertex);
	return [
		[
			{
				text: wsi4.util.translate("ContourLength"),
				alignment: DocumentAlignment.left,
			},
			{
				text: cuttingLength === undefined ? "N/A" : mmToString(cuttingLength),
				alignment: DocumentAlignment.right,
			},
		],
		[
			{
				text: wsi4.util.translate("NumPiercings"),
				alignment: DocumentAlignment.left,
			},
			{
				text: numPiercings,
				alignment: DocumentAlignment.right,
			},
		],
		[
			{
				text: wsi4.util.translate("charge_weight_in_kg"),
				alignment: DocumentAlignment.left,
			},
			{
				text: overallChargeWeight === undefined ? "N/A" : kgToString(overallChargeWeight),
				alignment: DocumentAlignment.right,
			},
		],
		[
			{
				text: wsi4.util.translate("charge_weight_in_kg_per_piece"),
				alignment: DocumentAlignment.left,
			},
			{
				text: chargeWeightPerPiece === undefined ? "N/A" : kgToString(chargeWeightPerPiece),
				alignment: DocumentAlignment.right,
			},
		],
		[
			{
				text: wsi4.util.translate("fixed_rotations") + " [°]",
				alignment: DocumentAlignment.left,
			},
			{
				text: fixedRotations.length === 0 ? "--" : fixedRotations.join(", "),
				alignment: DocumentAlignment.right,
			},
		],
		createLaserSheetCuttingDetails(vertex),
	];
}

function createDieBendingDetails(inputVertex: Vertex): DocumentTableRow[] {
	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) {
		return [];
	}
	if (partition.length > 1) {
		const result: DocumentTableRow[] = [
			[
				{
					text: wsi4.util.translate("linked_articles"),
					alignment: DocumentAlignment.left,
				},
				{
					text: "",
					alignment: DocumentAlignment.right,
				},
			],
		];
		result.push(...partition.filter(vertex => !isEqual(vertex, inputVertex))
			.map((vertex): DocumentTableRow => {
				const name = getArticleName(vertex);
				return [
					{
						text: "",
						alignment: DocumentAlignment.left,
					},
					{
						text: name === undefined ? "N/A" : name,
						alignment: DocumentAlignment.right,
					},
				];
			}));
		return result;
	} else {
		return [];
	}
}

function createSheetBendingWsDetails(vertex: Vertex): Array<DocumentTableRow> {
	const rows: DocumentTableRow[] = (() => {
		const numBends = sheetBendingWsNumBendLines(vertex);
		return [
			[
				{
					text: wsi4.util.translate("NumBends"),
					alignment: DocumentAlignment.left,
				},
				{
					text: numBends,
					alignment: DocumentAlignment.right,
				},
			],
		];
	})();

	if (wsi4.node.processType(vertex) === ProcessType.dieBending) {
		rows.push(...createDieBendingDetails(vertex));
	}

	return rows;
}

function createSheetTappingDetails(vertex: Vertex): Array<DocumentTableRow> {
	const screwThreads = getTable(TableType.screwThread);
	const screwCountMap = new Map<string, number>();
	nodeUserDatumOrDefault("sheetTappingData", vertex)
		.forEach(sheetTappingDataEntry => {
			const screwThread = screwThreads.find(row => row.identifier === sheetTappingDataEntry.screwThread.identifier);
			if (screwThread === undefined) {
				wsi4.util.error("createSheetTappingDetails():  Missing screw thread table entry for id " + sheetTappingDataEntry.screwThread.identifier);
				return;
			} else {
				const count = screwCountMap.get(screwThread.identifier) ?? 0;
				screwCountMap.set(screwThread.identifier, count + 1);
			}
		});

	return Array.from(screwCountMap.entries())
		.map(([
			screwId,
			count,
		]): DocumentTableRow => {
			const screw = screwThreads.find(row => row.identifier === screwId);
			assert(screw !== undefined);
			return [
				{
					text: screw.name,
					alignment: DocumentAlignment.left,
				},
				{
					text: "x " + count.toFixed(0),
					alignment: DocumentAlignment.right,
				},
			];
		});
}

function createUserDefinedWsDetails(vertex: Vertex): Array<DocumentTableRow> {
	if (wsi4.node.processType(vertex) === ProcessType.sheetTapping) {
		return createSheetTappingDetails(vertex);
	}
	return [];
}

function createJoiningWsDetails(vertex: Vertex): Array<DocumentTableRow> {
	return wsi4.graph.sources(vertex)
		.map(sourceVertex => [
			{
				text: Number(wsi4.graph.sourceMultiplicity(sourceVertex, vertex))
					.toString() + " x ",
				alignment: DocumentAlignment.right,
			},
			{
				text: getArticleName(sourceVertex),
				alignment: DocumentAlignment.left,
			},
		]);
}

function createTubeCuttingWsDetails(vertex: Vertex): Array<DocumentTableRow> {
	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 [
		[
			{
				text: wsi4.util.translate("ContourLength"),
				alignment: DocumentAlignment.left,
			},
			{
				text: cuttingLength === undefined ? "N/A" : mmToString(cuttingLength),
				alignment: DocumentAlignment.right,
			},
		],
		[
			{
				text: wsi4.util.translate("NumPiercings"),
				alignment: DocumentAlignment.left,
			},
			{
				text: numPiercings,
				alignment: DocumentAlignment.right,
			},
		],
		[
			{
				text: wsi4.util.translate("extrusion_depth"),
				alignment: DocumentAlignment.left,
			},
			{
				text: mmToString(extrusionLength),
				alignment: DocumentAlignment.right,
			},
		],
	];
}

function createTubeWsDetails(tubeVertex: Vertex): Array<DocumentTableRow> {
	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 [
		[
			{
				text: wsi4.util.translate("count"),
				alignment: DocumentAlignment.left,
			},
			{
				text: grossConsumption === undefined ? "N/A" : Math.ceil(grossConsumption)
					.toString(),
				alignment: DocumentAlignment.right,
			},
		],
		[
			{
				text: wsi4.util.translate("Consumption"),
				alignment: DocumentAlignment.left,
			},
			{
				text: grossConsumption === undefined ? "N/A" : grossConsumption.toFixed(2),
				alignment: DocumentAlignment.right,
			},
		],
		[
			{
				text: wsi4.util.translate("charge_weight_in_kg"),
				alignment: DocumentAlignment.left,
			},
			{
				text: consumptionMasses === undefined ? "N/A" : kgToString(consumptionMasses.gross),
				alignment: DocumentAlignment.right,
			},
		],
		[
			{
				text: wsi4.util.translate("scrap_in_kg"),
				alignment: DocumentAlignment.left,
			},
			{
				text: consumptionMasses === undefined ? "N/A" : kgToString(consumptionMasses.scrap),
				alignment: DocumentAlignment.right,
			},
		],
	];
}

function startNodeDetails(vertex: Vertex): Array<Array<DocumentItem>> {
	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;
	})();
	return [
		createSeparator(),
		createHeading(processName, 3),
		[
			{
				type: DocumentItemType.barcode,
				content: {
					width: 12,
					text: processName,
					alignment: DocumentAlignment.left,
				},
			},
		],
	];
}

function createNodeTimesTableRows(vertex: Vertex): Array<DocumentTableRow> {
	const workStepType = wsi4.node.workStepType(vertex);
	const addTimes = workStepType === "sheetCutting" || workStepType === "sheetBending" || workStepType === "userDefined" || WorkStepType.joining;
	if (!addTimes) {
		return [];
	}
	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})`;
		}
	};

	return [
		[
			{
				text: wsi4.util.translate("SetupTimeAbbr"),
				alignment: DocumentAlignment.left,
			},
			{
				text: timeToString(times.setup),
				alignment: DocumentAlignment.right,
			},
		],
		[
			{
				text: wsi4.util.translate("UnitTimeAbbr"),
				alignment: DocumentAlignment.left,
			},
			{
				text: timeToString(times.unit),
				alignment: DocumentAlignment.right,
			},
		],
		[
			{
				text: wsi4.util.translate("UnitTimeAbbr") + " / " + wsi4.util.translate("piece"),
				alignment: DocumentAlignment.left,
			},
			{
				text: timeToString(times.unit / multiplicity),
				alignment: DocumentAlignment.right,
			},
		],
	];
}

function createNodeTableWsSpecificRows(vertex: Vertex): Array<DocumentTableRow> {
	switch (wsi4.node.workStepType(vertex)) {
		case WorkStepType.sheet: return createSheetWsDetails(vertex);
		case WorkStepType.sheetCutting: return createSheetCuttingWsDetails(vertex);
		case WorkStepType.sheetBending: return createSheetBendingWsDetails(vertex);
		case WorkStepType.joining: return createJoiningWsDetails(vertex);
		case WorkStepType.userDefined: return createUserDefinedWsDetails(vertex);
		case WorkStepType.tubeCutting: return createTubeCuttingWsDetails(vertex);
		case WorkStepType.tube: return createTubeWsDetails(vertex);
		case WorkStepType.userDefinedBase: return [];
		case WorkStepType.packaging: return [];
		case WorkStepType.transform: return [];
		case WorkStepType.undefined: return [];
	}
}

function createNodeTableAndImages(vertex: Vertex): Array<Array<DocumentItem>> {
	const svgOrPlaceholder = () => {
		if (wsi4.node.twoDimRep(vertex) !== undefined || wsi4.node.layered(vertex) !== undefined) {
			return createImageItem(vertex, DocumentImageType.svg, 4);
		} else {
			return createSpacerItem(4);
		}
	};
	const pngOrPlaceholder = () => {
		if (wsi4.node.assembly(vertex) || wsi4.node.inputAssembly(vertex)) {
			return createImageItem(vertex, DocumentImageType.png, 4);
		} else {
			return createSpacerItem(4);
		}
	};
	return [
		[
			{
				type: DocumentItemType.table,
				content: {
					width: 4,
					alignment: DocumentAlignment.left,
					columnWidths: [
						50,
						50,
					],
					columnHeaders: [],
					rows: [
						...createNodeTimesTableRows(vertex),
						...createNodeTableWsSpecificRows(vertex),
					],
				},
			},
			svgOrPlaceholder(),
			pngOrPlaceholder(),
		],
	];
}

export function createJobCard(article: Array<Vertex>): Array<Array<DocumentItem>> {
	return article.length === 0 ? [] : [
		...createTitle(wsi4.util.translate("JobCard")),
		...createArticleOverview(back(article)),
		...article.reduce(
			(acc: Array<Array<DocumentItem>>, vertex) => [
				...acc,
				...startNodeDetails(vertex),
				...createNodeTableAndImages(vertex),
				...createCommentOnDemand(vertex),
			],
			[]),
	];
}
