import {
	ProcessType,
	TableType,
} from "qrc:/js/lib/generated/enum";
import {
	createUpdatedTable,
	getSettingOrDefault,
} from "qrc:/js/lib/settings_table";
import {
	getDefaultTable,
	getMutableTable,
	getTable,
	setInternalTable,
	TableTypeMap,
} from "qrc:/js/lib/table_utils";
import {
	front,
} from "qrc:/js/lib/array_util";
import {
	computeOrderedProcesses,
	findActiveProcess,
} from "qrc:/js/lib/process";
import {
	assert,
	getRowForThreshold,
} from "qrc:/js/lib/utils";

// This should not be required any more.
// At program start all tables that are not present at all are set to their default values.
function addDefaultTableIfMissing(type: TableType): void {
	if (wsi4.tables.isInternalTable(type) || wsi4.tables.isExternalTable(type)) {
		return;
	}
	wsi4.tables.setInternalTable(wsi4.tables.getDefault(type));
}

// Fix process id (see #1565)
function fixProcessId(): void {
	if (wsi4.tables.isExternalTable(TableType.process)) {
		return;
	}
	const oldTable = getTable(TableType.process);
	const newTable = oldTable.filter(row => row.identifier !== "mechanicalDeburring");
	if (newTable.length === oldTable.length) {
		return;
	}
	const fixedRow = getDefaultTable(TableType.process)
		.find(row => row.identifier === "mechanical-deburring");
	if (fixedRow === undefined) {
		return;
	}
	newTable.push(fixedRow);
	setInternalTable(TableType.process, newTable);
}

// * Add new ProcessType:s automatic and manual mechanical deburring to
//     * process table
//     * process rate table
//     * process setup time fallback table
// * Deactivate mechanicalDeburring base process
// * Add new tables
//     * automaticMechanicalDeburringMaterial
//     * automaticMechanicalDeburringParameters
function applyDeburringRelatedChanges(): void {
	// Extend / modify process table
	(() => {
		if (wsi4.tables.isExternalTable(TableType.process)) {
			return;
		}
		// Removing existing row as this table row is modified (active is changed)
		const newTable = getTable(TableType.process)
			.filter(row => row.type !== ProcessType.mechanicalDeburring);
		const addProcessIfMissing = (newRow: Process | undefined) => {
			if (newRow === undefined) {
				return;
			}
			if (newTable.some(row => row.identifier === newRow.identifier)) {
				return;
			}
			newTable.push(newRow);
		};
		const defaultTable = getDefaultTable(TableType.process);
		addProcessIfMissing(defaultTable.find(row => row.type === ProcessType.mechanicalDeburring));
		addProcessIfMissing(defaultTable.find(row => row.type === ProcessType.automaticMechanicalDeburring));
		addProcessIfMissing(defaultTable.find(row => row.type === ProcessType.manualMechanicalDeburring));
		setInternalTable(TableType.process, newTable);
	})();

	// Utility function to extend a process-refererencing table
	const addRowIfMissing = <Row extends {processId: string}>(processType: ProcessType, defaultTable: readonly Readonly<Row>[], targetTable: Row[]) => {
		const process = getTable(TableType.process)
			.find(row => row.type === processType);
		if (process === undefined) {
			return;
		}
		const defaultRow = defaultTable.find(row => row.processId === process.identifier);
		if (defaultRow === undefined) {
			return;
		}
		if (targetTable.some(row => row.processId === defaultRow.processId)) {
			return;
		}
		targetTable.push(defaultRow);
	};

	// Extend process rate table
	(() => {
		if (wsi4.tables.isExternalTable(TableType.processRate)) {
			return;
		}
		const defaultTable = getDefaultTable(TableType.processRate);
		const newTable = Array.from(getTable(TableType.processRate));
		addRowIfMissing(ProcessType.automaticMechanicalDeburring, defaultTable, newTable);
		addRowIfMissing(ProcessType.manualMechanicalDeburring, defaultTable, newTable);
		setInternalTable(TableType.processRate, newTable);
	})();

	// Extend process setup time table
	(() => {
		if (wsi4.tables.isExternalTable(TableType.processSetupTimeFallback)) {
			return;
		}
		const defaultTable = getDefaultTable(TableType.processSetupTimeFallback);
		const newTable = Array.from(getTable(TableType.processSetupTimeFallback));
		addRowIfMissing(ProcessType.automaticMechanicalDeburring, defaultTable, newTable);
		addRowIfMissing(ProcessType.manualMechanicalDeburring, defaultTable, newTable);
		setInternalTable(TableType.processSetupTimeFallback, newTable);
	})();

	// Add new tables
	addDefaultTableIfMissing(TableType.automaticMechanicalDeburringMaterial);
	addDefaultTableIfMissing(TableType.automaticMechanicalDeburringParameters);
}

function addDimensionConstraintsTable(): void {
	addDefaultTableIfMissing(TableType.dimensionConstraints);
}

function fixPackagingTimePerArticle(): void {
	if (wsi4.tables.isExternalTable(TableType.packaging)) {
		return;
	}
	const table = getTable(TableType.packaging);
	if (table.some(row => row.tea !== 2.0)) {
		// User has modified the table so keeping as is
		return;
	}
	table.map((row): Packaging => Object.assign(row, {tea: 0.}));
	setInternalTable(TableType.packaging, table);
}

// Adding the new table AutomaticMechanicalDeburringMaterial breaks table consistency for users with custom sheetMaterial table entries.
// To address this issue this function updates the automatic mechanical deburring tables so for each sheet material there is an entry.
function fillAutomaticDeburringTablesFromSheetMaterial(): void {
	if (wsi4.tables.isExternalTable(TableType.automaticMechanicalDeburringMaterial) || wsi4.tables.isExternalTable(TableType.automaticMechanicalDeburringParameters)) {
		return;
	}
	const sheetMaterialTable = getTable(TableType.sheetMaterial);
	const oldDeburringMatTable = (() => {
		const table = getTable(TableType.automaticMechanicalDeburringMaterial);
		return table.length === 0 ? getDefaultTable(TableType.automaticMechanicalDeburringMaterial) : table;
	})();
	if (sheetMaterialTable.every(sheetMatRow => oldDeburringMatTable.some(deburringMatRow => sheetMatRow.identifier === deburringMatRow.sheetMaterialId))) {
		// Tables are ok
		return;
	}
	const newDeburringMatTable: AutomaticMechanicalDeburringMaterial[] = sheetMaterialTable.map(row => ({
		sheetMaterialId: row.identifier,
		automaticMechanicalDeburringMaterialId: front(oldDeburringMatTable).automaticMechanicalDeburringMaterialId,
	}));
	const newDeburringParamTable = getTable(TableType.automaticMechanicalDeburringParameters)
		.filter(lhs => newDeburringMatTable.some(rhs => lhs.automaticMechanicalDeburringMaterialId === rhs.automaticMechanicalDeburringMaterialId));
	if (newDeburringMatTable.length === 0) {
		// Cannot apply fix
		return;
	}
	setInternalTable(TableType.automaticMechanicalDeburringMaterial, newDeburringMatTable);
	setInternalTable(TableType.automaticMechanicalDeburringParameters, newDeburringParamTable);
}

function addLaserSheetCuttingMaxThicknessTable() {
	addDefaultTableIfMissing(TableType.laserSheetCuttingMaxThickness);
}

function addDefaultRowIfMissing<Type extends keyof TableTypeMap>(type: Type, isMatchingRow: (row: Readonly<TableTypeMap[Type]>) => boolean) {
	if (wsi4.tables.isExternalTable(type)) {
		return;
	}
	const table = Array.from(getTable(type));
	if (table.some(isMatchingRow)) {
		return;
	}
	const defaultRow = getDefaultTable(type)
		.find(isMatchingRow);
	if (defaultRow === undefined) {
		return;
	}
	table.push(defaultRow);
	setInternalTable(type, table);
}

function addProcessTypeUserDefinedMachining() {
	addDefaultRowIfMissing(TableType.process, row => row.type === ProcessType.userDefinedMachining);
	addDefaultRowIfMissing(TableType.process, row => row.type === ProcessType.userDefinedThreading);
	addDefaultRowIfMissing(TableType.process, row => row.type === ProcessType.userDefinedCountersinking);

	// Search for previously set table rows...
	const udmProcess = getTable(TableType.process)
		.find(row => row.type === ProcessType.userDefinedMachining);
	const udtProcess = getTable(TableType.process)
		.find(row => row.type === ProcessType.userDefinedThreading);
	const udcProcess = getTable(TableType.process)
		.find(row => row.type === ProcessType.userDefinedCountersinking);

	// ... and leave if there are none as this indicates that user defined external tables are involved
	if (udmProcess === undefined || udtProcess === undefined || udcProcess === undefined) {
		return;
	}

	addDefaultRowIfMissing(TableType.processRate, row => row.processId === udmProcess.identifier);
	addDefaultRowIfMissing(TableType.processRate, row => row.processId === udtProcess.identifier);
	addDefaultRowIfMissing(TableType.processRate, row => row.processId === udcProcess.identifier);

	addDefaultRowIfMissing(TableType.processSetupTimeFallback, row => row.processId === udmProcess.identifier);
	addDefaultRowIfMissing(TableType.processSetupTimeFallback, row => row.processId === udtProcess.identifier);
	addDefaultRowIfMissing(TableType.processSetupTimeFallback, row => row.processId === udcProcess.identifier);

	addDefaultRowIfMissing(TableType.processUnitTimeFallback, row => row.processId === udmProcess.identifier);
	addDefaultRowIfMissing(TableType.processUnitTimeFallback, row => row.processId === udtProcess.identifier);
	addDefaultRowIfMissing(TableType.processUnitTimeFallback, row => row.processId === udcProcess.identifier);
}

function addProcessTypeSlideGrinding() {
	addDefaultRowIfMissing(TableType.process, row => row.type === ProcessType.slideGrinding);
	const process = getTable(TableType.process)
		.find(row => row.type === ProcessType.slideGrinding);
	if (process === undefined) {
		return;
	}
	addDefaultRowIfMissing(TableType.processRate, row => row.processId === process.identifier);
	addDefaultRowIfMissing(TableType.processSetupTimeFallback, row => row.processId === process.identifier);
	addDefaultRowIfMissing(TableType.processUnitTimeFallback, row => row.processId === process.identifier);
}

function addScrewThreadTable() {
	addDefaultTableIfMissing(TableType.screwThread);
}

function addProcessTypeSheetTapping() {
	addDefaultRowIfMissing(TableType.process, row => row.type === ProcessType.sheetTapping);
	const process = getTable(TableType.process)
		.find(row => row.type === ProcessType.sheetTapping);
	if (process === undefined) {
		return;
	}
	addDefaultRowIfMissing(TableType.processRate, row => row.processId === process.identifier);
	addDefaultRowIfMissing(TableType.processSetupTimeFallback, row => row.processId === process.identifier);
}

function addTappingTimeParametersTable() {
	addDefaultTableIfMissing(TableType.tappingTimeParameters);
}

function extendTransportationCostsTable() {
	if (wsi4.tables.isExternalTable(TableType.transportationCosts)) {
		return;
	}

	const packagingTable = getTable(TableType.packaging);
	const transporationCostsTable = getMutableTable(TableType.transportationCosts);
	transporationCostsTable.forEach((transportationCosts, index) => {
		const packaging = packagingTable.find(row => row.identifier === transportationCosts.packagingId);
		if (packaging === undefined) {
			// Valid case as user tables can be inconsistent at this point - user will receive a warning when the gui shows up.
			transportationCosts.identifier = "transport-" + index.toFixed(0);
		} else {
			transportationCosts.identifier = "transport-" + packaging.identifier;
			transportationCosts.name = packaging.name;
		}
	});
	setInternalTable(TableType.transportationCosts, transporationCostsTable);
}

function addExportIdentifierToDieGroupTable(tableType: "upperDieGroup" | "lowerDieGroup") {
	if (wsi4.tables.isExternalTable(tableType)) {
		return;
	}

	const defaultTable = getDefaultTable(tableType);
	const updatedTable = getMutableTable(tableType)
		.map(rowToUpdate => {
			const defaultRow = defaultTable.find(row => row.identifier === rowToUpdate.identifier);
			if (defaultRow !== undefined) {
				rowToUpdate.exportIdentifier = defaultRow.exportIdentifier;
			}
			return rowToUpdate;
		});
	setInternalTable(tableType, updatedTable);
}

function addExportIdentifierToUpperDieGroupTable() {
	addExportIdentifierToDieGroupTable(TableType.upperDieGroup);
}

function addExportIdentifierToLowerDieGroupTable() {
	addExportIdentifierToDieGroupTable(TableType.lowerDieGroup);
}

function addTubeTables() {
	addDefaultTableIfMissing(TableType.tubeProfile);
	addDefaultTableIfMissing(TableType.tubeMaterial);
	addDefaultTableIfMissing(TableType.tubeMaterialDensity);
	addDefaultTableIfMissing(TableType.tubeSpecification);
	addDefaultTableIfMissing(TableType.tube);
}

function addProcessUserDefinedTube() {
	// ProcessType is deprecated; nothing is left to do
	return;
}

function removeDeprecatedSurchargesIfDefault() {
	if (wsi4.tables.isExternalTable(TableType.surcharge)) {
		return;
	} else {
		setInternalTable(TableType.surcharge, getTable(TableType.surcharge)
			.filter(row => row.type !== "globalDelta" || row.value === 0)
			.filter(row => row.type !== "articleDelta" || row.value === 0));
	}
}

function populateSheetCuttingMaterials() {
	if (wsi4.tables.isExternalTable(TableType.sheetCuttingMaterial)) {
		return;
	} else {
		const allIds = getTable(TableType.sheetCuttingMaterialMapping)
			.map(row => row.sheetCuttingMaterialId);
		const uniqueIds = Array.from(new Set(allIds));
		setInternalTable(TableType.sheetCuttingMaterial, uniqueIds.map((id): SheetCuttingMaterial => ({
			identifier: id,
			name: id,
		})));
	}
}

function populateSheetBendingMaterials() {
	if (wsi4.tables.isExternalTable(TableType.sheetBendingMaterial)) {
		return;
	} else {
		const allIds = getTable(TableType.sheetBendingMaterialMapping)
			.map(row => row.sheetBendingMaterialId);
		const uniqueIds = Array.from(new Set(allIds));
		setInternalTable(TableType.sheetBendingMaterial, uniqueIds.map((id): SheetBendingMaterial => ({
			identifier: id,
			name: id,
		})));
	}
}

function populateBendDeductions() {
	addDefaultTableIfMissing(TableType.bendDeduction);
}

function populateTubeRelatedTables() {
	if (getTable(TableType.process)
		.some(row => row.type === ProcessType.tubeCutting)) {
		return;
	}

	// So far tubes were considered an experimental feature.
	// Since the default tube related tables have been changed substantially
	// the old table content is overwritten.
	setInternalTable(TableType.tubeProfile, []);
	setInternalTable(TableType.tubeMaterial, []);
	setInternalTable(TableType.tubeMaterialDensity, []);
	setInternalTable(TableType.tubeSpecification, []);
	setInternalTable(TableType.tubeCuttingProcess, []);
	setInternalTable(TableType.tubeCuttingProcessMapping, []);
	setInternalTable(TableType.tubeCuttingSpeed, []);
	setInternalTable(TableType.tubeCuttingPierceTime, []);
	setInternalTable(TableType.tube, []);
	setInternalTable(TableType.tubePrice, []);
	setInternalTable(TableType.tubeStock, []);
	setInternalTable(TableType.tubeMaterialScrapValue, []);
}

function migrateLaserSheetCuttingToSheetCuttingTables() {
	if (getTable("sheetCuttingProcess").length > 0) {
		return;
	}

	type ProcessWithCuttingGas = {
		processId: string;
		cuttingGasId: string;
	};

	const processToCuttingGas: ProcessWithCuttingGas[] = [];
	{
		const processTable = getMutableTable("process");
		const lscProcess = findActiveProcess(p => p.type === "laserSheetCutting", processTable)
			?? computeOrderedProcesses(p => p.type === "laserSheetCutting", processTable).find(p => p.type === "laserSheetCutting");
		if (lscProcess === undefined) {
			// Cannot migrate without any laser sheet cutting process
			return;
		}

		const computeUniqueProcessId = (baseId: string): string => {
			if (processTable.every(row => row.identifier !== baseId)) {
				return baseId;
			}
			let suffix = 0;
			for (;;) {
				const candidateId = baseId + "_" + suffix.toString();
				if (processTable.every(row => row.identifier !== candidateId)) {
					return candidateId;
				}
				++suffix;
			}
		};

		const processRates = getMutableTable("processRate");

		const processSetupTimes = getMutableTable("processSetupTimeFallback");
		const oldSetupTime = processSetupTimes.find(row => row.processId === lscProcess.identifier)?.time;

		const dimensionConstraints = getMutableTable("dimensionConstraints");
		const oldDimensionConstraint = dimensionConstraints.find(row => row.processId === lscProcess.identifier);

		getTable("laserSheetCuttingGas").forEach(cuttingGas => {
			const newProcess: Process = {
				identifier: computeUniqueProcessId("laserSheetCutting" + cuttingGas.identifier + "Id"),
				parentIdentifier: lscProcess.identifier,
				name: lscProcess.name + " " + cuttingGas.name,
				type: "laserSheetCutting",
				active: true,
				childrenActive: true,
				costCenter: "",
				priority: 0,
				description: "",
			};
			processTable.push(newProcess);
			processToCuttingGas.push({
				processId: newProcess.identifier,
				cuttingGasId: cuttingGas.identifier,
			});

			const rate = getTable("laserSheetCuttingRate")
				.filter(row => row.laserSheetCuttingGasId === cuttingGas.identifier)
				.reduce((acc, row) => Math.max(acc, row.rate), 0);
			if (rate > 0) {
				processRates.push({
					processId: newProcess.identifier,
					rate: rate,
				});
			}

			if (oldSetupTime !== undefined) {
				processSetupTimes.push({
					processId: newProcess.identifier,
					time: oldSetupTime,
				});
			}
			if (oldDimensionConstraint !== undefined && dimensionConstraints.every(row => row.processId !== newProcess.identifier)) {
				const value = Object.assign({}, oldDimensionConstraint);
				value.processId = newProcess.identifier;
				dimensionConstraints.push(value);
			}
		});
		setInternalTable("process", processTable);
		setInternalTable("processRate", processRates);
		setInternalTable("processSetupTimeFallback", processSetupTimes);
		setInternalTable("dimensionConstraints", dimensionConstraints);
	}

	type SheetCuttingProcessWithCuttingGas = {
		sheetCuttingProcessId: string;
		sheetCuttingMaterialId: string;
		cuttingGasId: string;
	};

	const scProcessToScMaterialAndGas: SheetCuttingProcessWithCuttingGas[] = [];
	{
		const materialGasCombinations = [
			...getTable("laserSheetCuttingSpeed"),
			...getTable("laserSheetCuttingPierceTime"),
		].filter((lhs, index, self) => self.findIndex(rhs => lhs.sheetCuttingMaterialId === rhs.sheetCuttingMaterialId && lhs.laserSheetCuttingGasId === rhs.laserSheetCuttingGasId) === index);

		const sheetCuttingProcesses: SheetCuttingProcess[] = [];
		materialGasCombinations.forEach(item => {
			const scp: SheetCuttingProcess = {
				identifier: "laser-cutting-" + item.laserSheetCuttingGasId + "-" + item.sheetCuttingMaterialId,
				name: "laser-cutting-" + item.laserSheetCuttingGasId + "-" + item.sheetCuttingMaterialId,
				description: "",
			};
			sheetCuttingProcesses.push(scp);
			scProcessToScMaterialAndGas.push({
				sheetCuttingProcessId: scp.identifier,
				sheetCuttingMaterialId: item.sheetCuttingMaterialId,
				cuttingGasId: item.laserSheetCuttingGasId,
			});
		});

		const sheetCuttingMaterialMappings = getTable("sheetCuttingMaterialMapping");
		const sheetCuttingProcessMappings: SheetCuttingProcessMapping[] = [];
		const sheetCuttingProcessToLaserCuttingGas: SheetCuttingProcessToLaserCuttingGas[] = [];
		materialGasCombinations.forEach(combo => {
			const processId = processToCuttingGas.find(entry => entry.cuttingGasId === combo.laserSheetCuttingGasId)?.processId;
			if (processId === undefined) {
				// Skipping table inconsistency
				return;
			}

			sheetCuttingMaterialMappings.filter(row => row.sheetCuttingMaterialId === combo.sheetCuttingMaterialId)
				.forEach(sheetCuttingMaterialMapping => {
					const sheetCuttingProcessId = scProcessToScMaterialAndGas.find(
						entry => entry.sheetCuttingMaterialId === combo.sheetCuttingMaterialId && entry.cuttingGasId === combo.laserSheetCuttingGasId,
					)?.sheetCuttingProcessId;
					assert(sheetCuttingProcessId !== undefined);
					sheetCuttingProcessMappings.push({
						processId: processId,
						sheetMaterialId: sheetCuttingMaterialMapping.sheetMaterialId,
						sheetCuttingProcessId: sheetCuttingProcessId,
					});
					if (sheetCuttingProcessToLaserCuttingGas.every(row => row.sheetCuttingProcessId !== sheetCuttingProcessId)) {
						sheetCuttingProcessToLaserCuttingGas.push({
							sheetCuttingProcessId: sheetCuttingProcessId,
							laserSheetCuttingGasId: combo.laserSheetCuttingGasId,
						});
					}
				});
		});

		setInternalTable("sheetCuttingProcess", sheetCuttingProcesses);
		setInternalTable("sheetCuttingProcessMapping", sheetCuttingProcessMappings);
		setInternalTable("sheetCuttingProcessToLaserCuttingGas", sheetCuttingProcessToLaserCuttingGas);
	}

	{
		// Formerly hardcoded approx. max. acceleration [m / s²]
		const aMax = 5;

		const allPierceTimesSorted: readonly Readonly<LaserSheetCuttingPierceTime>[] = getMutableTable("laserSheetCuttingPierceTime").sort((lhs, rhs) => lhs.thickness - rhs.thickness);
		const thicknessConstraints: readonly Readonly<LaserSheetCuttingMaxThickness>[] = getTable("laserSheetCuttingMaxThickness");
		const areaConstraintsSorted: readonly Readonly<LaserSheetCuttingMinArea>[] = getMutableTable("laserSheetCuttingMinArea").sort((lhs, rhs) => lhs.thickness - rhs.thickness);

		const sheetCuttingMotionParameters: SheetCuttingMotionParameters[] = [];
		getTable("laserSheetCuttingSpeed").forEach(lscSpeed => {
			if (thicknessConstraints.some(row => row.sheetCuttingMaterialId === lscSpeed.sheetCuttingMaterialId
				&& row.laserSheetCuttingGasId === lscSpeed.laserSheetCuttingGasId
				&& (row.maxThickness < lscSpeed.thickness || lscSpeed.thickness < row.minThickness))) {
				return;
			}

			const sheetCuttingProcessId = scProcessToScMaterialAndGas.find(
				entry => entry.sheetCuttingMaterialId === lscSpeed.sheetCuttingMaterialId && entry.cuttingGasId === lscSpeed.laserSheetCuttingGasId,
			)?.sheetCuttingProcessId;
			assert(sheetCuttingProcessId !== undefined);

			const applicablePierceTimes = allPierceTimesSorted.filter(pierceTime => pierceTime.sheetCuttingMaterialId === lscSpeed.sheetCuttingMaterialId
				&& pierceTime.laserSheetCuttingGasId === lscSpeed.laserSheetCuttingGasId);
			const pierceTime = getRowForThreshold(applicablePierceTimes, pierceTime => pierceTime.thickness >= lscSpeed.thickness)?.time ?? 0;

			const minAreaConstraint = areaConstraintsSorted.find(
				row => row.sheetCuttingMaterialId === lscSpeed.sheetCuttingMaterialId
					&& row.laserSheetCuttingGasId === lscSpeed.laserSheetCuttingGasId
					&& row.thickness >= lscSpeed.thickness,
			)?.area;

			// Formerly hardcoded threshold area:
			// * Contour area < threshold       : laser sheet cutting speed slow-down by factor 0.5
			// * Contour area < 0.70 * threshold : laser sheet cutting speed slow-down by factor 0.25
			// * Contour area < 0.35 * threshold : not manufacturable any more
			// See https://gitlab.hq.wsoptics.de/src/wsi4/issues/417
			const largeContourArea = 0.25 * lscSpeed.thickness * lscSpeed.thickness * Math.PI;
			const mediumContourArea = 0.70 * largeContourArea;
			const smallContourArea = minAreaConstraint ?? 0.35 * largeContourArea;

			const areasAndFactors: [number, number][] = [
				[
					largeContourArea,
					1,
				],
				[
					mediumContourArea,
					0.5,
				],
				[
					smallContourArea,
					0.25,
				],
			];

			areasAndFactors.forEach(tuple => {
				sheetCuttingMotionParameters.push({
					sheetCuttingProcessId: sheetCuttingProcessId,
					thickness: lscSpeed.thickness,
					contourArea: tuple[0],
					speed: tuple[1] * lscSpeed.speed,
					acceleration: tuple[1] * aMax,
					pierceTime: pierceTime,
				});
			});
		});

		setInternalTable("sheetCuttingMotionParameters", sheetCuttingMotionParameters);
	}

	{
		const lscThicknessConstraints: readonly Readonly<LaserSheetCuttingMaxThickness>[] = getTable("laserSheetCuttingMaxThickness");
		const sheetCuttingThicknessConstraints: SheetCuttingThicknessConstraints[] = [];
		lscThicknessConstraints.forEach(row => {
			const sheetCuttingProcessId = scProcessToScMaterialAndGas.find(
				item => item.sheetCuttingMaterialId === row.sheetCuttingMaterialId && item.cuttingGasId === row.laserSheetCuttingGasId,
			)?.sheetCuttingProcessId;
			if (sheetCuttingProcessId === undefined) {
				return;
			}
			sheetCuttingThicknessConstraints.push({
				sheetCuttingProcessId: sheetCuttingProcessId,
				minThickness: row.minThickness,
				maxThickness: row.maxThickness,
			});
		});
		setInternalTable("sheetCuttingThicknessConstraints", sheetCuttingThicknessConstraints);
	}
}

// Note: new entries must be appended;  array-index indicates associated migration version
const migrationFunctions = [
	fixProcessId,
	applyDeburringRelatedChanges,
	addDimensionConstraintsTable,
	fixPackagingTimePerArticle,
	fillAutomaticDeburringTablesFromSheetMaterial,
	addLaserSheetCuttingMaxThicknessTable,
	addProcessTypeUserDefinedMachining,
	addProcessTypeSlideGrinding,
	addScrewThreadTable,
	addProcessTypeSheetTapping,
	addTappingTimeParametersTable,
	extendTransportationCostsTable,
	addExportIdentifierToUpperDieGroupTable,
	addExportIdentifierToLowerDieGroupTable,
	addTubeTables,
	addProcessUserDefinedTube,
	removeDeprecatedSurchargesIfDefault,
	populateSheetCuttingMaterials,
	populateSheetBendingMaterials,
	populateBendDeductions,
	populateTubeRelatedTables,
	migrateLaserSheetCuttingToSheetCuttingTables,
];

export function currentVersion(): number {
	return migrationFunctions.length;
}

export function run() : void {
	const lastVersion = getSettingOrDefault("tableMigrationVersion");
	migrationFunctions.filter((_f, index) => index >= lastVersion)
		.forEach(f => f());
	setInternalTable(TableType.setting, createUpdatedTable("tableMigrationVersion", migrationFunctions.length));
}
