import {
	CalcSettings,
	isScaleValueMode,
	readCalcSettings,
	ScaleValueMode,
	writeCalcSettings,
} from "qrc:/js/lib/calc_settings";
import {
	ErpInterfaceVersion,
	isErpInterfaceVersion,
} from "qrc:/js/lib/erp_interface";
import {
	ProcessType,
	TableType,
	WidgetType,
	WorkStepType,
} from "qrc:/js/lib/generated/enum";
import {
	changeArticleNames,
} from "qrc:/js/lib/graph_manipulator";
import {
	getArticleName,
} from "qrc:/js/lib/graph_utils";
import {
	createCheckBoxRow,
	createDropDownRow,
	createLabelRow,
	createLineEditRow,
	createSpinBoxRow,
} from "qrc:/js/lib/gui_form_widget";
import {
	getDirectoryPath,
	getOpenFilePath,
	getSaveFilePath,
	sheetMaterialToId,
	sheetMaterialToString,
	showError,
	showFormWidget,
} from "qrc:/js/lib/gui_utils";
import {
	createExternalJoining,
	createInternalJoining,
	ExternalJoining,
} from "qrc:/js/lib/joining_utils";
import {
	createPngFutures, dieChoiceAlternativesForVertex,
} from "qrc:/js/lib/node_utils";
import {
	createLProfileIop,
	LProfile,
} from "qrc:/js/lib/part_creation";
import {
	computeGraphContext,
	GraphConstraintsContext,
	isAvailableProcess,
	matchesGraphConstraints,
	workStepTypeMap,
} from "qrc:/js/lib/process";
import {
	CompletionTimeMode,
	createUpdatedTable,
	getSettingGraphRepResourcesConfig,
	getSettingOrDefault,
	GraphRepResourcesConfig,
	isCompletionTimeMode,
	isGraphRepResourcesConfig,
	isSheetNestingMode,
	SheetNestingMode,
} from "qrc:/js/lib/settings_table";
import {
	getMutableTable,
	getTable,
	setInternalTable,
} from "qrc:/js/lib/table_utils";
import {
	toVertex,
} from "qrc:/js/lib/ui_utils";
import {
	assert,
	assertDebug,
	computeNameMaxLengthLowerBound,
	computeUniqueNames,
	ElisionMode,
	exhaustiveStringTuple,
	getKeysOfObject,
	isArray,
	isBoolean,
	isElisionMode,
	isNumber,
	isString,
	parseJson,
	readSetting as legacyReadSetting,
	writeSetting as legacyWriteSetting,
	computeArrayIntersection,
	defaultPngResolution,
} from "qrc:/js/lib/utils";
import {
	addExtrusion,
	addUserDefinedTubeNode,
} from "qrc:/js/lib/graph_manipulator_utils";
import {
	AutomaticProcessConfig,
	DeburringMode,
	isDeburringMode,
} from "qrc:/js/lib/graph_post_processing";
import {
	createUpdatedSharedData,
	getSharedDataEntry,
} from "qrc:/js/lib/shared_data";

import {
	isBendLineEngravingMode,
} from "qrc:/js/lib/bend_line_engraving_mode";
import {
	front,
} from "qrc:/js/lib/array_util";
import {
	getGraphUserDataEntry,
} from "qrc:/js/lib/userdata_config";
import {
	isSheetUpperSideStrategy,
} from "qrc:/js/lib/generated/typeguard";
import {
	ExportSettings,
	isExportSettings,
	readExportSettings,
	writeExportSettings,
} from "./gui_export_settings";
import {
	InitialUserDataMode,
	isInitialUserDataMode,
	readSetting,
	writeSetting,
} from "./gui_local_settings";
import {
	createQuotationDocx,
	createQuotationInput,
} from "./gui_export_quotation";
import {
	createGraphRepresentation,
	ScaleDataConfig,
} from "./export_graph_representation";

export declare interface JoiningInit {
	joining: ExternalJoining;
	articleName: string;
}

export function sequenceEditorInit(vertexRepresentation: Vertex | string): JoiningInit {
	const vertex = toVertex(vertexRepresentation);
	if (vertex === undefined) {
		return wsi4.throwError("sequenceEditorInit(): Error in vertex");
	}

	const internalJoining = createInternalJoining(vertex);
	if (internalJoining === undefined) {
		return wsi4.throwError("sequenceEditorInit(): Missing internal joining");
	}
	const externalJoining = createExternalJoining(internalJoining);
	if (externalJoining === undefined) {
		return wsi4.throwError("sequenceEditorInit(): Missing external joining");
	}
	return {
		// Note: 'joining' is the identifier defined in configuration.js
		joining: externalJoining,
		articleName: getArticleName(vertex),
	};
}

export function dieSelectionWidgetInit(vertex: Vertex): WidgetConfigBendingToolEditor {
	assertDebug(() => wsi4.node.workStepType(vertex) === WorkStepType.sheetBending, "Pre-condition violated:  Expecting WST sheetBending");

	const bendLineDataArray = wsi4.node.computeBendLineData(vertex);
	assert(bendLineDataArray !== undefined, "bendLineDataArray not available");

	const dieChoiceMap = wsi4.node.dieChoiceMap(vertex);
	assert(dieChoiceMap !== undefined, "dieChoiceMap not available");

	return {
		input: dieChoiceAlternativesForVertex(vertex)
			.map(candidates => {
				const bendLineData = bendLineDataArray.find(entry => entry.bendDescriptor === candidates.bendDescriptor);
				assert(bendLineData !== undefined, "Expecting matching bendLineData");
				return {
					bendDescriptor: bendLineData.bendDescriptor,
					constructedInnerRadius: bendLineData.constructedInnerRadius,
					angle: bendLineData.bendAngle,
					bendDieChoices: candidates.bendDieChoices,
				};
			}),
		initialValue: {
			dieChoiceMap: dieChoiceMap,
		},
	};
}

export function createProcessSelectorConfigForVertex(vertices: Vertex[]): WidgetConfig {
	assert(vertices.length > 0, "Pre-condition violated");

	const processId = wsi4.node.processId(front(vertices));
	const forced = wsi4.node.forcedProcessType(front(vertices));

	const allAvailableWsts = Array.from(new Set(getTable("process")
		.filter((p, _, self) => isAvailableProcess(p, self))
		.map(p => workStepTypeMap[p.type])));
	const wstIntersection = computeArrayIntersection(vertices.map(v => wsi4.node.checkWorkStepAvailability(v, allAvailableWsts)));

	const denyList: readonly ProcessType[] = [
		// ProcessType is deprecated and should not be selectable
		ProcessType.userDefinedTube,
	];

	const reducedProcessTable = (() => {
		const graphContext = computeGraphContext(front(vertices));
		const processTable = getTable(TableType.process);
		const candidates = processTable
			.filter(row => denyList.every(processType => processType !== row.type))
			.filter(row => matchesGraphConstraints(row.type, graphContext))
			.filter(row => isAvailableProcess(row, processTable));
		return computeConsistentProcessTree(candidates, processTable);
	})();

	const content: WidgetConfigProcessSelector = {
		initialValue: {
			processId: processId,
			forced: forced,
		},
		processTable: reducedProcessTable,
		supportedWorkStepTypes: wstIntersection,
	};
	return {
		type: WidgetType.processSelector,
		content: content,
	};
}

function recursiveCollectParents(acc: Readonly<Process>[], targetProcess: Readonly<Process>, allProcesses: readonly Readonly<Process>[]): Readonly<Process>[] {
	const parentProcess = allProcesses.find(process => process.identifier === targetProcess.parentIdentifier);
	if (parentProcess === undefined) {
		return acc;
	} else {
		assertDebug(() => acc.every(item => item.identifier !== parentProcess.identifier), "Duplicate process should not be encourered: " + parentProcess.identifier);
		acc.push(parentProcess);
		return recursiveCollectParents(acc, parentProcess, allProcesses);
	}
}

/**
 * Process selector widget expects self-contained tree so adding all (indirect) parent processes for all candidates
 */
function computeConsistentProcessTree(candidates: readonly Readonly<Process>[], processTableInput?: readonly Readonly<Process>[]): Process[] {
	const processTable = processTableInput === undefined ? getTable(TableType.process) : processTableInput;

	const idsInclDups = candidates
		.map(process => process.identifier)
		.reduce((acc: string[], processId) => {
			const currentProcess = processTable.find(process => process.identifier === processId);
			assert(currentProcess !== undefined, "Expecting valid process");
			acc.push(currentProcess.identifier);

			const parentProcesses = recursiveCollectParents([], currentProcess, processTable);
			parentProcesses.forEach(process => acc.push(process.identifier));

			return acc;
		}, []);

	return Array
		.from([ ...new Set([ ...idsInclDups ]) ])
		.map(processId => processTable.find(process => process.identifier === processId))
		.filter((row): row is Readonly<Process> => row !== undefined);
}

/**
 * Compute process selector config for a node that has been added to a sub-graph
 *
 * Relevant `WorkStepType`s are userDefined and transform as of now.
 */
export function createProcessSelectorConfigForInjectedNode(graphContext: GraphConstraintsContext): WidgetConfig | undefined {
	const relevantWsts: WorkStepType[] = [
		WorkStepType.userDefined,
		WorkStepType.transform,
	];

	const processes = (() => {
		const allProcesses = getTable(TableType.process);

		const candidates = allProcesses
			.filter(process => {
				const mappedWst = workStepTypeMap[process.type];
				return relevantWsts.some(wst => wst === mappedWst);
			})
			.filter(process => isAvailableProcess(process))
			.filter(row => matchesGraphConstraints(row.type, graphContext));

		return computeConsistentProcessTree(candidates, allProcesses);
	})();

	if (processes.length > 0) {
		const content: WidgetConfigProcessSelector = {
			initialValue: {
				processId: processes[0]!.identifier,
				forced: true,
			},
			processTable: processes,
			supportedWorkStepTypes: relevantWsts,
		};
		return {
			type: WidgetType.processSelector,
			content: content,
		};
	} else {
		return undefined;
	}
}

export declare interface ProcessDialogInitData {
	table: readonly Readonly<Process>[];
}

export function processDialogInit(): ProcessDialogInitData {
	const table = getTable(TableType.process);
	const initData = {
		table: table,
	};
	return initData;
}

export interface IntAndExtTables {
	internal: AnyTable[];
	external: AnyTable[];
}

// Pre-condition of table editor: Each table must be submitted exactly once
// (either as internal table or as external external table - not both)
export function userTableEditorDialogInit(): IntAndExtTables {
	const mapTables = (externalOnly: boolean): AnyTable[] => Array.from(TableType)
		.filter(type => wsi4.tables.isExternalTable(type) === externalOnly)
		.map(type => wsi4.tables.get(type));
	return {
		internal: mapTables(false),
		external: mapTables(true),
	};
}

export function showNodeUserDataDefaultValuesDialog(): void {
	const modeFormKey = "mode";
	const sheetMaterialIdFormKey = "sheetMaterialId";
	const tubeMaterialIdFormKey = "tubeMaterialId";

	const dialogResult = showFormWidget([
		createDropDownRow<InitialUserDataMode>({
			key: modeFormKey,
			name: wsi4.util.translate("mode"),
			values: exhaustiveStringTuple<InitialUserDataMode>()("none", "static", "query"),
			toId: mode => mode,
			toName: mode => {
				switch (mode) {
					case "none": return "--";
					case "static": return wsi4.util.translate("static");
					case "query": return wsi4.util.translate("query_on_demand");
				}
			},
			computeInitialValueIndex: values => {
				const settingsValue = readSetting("initialUserDataValueMode");
				return Math.max(0, values.findIndex(mode => mode === settingsValue));
			},
		}),
		createDropDownRow({
			key: sheetMaterialIdFormKey,
			name: wsi4.tables.name("sheetMaterial"),
			values: getTable("sheetMaterial"),
			toId: sheetMaterialToId,
			toName: sheetMaterialToString,
			computeInitialValueIndex: rows => {
				const settingsValue = readSetting("defaultSheetMaterialId");
				return Math.max(0, rows.findIndex(row => settingsValue !== undefined && row.identifier === settingsValue));
			},
		}),
		...(() => {
			if (!wsi4.isFeatureEnabled("tubeDetection")) {
				return [];
			}
			return [
				createDropDownRow({
					key: tubeMaterialIdFormKey,
					name: wsi4.tables.name("tubeMaterial"),
					values: getTable("tubeMaterial"),
					toId: row => row.identifier,
					toName: row => row.name,
					computeInitialValueIndex: rows => {
						const settingsValue = readSetting("defaultTubeMaterialId");
						return Math.max(0, rows.findIndex(row => settingsValue !== undefined && row.identifier === settingsValue));
					},
				}),
			];
		})(),
	]);
	if (dialogResult === undefined) {
		// User canceled
		return;
	}

	const selectedMode = dialogResult.values[modeFormKey];
	assert(isInitialUserDataMode(selectedMode));

	const selectedSheetMaterialId = dialogResult.values[sheetMaterialIdFormKey];
	assert(isString(selectedSheetMaterialId));

	writeSetting("initialUserDataValueMode", selectedMode);
	writeSetting("defaultSheetMaterialId", selectedSheetMaterialId);

	// Optional since only added if tubeDetection feature is available
	const selectedTubeMaterialId = dialogResult.values[tubeMaterialIdFormKey];
	if (typeof selectedTubeMaterialId === "string") {
		writeSetting("defaultTubeMaterialId", selectedTubeMaterialId);
	}

	if (selectedMode === "static") {
		let sharedData = wsi4.sharedData.read();
		sharedData = createUpdatedSharedData("defaultSheetMaterialId", selectedSheetMaterialId, sharedData);
		if (typeof selectedTubeMaterialId === "string") {
			sharedData = createUpdatedSharedData("defaultTubeMaterialId", selectedTubeMaterialId, sharedData);
		}
		wsi4.sharedData.write(sharedData);
	} else {
		let sharedData = wsi4.sharedData.read();
		sharedData = createUpdatedSharedData("defaultSheetMaterialId", undefined, sharedData);
		sharedData = createUpdatedSharedData("defaultTubeMaterialId", undefined, sharedData);
		wsi4.sharedData.write(sharedData);
	}
}

export function showBendLineEngravingConfigDialog(): void {
	const engravingModeFormKey = "engravingRowString";
	const engravingLengthKey = "engravingLength";

	const bendLineEngravingModes = exhaustiveStringTuple<BendLineEngravingMode>()(
		"none",
		"upwardOnly",
		"downwardOnly",
		"all",
	);

	const initialBendLineEngravingMode = readSetting("defaultBendLineEngravingMode");
	const initialEngravingLength = readSetting("bendLineEngravingLength");

	const dialogResult = showFormWidget([
		createDropDownRow<BendLineEngravingMode>({
			key: engravingModeFormKey,
			name: wsi4.util.translate("bend_line_engraving"),
			values: bendLineEngravingModes,
			toId: mode => mode,
			toName: mode => {
				switch (mode) {
					case "none": return "-";
					case "upwardOnly": return wsi4.util.translate("upward_bends");
					case "downwardOnly": return wsi4.util.translate("downward_bends");
					case "all": return wsi4.util.translate("all_bends");
				}
			},
			computeInitialValueIndex: values => values.indexOf(initialBendLineEngravingMode),
		}),
		createSpinBoxRow({
			key: engravingLengthKey,
			name: wsi4.util.translate("engraving_length"),
			min: 0,
			max: 1e7,
			initialValue: initialEngravingLength,
			decimals: 0,
		}),
	]);
	if (dialogResult === undefined) {
		// User canceled
		return;
	}

	const selectedEngravingMode = dialogResult.values[engravingModeFormKey];
	assert(isBendLineEngravingMode(selectedEngravingMode));

	const selectedEngravingLength = dialogResult.values[engravingLengthKey];
	assert(isNumber(selectedEngravingLength));

	writeSetting("defaultBendLineEngravingMode", selectedEngravingMode);
	writeSetting("bendLineEngravingLength", selectedEngravingLength);

	let sharedData = wsi4.sharedData.read();
	sharedData = createUpdatedSharedData("defaultBendLineEngravingMode", selectedEngravingMode, sharedData);
	sharedData = createUpdatedSharedData("bendLineEngravingLength", selectedEngravingLength, sharedData);
	wsi4.sharedData.write(sharedData);
}

export function editCompletionTimeMode(): void {
	const modeFormKey = "mode";

	const modes = exhaustiveStringTuple<CompletionTimeMode>()(
		"maximalPath",
		"maximalNodeInclManufacturingTime",
		"maximalNodeExclManufacturingTime",
	);

	const initialMode = getSettingOrDefault("completionTimeMode");

	const dialogResult = showFormWidget([
		createDropDownRow<CompletionTimeMode>({
			key: modeFormKey,
			name: wsi4.util.translate("mode"),
			values: modes,
			toId: mode => mode,
			toName: mode => {
				switch (mode) {
					case "maximalPath": return wsi4.util.translate("maximal_path");
					case "maximalNodeInclManufacturingTime": return wsi4.util.translate("maximal_node_incl_manufacturing_time");
					case "maximalNodeExclManufacturingTime": return wsi4.util.translate("maximal_node_excl_manufacturing_time");
				}
			},
			computeInitialValueIndex: values => values.indexOf(initialMode),
		}),
	]);
	if (dialogResult === undefined) {
		// User canceled
		return;
	}

	const selectedMode = dialogResult.values[modeFormKey];
	assert(isCompletionTimeMode(selectedMode));

	const table = createUpdatedTable("completionTimeMode", selectedMode);
	setInternalTable(TableType.setting, table);
	wsi4.tables.commit();
}

export function showExportSettingsDialog(): void {
	const currentExportSettings = readExportSettings();
	const translations: {[index in keyof ExportSettings]: string} = {
		threeDimStepGenerated: wsi4.util.translate("ThreeDimStepGenerated"),
		threeDimStepInput: wsi4.util.translate("ThreeDimStepInput"),
		twoDimDxfCutting: wsi4.util.translate("TwoDimDxfCutting"),
		twoDimDxfBending: wsi4.util.translate("TwoDimDxfBending"),
		twoDimSvg: wsi4.util.translate("TwoDimSvg"),
		twoDimGeoCutting: wsi4.util.translate("TwoDimGeoCutting"),
		twoDimGeoBending: wsi4.util.translate("TwoDimGeoBending"),
		bendDrawing: wsi4.util.translate("bend_drawing"),
		bendDrawingDocx: wsi4.util.translate("bend_drawing_docx"),
		coatingDetails: wsi4.util.translate("coating_drawing"),
		threeDimPng: wsi4.util.translate("ThreeDimPng"),
		threeDimHtml: wsi4.util.translate("ThreeDimHtml"),
		joiningInstructions: wsi4.util.translate("JoiningInstructions"),
		jobCards: wsi4.util.translate("JobCards"),
		jobCardsDocx: wsi4.util.translate("jobcards_docx"),
		calculation: wsi4.util.translate("Calculation"),
		quotation: wsi4.util.translate("Quotation"),
		subGraphs: wsi4.util.translate("sub_graphs"),
		erpInputData: wsi4.util.translate("ErpInputData"),
		bomCsv: wsi4.util.translate("bill_of_materials_csv"),
		createArticleDir: wsi4.util.translate("create_article_directory"),
		nameElisionMode: wsi4.util.translate("name_elision_mode"),
		nameElisionThreshold: wsi4.util.translate("name_elision_threshold"),
	};

	// This defines the order of the form widget rows
	const sortedKeys = exhaustiveStringTuple<keyof ExportSettings>()(
		"twoDimDxfCutting",
		"twoDimDxfBending",
		"twoDimSvg",
		"twoDimGeoCutting",
		"twoDimGeoBending",
		"threeDimStepGenerated",
		"threeDimStepInput",
		"threeDimPng",
		"threeDimHtml",
		"bendDrawing",
		"bendDrawingDocx",
		"coatingDetails",
		"joiningInstructions",
		"jobCards",
		"jobCardsDocx",
		"calculation",
		"quotation",
		"subGraphs",
		"erpInputData",
		"bomCsv",
		"createArticleDir",
		"nameElisionMode",
		"nameElisionThreshold",
	);

	const createCheckBoxConfig = <Key extends keyof ExportSettings>(key: Key, initialValue: boolean) => createCheckBoxRow(
		key,
		translations[key],
		initialValue,
	);

	const elisionModeObj: {[key in ElisionMode]: undefined} = {
		none: undefined,
		left: undefined,
		middle: undefined,
		right: undefined,
	};
	const elisionModes = getKeysOfObject(elisionModeObj);

	const createElisionModeConfig = <Key extends keyof ExportSettings>(key: Key, initialValue: ElisionMode): FormRowConfig => createDropDownRow({
		key: key,
		name: translations[key],
		values: elisionModes,
		toId: (arg: string) => arg,
		toName: (arg: string) => arg,
		computeInitialValueIndex: args => args.findIndex(arg => arg === initialValue),

	});

	const createIntSpinBoxConfig = <Key extends keyof ExportSettings>(key: Key, initialValue: number) => createSpinBoxRow({
		key: key,
		name: translations[key],
		min: 1,
		max: Number.MAX_SAFE_INTEGER,
		decimals: 0,
		initialValue: initialValue,
	});

	// eslint-disable-next-line array-callback-return
	const dialogResult = showFormWidget(sortedKeys.map(key => {
		switch (key) {
			case "twoDimDxfCutting": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "twoDimDxfBending": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "twoDimSvg": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "twoDimGeoCutting": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "twoDimGeoBending": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "threeDimStepGenerated": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "threeDimStepInput": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "threeDimPng": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "threeDimHtml": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "bendDrawing": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "bendDrawingDocx": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "coatingDetails": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "joiningInstructions": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "jobCards": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "calculation": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "quotation": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "subGraphs": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "erpInputData": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "bomCsv": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "createArticleDir": return createCheckBoxConfig(key, currentExportSettings[key]);
			case "nameElisionMode": return createElisionModeConfig(key, currentExportSettings[key]);
			case "nameElisionThreshold": return createIntSpinBoxConfig(key, currentExportSettings[key]);
			case "jobCardsDocx": return createCheckBoxConfig(key, currentExportSettings[key]);
		}
	}));
	if (dialogResult === undefined) {
		// User canceled
		return;
	}

	const newExportSettings = dialogResult.values;
	assert(isExportSettings(newExportSettings), "Expecting valid export settings");
	writeExportSettings(newExportSettings);
}

export function showCalcSettingsDialog(): void {
	const currentCalcSettings = readCalcSettings();

	const translations: {[index in keyof CalcSettings]: string} = {
		scaleValueMode: wsi4.util.translate("scale_value_mode"),
		baseScaleValues: wsi4.util.translate("base_scale_values"),
	};

	const sortIndexMap: {[index in keyof CalcSettings]: number} = {
		scaleValueMode: 0,
		baseScaleValues: 1,
	};

	const createNumberArrayConfig = <Key extends keyof CalcSettings>(key: Key, initialValue: number[]) => {
		assert(isArray(initialValue, isNumber));
		return createLineEditRow(key, translations[key], initialValue.join(", "));
	};

	const dialogResult = showFormWidget(getKeysOfObject(sortIndexMap)
		.sort((lhs, rhs) => sortIndexMap[lhs] - sortIndexMap[rhs])
		// eslint-disable-next-line array-callback-return
		.map((key): FormRowConfig => {
			switch (key) {
				case "baseScaleValues": return createNumberArrayConfig(key, currentCalcSettings[key]);
				case "scaleValueMode": {
					const values = exhaustiveStringTuple<ScaleValueMode>()(
						"none",
						"relative",
						"absolute",
					);
					return createDropDownRow<ScaleValueMode>({
						key: key,
						name: translations[key],
						values: values,
						toId: mode => mode,
						toName: mode => {
							switch (mode) {
								case "none": return "-";
								case "relative": return wsi4.util.translate("relative");
								case "absolute": return wsi4.util.translate("absolute");
							}
						},
						computeInitialValueIndex: () => values.indexOf(currentCalcSettings[key]),
					});
				}
			}
		}));

	if (dialogResult === undefined) {
		// User canceled
		return;
	}

	const convertNumberArray = (arg: unknown): number[] | undefined => {
		assert(isString(arg), "Dialog result invalid");
		const value = parseJson("[" + arg + "]", (arg: unknown): arg is number[] => isArray(arg, isNumber));
		if (value === undefined) {
			showError(wsi4.util.translate("value_invalid"), wsi4.util.translate("expecting_comma_separated_numbers"));
			return undefined;
		} else {
			return value;
		}
	};

	const baseScaleValues = convertNumberArray(dialogResult.values["baseScaleValues"]);
	if (baseScaleValues === undefined) {
		return;
	}

	const selectedMode = dialogResult.values["scaleValueMode"];
	assert(isScaleValueMode(selectedMode));

	const newCalcSettings: CalcSettings = {
		scaleValueMode: selectedMode,
		baseScaleValues: baseScaleValues,
	};
	writeCalcSettings(newCalcSettings);
}

export function editTestReportSettings(): void {
	const result = showFormWidget(
		[
			createCheckBoxRow(
				"enabled",
				wsi4.util.translate("enabled"),
				getSettingOrDefault("sheetTestReportEnabled"),
			),
			createSpinBoxRow({
				key: "costs",
				name: wsi4.util.translate("costs"),
				min: 0,
				max: Number.MAX_SAFE_INTEGER,
				decimals: 2,
				initialValue: getSettingOrDefault("sheetTestReportCosts"),
			}),
		],
	);
	if (result === undefined) {
		// User canceled
		return;
	}
	const enabled = result.values["enabled"];
	const costs = result.values["costs"];
	if (!isBoolean(enabled) || !isNumber(costs)) {
		return wsi4.throwError("Result invalid");
	}
	let table = Array.from(getTable(TableType.setting));
	table = createUpdatedTable("sheetTestReportEnabled", enabled, table);
	table = createUpdatedTable("sheetTestReportCosts", costs, table);
	setInternalTable(TableType.setting, table);
	wsi4.tables.commit();
}

export function editFixedRotationSettings(): void {
	const result = showFormWidget(
		[
			createCheckBoxRow(
				"enabled",
				wsi4.util.translate("enabled"),
				getSettingOrDefault("sheetCuttingFixedRotationsEnabled"),
			),
		],
	);
	if (result === undefined) {
		// User canceled
		return;
	}
	const enabled = result.values["enabled"];
	if (!isBoolean(enabled)) {
		return wsi4.throwError("Result invalid");
	}
	const table = createUpdatedTable("sheetCuttingFixedRotationsEnabled", enabled, Array.from(getTable(TableType.setting)));
	setInternalTable(TableType.setting, table);
	wsi4.tables.commit();
}

export function editLaserSheetCuttingMovementParameters(): void {
	const result = showFormWidget(
		[
			createSpinBoxRow({
				key: "vMax",
				name: wsi4.util.translate("max_speed_in_m_per_min"),
				initialValue: getSettingOrDefault("laserSheetCuttingVMax"),
				min: 0.01,
				max: Number.MAX_VALUE,
				decimals: 3,
			}),
			createSpinBoxRow({
				key: "aMax",
				name: wsi4.util.translate("max_acceleration_in_m_per_s_pow_2"),
				initialValue: getSettingOrDefault("laserSheetCuttingAMax"),
				min: 0.01,
				max: Number.MAX_VALUE,
				decimals: 3,
			}),
		],
	);
	if (result === undefined) {
		// User canceled
		return;
	}

	const vMax = result.values["vMax"];
	const aMax = result.values["aMax"];
	if (!isNumber(vMax) || !isNumber(aMax)) {
		return wsi4.throwError("Result invalid");
	}

	const newSettingsTable = (() => {
		let table = getMutableTable(TableType.setting);
		table = createUpdatedTable("laserSheetCuttingVMax", vMax, table);
		table = createUpdatedTable("laserSheetCuttingAMax", aMax, table);
		return table;
	})();

	setInternalTable(TableType.setting, newSettingsTable);
	wsi4.tables.commit();
}

export function editManualMechanicalDeburringParameters(): void {
	const result = showFormWidget(
		[
			createSpinBoxRow({
				key: "speed",
				name: wsi4.util.translate("speed_in_m_per_min"),
				initialValue: getSettingOrDefault("manualMechanicalDeburringSpeed"),
				min: 0.01,
				max: Number.MAX_VALUE,
				decimals: 3,
			}),
		],
	);
	if (result === undefined) {
		// User canceled
		return;
	}
	const speed = result.values["speed"];
	if (!isNumber(speed)) {
		return wsi4.throwError("Result invalid");
	}
	const table = createUpdatedTable("manualMechanicalDeburringSpeed", speed, Array.from(getTable(TableType.setting)));
	setInternalTable(TableType.setting, table);
	wsi4.tables.commit();
}

export function editLocalNestorSettings(): void {
	const sheetNestingMode = getSettingOrDefault("sheetNestingMode");
	if (sheetNestingMode !== "clientDecision") {
		const text = (() => {
			switch (sheetNestingMode) {
				case "individual": return `${wsi4.util.translate("individual")} (${wsi4.util.translate("database_setting")})`;
				case "mixed": return `${wsi4.util.translate("mixed")} (${wsi4.util.translate("database_setting")})`;
			}
		})();
		showFormWidget(
			[
				createLabelRow(
					"sheetNodeMergingEnabled",
					wsi4.util.translate("nesting_mode") + ": ",
					text,
				),
			],
		);
		return;
	}

	const result = showFormWidget(
		[
			createCheckBoxRow(
				"sheetNodeMergingEnabled",
				wsi4.util.translate("merge_compatible_sheet_nodes"),
				readSetting("sheetMergingEnabled"),
			),
		],
	);
	if (result === undefined) {
		// User canceled
		return;
	}
	const sheetNodeMergingEnabled = result.values["sheetNodeMergingEnabled"];
	assert(isBoolean(sheetNodeMergingEnabled), "Dialog result invalid");

	writeSetting("sheetMergingEnabled", sheetNodeMergingEnabled);
	wsi4.sharedData.write(
		createUpdatedSharedData(
			"sheetMergingEnabled",
			sheetNodeMergingEnabled,
		),
	);
}

export function editBendFlangeSafetyDistance(): void {
	const key = "distance";
	const result = showFormWidget(
		[
			createSpinBoxRow({
				key: key,
				name: wsi4.util.translate("distance") + " [mm]",
				initialValue: getSettingOrDefault("bendFlangeSafetyDistance"),
				min: 0.,
				max: Number.MAX_VALUE,
				decimals: 2,
			}),
		],
	);
	if (result === undefined) {
		// User canceled
		return;
	}
	const safetyDistance = result.values[key];
	assert(isNumber(safetyDistance), "Dialog result invalid");

	const table = createUpdatedTable("bendFlangeSafetyDistance", safetyDistance, getMutableTable(TableType.setting));
	setInternalTable(TableType.setting, table);
	wsi4.tables.commit();
}

export function editCsvLocale(): void {
	const key = "locale";

	const values = Array.from(new Set([
		getSettingOrDefault("csvLocale"),
		wsi4.locale.system(),
		"en_US",
		"de_DE",
	]))
		.map(value => ({
			id: value,
			name: value,
		}));

	const result = showFormWidget(
		[
			createDropDownRow({
				key: key,
				name: wsi4.util.translate("csv_locale"),
				values: values,
				toId: item => item.id,
				toName: item => item.name,
				computeInitialValueIndex: () => 0,
			}),
		],
	);
	if (result === undefined) {
		// User canceled
		return;
	}
	const csvLocale = result.values[key];
	assert(isString(csvLocale), "Dialog result invalid");

	const table = createUpdatedTable("csvLocale", csvLocale, getMutableTable(TableType.setting));
	setInternalTable(TableType.setting, table);
	wsi4.tables.commit();
}

export function editDatabaseNestorSettings(): void {
	const modeKey = "mode";
	const distanceKey = "distance";

	const result = showFormWidget(
		[
			createDropDownRow<SheetNestingMode>({
				key: modeKey,
				name: wsi4.util.translate("nesting_mode"),
				values: exhaustiveStringTuple<SheetNestingMode>()("clientDecision", "individual", "mixed"),
				toId: item => item,
				toName: item => {
					switch (item) {
						case "clientDecision": return "--";
						case "individual": return wsi4.util.translate("individual");
						case "mixed": return wsi4.util.translate("mixed");
					}
				},
				computeInitialValueIndex: values => {
					const currentValue = getSettingOrDefault("sheetNestingMode");
					return values.indexOf(currentValue);
				},
			}),
			createSpinBoxRow({
				key: distanceKey,
				name: wsi4.util.translate("nesting_distance") + " [mm]",
				initialValue: getSettingOrDefault("sheetNestingDistance"),
				min: 0.01,
				max: Number.MAX_VALUE,
				decimals: 3,
			}),
		],
	);
	if (result === undefined) {
		// User canceled
		return;
	}

	const nestingMode = result.values[modeKey];
	assert(isSheetNestingMode(nestingMode), "Dialog result invalid");

	const nestingDistance = result.values[distanceKey];
	assert(isNumber(nestingDistance), "Dialog result invalid");

	switch (nestingMode) {
		case "clientDecision": wsi4.sharedData.write(createUpdatedSharedData("sheetMergingEnabled", readSetting("sheetMergingEnabled"))); break;
		case "individual": wsi4.sharedData.write(createUpdatedSharedData("sheetMergingEnabled", false)); break;
		case "mixed": wsi4.sharedData.write(createUpdatedSharedData("sheetMergingEnabled", true)); break;
	}

	let table = getMutableTable("setting");
	table = createUpdatedTable("sheetNestingMode", nestingMode, table);
	table = createUpdatedTable("sheetNestingDistance", nestingDistance, table);
	setInternalTable(TableType.setting, table);

	wsi4.tables.commit();
}

export function editDieBendingCalcConfig(): void {
	const formKey = "key";
	const result = showFormWidget(
		[
			createCheckBoxRow(
				formKey,
				wsi4.util.translate("distribute_die_bending_setup_time"),
				getSettingOrDefault("dieBendingSetupTimeDistributionEnabled"),
			),
		],
	);
	if (result === undefined) {
		// User canceled
		return;
	}
	const value = result.values[formKey];
	assert(isBoolean(value), "Dialog result invalid");

	const newSettingsTable = (() => {
		let table = getMutableTable(TableType.setting);
		table = createUpdatedTable("dieBendingSetupTimeDistributionEnabled", value, table);
		return table;
	})();

	setInternalTable(TableType.setting, newSettingsTable);
	wsi4.tables.commit();
}

export function createLProfilePart(): void {
	const minDim = 0.1;
	const maxDim = 1000000;
	const result = showFormWidget(
		[
			createSpinBoxRow({
				key: "thickness",
				name: wsi4.util.translate("SheetThickness"),
				initialValue: legacyReadSetting("create_l_profile_thickness", 1, isNumber),
				min: minDim,
				max: maxDim,
				decimals: 2,
			}),
			createSpinBoxRow({
				key: "innerRadius",
				name: wsi4.util.translate("inner_radius"),
				initialValue: legacyReadSetting("create_l_profile_inner_radius", 1, isNumber),
				min: minDim,
				max: maxDim,
				decimals: 2,
			}),
			createSpinBoxRow({
				key: "xFlangeLength",
				name: wsi4.util.translate("flange_length_x"),
				initialValue: legacyReadSetting("create_l_profile_x_flange_length", 100, isNumber),
				min: minDim,
				max: maxDim,
				decimals: 2,
			}),
			createSpinBoxRow({
				key: "yFlangeLength",
				name: wsi4.util.translate("flange_length_y"),
				initialValue: legacyReadSetting("create_l_profile_y_flange_length", 100, isNumber),
				min: minDim,
				max: maxDim,
				decimals: 2,
			}),
			createSpinBoxRow({
				key: "extrusionDepth",
				name: wsi4.util.translate("extrusion_depth"),
				initialValue: legacyReadSetting("create_l_profile_extrusion_depth", 100, isNumber),
				min: minDim,
				max: maxDim,
				decimals: 2,
			}),
			createLineEditRow(
				"name",
				wsi4.util.translate("Name"),
				legacyReadSetting("create_l_profile_name", "", isString),
			),
		],
	);
	if (result === undefined) {
		// User canceled
		return;
	}

	const thickness = result.values["thickness"];
	const innerRadius = result.values["innerRadius"];
	const xFlangeLength = result.values["xFlangeLength"];
	const yFlangeLength = result.values["yFlangeLength"];
	const extrusionDepth = result.values["extrusionDepth"];
	const partName = result.values["name"];

	if (!isNumber(thickness) || !isNumber(innerRadius) || !isNumber(xFlangeLength) || !isNumber(yFlangeLength) || !isNumber(extrusionDepth) || !isString(partName)) {
		wsi4.util.error("createLProfilePart():  Dialog result invalid");
		return;
	}

	legacyWriteSetting("create_l_profile_thickness", result.values["thickness"]);
	legacyWriteSetting("create_l_profile_inner_radius", result.values["innerRadius"]);
	legacyWriteSetting("create_l_profile_x_flange_length", result.values["xFlangeLength"]);
	legacyWriteSetting("create_l_profile_y_flange_length", result.values["yFlangeLength"]);
	legacyWriteSetting("create_l_profile_extrusion_depth", result.values["extrusionDepth"]);
	legacyWriteSetting("create_l_profile_name", result.values["name"]);

	const profile: LProfile = {
		thickness: thickness,
		innerRadius: innerRadius,
		outerRadius: innerRadius + thickness,
		xFlangeLength: xFlangeLength,
		yFlangeLength: yFlangeLength,
	};

	const iop = createLProfileIop(profile);
	if (iop === undefined) {
		wsi4.util.error("createLProfilePart():  Profile invalid");
	} else {
		addExtrusion(iop, extrusionDepth, partName);
	}
}

export function tubeTablesDialogInit(): PrivateTubeTablesEditorInitData {
	return {
		tubeMaterials: Array.from(getTable(TableType.tubeMaterial)),
		tubeMaterialDensities: Array.from(getTable(TableType.tubeMaterialDensity)),
		tubeProfiles: Array.from(getTable(TableType.tubeProfile)),
		tubeSpecifications: Array.from(getTable(TableType.tubeSpecification)),
		tubes: Array.from(getTable(TableType.tube)),
	};
}

export function sheetTablesDialogInit(): PrivateSheetTablesEditorInitData {
	return {
		sheetMaterials: Array.from(getTable(TableType.sheetMaterial)),
		sheetMaterialDensities: Array.from(getTable(TableType.sheetMaterialDensity)),
		sheetMaterialScrapValues: Array.from(getTable(TableType.sheetMaterialScrapValue)),
		sheetCuttingMaterialMappings: Array.from(getTable(TableType.sheetCuttingMaterialMapping)),
		sheetBendingMaterialMappings: Array.from(getTable(TableType.sheetBendingMaterialMapping)),
		sheets: Array.from(getTable(TableType.sheet)),
		sheetPrices: Array.from(getTable(TableType.sheetPrice)),
		sheetStocks: Array.from(getTable(TableType.sheetStock)),
		sheetModuli: Array.from(getTable(TableType.sheetModulus)),
		sheetPriorities: Array.from(getTable(TableType.sheetPriority)),
		sheetCuttingMaterials: Array.from(getTable(TableType.sheetCuttingMaterial)),
		sheetBendingMaterials: Array.from(getTable(TableType.sheetBendingMaterial)),
	};
}

function showTubeSelectionDialog(): [Tube, number, string] | undefined {
	const tubeKey = "tube";
	const dimXKey = "dimX";
	const nameKey = "name";

	const tubes = getTable(TableType.tube);
	if (tubes.length === 0) {
		wsi4.util.warn("Tube table empty; cannot create user-defined tube");
		return undefined;
	}

	const tubeWidgetRow = createDropDownRow({
		key: tubeKey,
		name: wsi4.util.translate("tube"),
		values: tubes,
		toId: tube => tube.identifier,
		toName: tube => tube.name,
		computeInitialValueIndex: () => 0,
	});

	const maxDimX = tubes.reduce((acc, tube) => Math.max(acc, tube.dimX), 0);
	const dimXWidgetRow = createSpinBoxRow({
		key: dimXKey,
		name: wsi4.util.translate("Length"),
		initialValue: 1000,
		min: 100,
		max: maxDimX,
		decimals: 2,
	});

	const nameWidgetRow = createLineEditRow(
		nameKey,
		wsi4.util.translate("Name"),
		"",
	);

	const dialogResult = showFormWidget([
		tubeWidgetRow,
		dimXWidgetRow,
		nameWidgetRow,
	]);

	if (dialogResult === undefined) {
		// User canceled
		return undefined;
	} else {
		const selectedTubeId = dialogResult.values[tubeKey];
		assert(isString(selectedTubeId), "Dialog result invalid");

		const selectedDimX = dialogResult.values[dimXKey];
		assert(isNumber(selectedDimX), "Dialog result invalid");

		const selectedName = dialogResult.values[nameKey];
		assert(isString(selectedName), "Dialog result invalid");

		const selectedTube = tubes.find(row => row.identifier === selectedTubeId);
		assert(selectedTube !== undefined, "Expecting valid tube");

		return [
			selectedTube,
			selectedDimX,
			selectedName,
		];
	}
}

export function createTube(): void {
	const selectedTubeProps = showTubeSelectionDialog();
	if (selectedTubeProps === undefined) {
		return;
	}

	const [
		tube,
		dimX,
		name,
	] = selectedTubeProps;

	const success = addUserDefinedTubeNode(tube.identifier, dimX, name);
	assert(success, "Unexpected error for user define tube node");
}

export function showArticleNameElisionDialog(): void {
	const modeKey = "mode";
	const maxLengthKey = "threshold";

	const modeSettingsKey = "article_name_elision_dialog_mode";
	const maxLengthSettingsKey = "article_name_elision_dialog_threshold";

	const elisionModes = (() => {
		const obj: {[key in ElisionMode]: null} = {
			none: null,
			left: null,
			middle: null,
			right: null,
		};
		return getKeysOfObject(obj);
	})();

	const modeDropDown = createDropDownRow({
		key: modeKey,
		name: wsi4.util.translate("name_elision_mode"),
		values: elisionModes,
		toId: (arg: string) => arg,
		toName: (arg: string) => arg,
		computeInitialValueIndex: args => {
			const initialValue = legacyReadSetting<ElisionMode>(modeSettingsKey, "none", isElisionMode);
			return args.findIndex(arg => arg === initialValue);
		},
	});

	const articles: readonly Readonly<readonly Vertex[]>[] = wsi4.graph.articles();
	const minLength = computeNameMaxLengthLowerBound(articles.length);

	const maxLengthSpinbox = createSpinBoxRow({
		key: maxLengthKey,
		name: wsi4.util.translate("max_length"),
		min: minLength,
		max: Number.MAX_SAFE_INTEGER,
		decimals: 0,
		initialValue: Math.max(
			minLength,
			legacyReadSetting(maxLengthSettingsKey, 10, isNumber),
		),
	});

	const dialogResult = showFormWidget([
		modeDropDown,
		maxLengthSpinbox,
	]);

	if (dialogResult === undefined) {
		// User canceled
		return;
	}

	const selectedMode = dialogResult.values[modeKey];
	assert(isElisionMode(selectedMode), "Dialog result invalid");
	legacyWriteSetting(modeSettingsKey, selectedMode);

	const selectedMaxLength = dialogResult.values[maxLengthKey];
	assert(isNumber(selectedMaxLength), "Dialog result invalid");
	legacyWriteSetting(maxLengthSettingsKey, selectedMaxLength);

	const currentNames = articles.map(article => getArticleName(article[0]!));

	const shortenedNames = computeUniqueNames(
		currentNames,
		[],
		{
			elisionMode: selectedMode,
			maxLength: selectedMaxLength,
		},
	);

	const verticesWithNames = shortenedNames
		.map((newName, index): [Vertex, string] => [
			articles[index]![0]!,
			newName,
		])
		.filter((tuple, index) => currentNames[index] !== tuple[1]);

	changeArticleNames(verticesWithNames);
}

export function editGraphRepResourcesConfig(): void {
	const currentConfig = getSettingGraphRepResourcesConfig();

	const nameMap: {[index in keyof GraphRepResourcesConfig]: string} = {
		bendDrawingHtmls: wsi4.util.translate("bend_drawing") + " HTMLs",
		dxfs: "DXFs",
		dxfsCompressed: `DXFs (${wsi4.util.translate("compressed")})`,
		geos: "GEOs",
		geosCompressed: `GEOs (${wsi4.util.translate("compressed")})`,
		svgs: "SVGs",
		svgsCompressed: `SVGs (${wsi4.util.translate("compressed")})`,
		inputSteps: `STEPs (${wsi4.util.translate("loaded")})`,
		inputStepsCompressed: `STEPs (${wsi4.util.translate("loaded")}, ${wsi4.util.translate("compressed")})`,
		outputSteps: `STEPs (${wsi4.util.translate("generated")})`,
		outputStepsCompressed: `STEPs (${wsi4.util.translate("generated")}, ${wsi4.util.translate("compressed")})`,
		pngs: "PNGs",
		attachments: wsi4.util.translate("attachments"),
		subGraphs: wsi4.util.translate("sub_graphs"),
	};

	const dialogResult = showFormWidget(
		getKeysOfObject(nameMap)
			.map(key => createCheckBoxRow(key, nameMap[key], currentConfig[key])),
	);

	if (dialogResult === undefined) {
		// User canceled
		return;
	} else {
		const newConfig = dialogResult.values;
		assert(isGraphRepResourcesConfig(newConfig), "Dialog result invalid");
		setInternalTable(
			TableType.setting,
			createUpdatedTable("graphRepResourcesConfig", newConfig),
		);
		wsi4.tables.commit();
	}
}

export function editAutomaticProcessConfig(): void {
	type Value = DeburringMode | "none";
	const values = exhaustiveStringTuple<Value>()(
		"none",
		"automaticSingleSided",
		"automaticDoubleSided",
		"manualSingleSided",
		"manualDoubleSided",
	);

	const deburringModeFormKey = "deburringMode";
	const dialogResult = showFormWidget([
		createDropDownRow<Value>({
			key: deburringModeFormKey,
			name: wsi4.util.translate(ProcessType.mechanicalDeburring),
			values: values,
			toId: value => value,
			toName: value => {
				switch (value) {
					case "none": return "-";
					case "automaticSingleSided": return wsi4.util.translate(ProcessType.automaticMechanicalDeburring);
					case "automaticDoubleSided": return wsi4.util.translate(ProcessType.automaticMechanicalDeburring) + "(" + wsi4.util.translate("double_sided") + ")";
					case "manualSingleSided": return wsi4.util.translate(ProcessType.manualMechanicalDeburring);
					case "manualDoubleSided": return wsi4.util.translate(ProcessType.manualMechanicalDeburring) + "(" + wsi4.util.translate("double_sided") + ")";
				}
			},
			computeInitialValueIndex: values => {
				const currentConfig = getSharedDataEntry("automaticProcessConfig");
				const currentMode: Value = currentConfig.deburringMode ?? "none";
				return values.indexOf(currentMode);
			},
		}),
	]);

	if (dialogResult === undefined) {
		// User canceled
		return;
	}

	const newConfig: AutomaticProcessConfig = {};

	{
		const selectedDeburringMode = dialogResult.values[deburringModeFormKey];
		assert(selectedDeburringMode === "none" || isDeburringMode(selectedDeburringMode));
		if (selectedDeburringMode !== "none") {
			newConfig.deburringMode = selectedDeburringMode;
		}
	}

	writeSetting("automaticProcessConfig", newConfig);
	wsi4.sharedData.write(createUpdatedSharedData("automaticProcessConfig", newConfig));
}

export function editErpInterfaceVersion(): void {
	type Value = "latest" | ErpInterfaceVersion;
	const values: Value[] = [
		"latest",
		...exhaustiveStringTuple<ErpInterfaceVersion>()("v0", "v1"),
	];
	const resultKey = "version";
	const dialogResult = showFormWidget([
		createDropDownRow<Value>({
			key: resultKey,
			name: wsi4.util.translate("version"),
			values: values,
			toId: value => value,
			toName: value => {
				switch (value) {
					case "latest": return wsi4.util.translate("latest_version");
					default: return value;
				}
			},
			computeInitialValueIndex: () => {
				const currentValue = getSettingOrDefault("erpInterfaceVersion");
				switch (currentValue) {
					case null: return values.findIndex(value => value === "latest");
					default: return values.findIndex(value => value === currentValue);
				}
			},
		}),
	]);

	if (dialogResult === undefined) {
		// User canceled
		return;
	}

	const newValue = (() => {
		const value = dialogResult.values[resultKey];
		assert(isErpInterfaceVersion(value) || value === "latest");
		switch (value) {
			case "latest": return null;
			default: return value;
		}
	})();

	setInternalTable(TableType.setting, createUpdatedTable("erpInterfaceVersion", newValue));
	wsi4.tables.commit();
}

export function editSheetScrapAreaThreshold(): void {
	const result = showFormWidget(
		[
			createSpinBoxRow({
				key: "threshold",
				name: wsi4.util.translate("min_contour_area_in_mm_pow_2"),
				initialValue: getSettingOrDefault("sheetScrapAreaThreshold"),
				min: 0,
				max: Number.MAX_VALUE,
				decimals: 0,
			}),
		],
	);
	if (result === undefined) {
		// User canceled
		return;
	}

	const threshold = result.values["threshold"];
	if (!isNumber(threshold)) {
		return wsi4.throwError("Result invalid");
	}

	const newSettingsTable = (() => {
		let table = getMutableTable(TableType.setting);
		table = createUpdatedTable("sheetScrapAreaThreshold", threshold, table);
		return table;
	})();

	setInternalTable(TableType.setting, newSettingsTable);
	wsi4.tables.commit();
}

export function showQuotationFilePathDialog(): void {
	const path = getOpenFilePath(wsi4.util.translate("quotation_template"), readSetting("quotationTemplatePath"), "*.docx");
	if (path === undefined) {
		return;
	}
	writeSetting("quotationTemplatePath", path);
}

export function showJobCardFilePathDialog(): void {
	const path = getOpenFilePath(wsi4.util.translate("job_card_template"), readSetting("jobCardTemplatePath"), "*.docx");
	if (path === undefined) {
		return;
	}
	writeSetting("jobCardTemplatePath", path);
}

export function showBendDrawingFilePathDialog(): void {
	const path = getOpenFilePath(wsi4.util.translate("bend_drawing_template"), readSetting("bendDrawingTemplatePath"), "*.docx");
	if (path === undefined) {
		return;
	}
	writeSetting("bendDrawingTemplatePath", path);
}

export function exportDefaultDocXTemplates(): void {
	const dir = getDirectoryPath(wsi4.util.translate("save_templates"));
	if (dir === undefined) {
		return;
	}
	const templates = {
		quotation: ":/templates/quotation.docx",
		jobcard: ":/templates/jobcard.docx",
		benddrawing: ":/templates/benddrawing.docx",
	};

	for (const key of getKeysOfObject(templates)) {
		const templ = wsi4.io.fs.readFile(templates[key]);
		if (templ === undefined) {
			wsi4.util.error(`Error reading ${key} template.`);
			return;
		}
		const path = dir + "/" + key + ".docx";
		if (!wsi4.io.fs.writeFile(path, templ)) {
			wsi4.util.error("writeFile(): " + wsi4.util.translate("ErrorWritingFile") + "(" + path + ")");
			return undefined;
		}

	}
}

export function exportAndOpenQuotation(): void {
	let savePath = getSaveFilePath(wsi4.util.translate("Quotation"), readSetting("lastQuotationExportDir"));
	if (savePath === undefined) {
		return;
	}
	if (!savePath.toLowerCase().endsWith(".docx")) {
		savePath += ".docx";
	}

	writeSetting("lastQuotationExportDir", savePath);

	const quotationInput = createQuotationInput();
	if (quotationInput === undefined) {
		return;
	}
	const templatePath = readSetting("quotationTemplatePath");
	const tmpl = wsi4.io.fs.readFile(templatePath === "" ? ":/templates/quotation.docx" : templatePath);
	if (tmpl === undefined) {
		wsi4.util.error("Error reading quotation template.");
		return;
	}
	const docxBa = createQuotationDocx(tmpl, quotationInput);
	if (docxBa === undefined) {
		return;
	}

	if (!wsi4.io.fs.writeFile(savePath, docxBa)) {
		wsi4.util.error("exportAndOpenQuotation(): " + wsi4.util.translate("ErrorWritingFile") + "(" + savePath + ")");
		return;
	}

	wsi4.ui.openFile(savePath);
}

export function editTubeCuttingConfiguration(): void {
	const result = showFormWidget(
		[
			createSpinBoxRow({
				key: "clampingLength",
				name: wsi4.util.translate("clamping_length") + " [mm]",
				initialValue: getSettingOrDefault("tubeClampingLength"),
				min: 0,
				max: Number.MAX_VALUE,
				decimals: 0,
			}),
		],
	);
	if (result === undefined) {
		// User canceled
		return;
	}

	const clampingLength = result.values["clampingLength"];
	if (!isNumber(clampingLength)) {
		return wsi4.throwError("Result invalid");
	}

	const table = createUpdatedTable("tubeClampingLength", clampingLength);
	setInternalTable(TableType.setting, table);

	wsi4.tables.commit();
}

export function exportErpJsonToFile(): void {
	let filePath = (() => {
		const lastPath = readSetting("lastErpJsonExportPath");
		const lastSeparatorIndex = lastPath.lastIndexOf("/");
		const lastDir = lastSeparatorIndex === -1 ? "" : (lastPath.substring(0, lastSeparatorIndex) + "/");
		const suggestion = lastDir + (getGraphUserDataEntry("name") ?? wsi4.util.translate("unknown")) + ".json";
		return getSaveFilePath(wsi4.util.translate("erp_export"), suggestion);
	})();
	if (filePath === undefined) {
		// User canceled
		return;
	}
	if (!filePath.toLowerCase().endsWith(".json")) {
		filePath += ".json";
	}
	writeSetting("lastErpJsonExportPath", filePath);

	const scaleDataConfig: ScaleDataConfig = (() => {
		const calcSettings = readCalcSettings();
		switch (calcSettings.scaleValueMode) {
			case "none": return {
				type: "none",
			};
			case "relative": return {
				type: "relativeValues",
				values: calcSettings.baseScaleValues,
			};
			case "absolute": return {
				type: "absoluteValues",
				values: calcSettings.baseScaleValues,
			};
		}
	})();
	const pngFutures = createPngFutures(wsi4.graph.vertices(), defaultPngResolution());
	const graphRep = createGraphRepresentation(scaleDataConfig, pngFutures);
	const json = JSON.stringify(graphRep);
	wsi4.io.fs.writeFile(filePath, wsi4.util.stringToArrayBuffer(json));
}

export function editCadImportSettings(): void {
	const sheetUpperSideStrategyKey = "sheetUpperSideStategy";

	const sheetUpperSideStrategies = exhaustiveStringTuple<SheetUpperSideStrategy>()(
		"preferUpwardBends",
		"preferDownwardBends",
		"preferConvexity",
		"preferConcavity",
	);

	const initialSheetUpperSideStrategy = readSetting("sheetUpperSideStrategy");

	const dialogResult = showFormWidget([
		createDropDownRow<SheetUpperSideStrategy>({
			key: sheetUpperSideStrategyKey,
			name: wsi4.util.translate("sheet_upper_side"),
			values: sheetUpperSideStrategies,
			toId: mode => mode,
			toName: s => {
				switch (s) {
					case "preferUpwardBends": return wsi4.util.translate("prefer_upward_bends");
					case "preferDownwardBends": return wsi4.util.translate("prefer_downward_bends");
					case "preferConvexity": return wsi4.util.translate("prefer_convex_surface");
					case "preferConcavity": return wsi4.util.translate("prefer_concave_surface");
				}
			},
			computeInitialValueIndex: values => values.indexOf(initialSheetUpperSideStrategy),
		}),
	]);
	if (dialogResult === undefined) {
		// User canceled
		return;
	}

	const selectedSheetUpperSideStrategy = dialogResult.values[sheetUpperSideStrategyKey];
	assert(isSheetUpperSideStrategy(selectedSheetUpperSideStrategy));

	writeSetting("sheetUpperSideStrategy", selectedSheetUpperSideStrategy);
}
