import {
	AddResultStatus,
} from "qrc:/js/lib/generated/enum";
import {
	showError,
	showLayeredImportDialog,
	showFormWidget,
	sheetMaterialToId,
	sheetMaterialToString,
} from "qrc:/js/lib/gui_utils";
import {
	addToGraph,
} from "qrc:/js/lib/graph_manipulator";
import {
	finalizeImportedGraph,
} from "qrc:/js/lib/graphimport";
import {
	ExternalInput,
	ExternalInputEntry,
} from "qrc:/js/lib/external_input";
import {
	loadExternalInput,
	NormalizedInputEntry,
} from "qrc:/js/lib/normalized_input";
import {
	createDropDownRow,
} from "qrc:/js/lib/gui_form_widget";
import {
	getTable,
} from "qrc:/js/lib/table_utils";
import {
	assert,
} from "qrc:/js/lib/utils";
import {
	createUpdatedSharedData,
} from "qrc:/js/lib/shared_data";
import {
	readSetting, writeSetting,
} from "./gui_local_settings";

function showPathError(path?: string) {
	showError(wsi4.util.translate("FileOpenErrorTitle"), wsi4.util.translate("FileOpenErrorMessage") + (path === undefined ? "" : "\n\n" + path));
}

export declare interface FileHandlingResult {
	success: boolean;
	path: string;
	graphUuid: string;
}

export function extractFileName(path: string): string {
	// Note: pop() is save here (also for empty string)
	return path.split("/")
		.pop()!.split("\\")
		.pop()!;
}

export function removeFileExtention(fileName: string): string {
	const lastDotIndex = fileName.lastIndexOf(".");
	return fileName.substring(0, lastDotIndex === -1 ? fileName.length : lastDotIndex);
}

/**
 * @returns true if user accepted
 */
function handleFixedUserDataQuery(): boolean {
	const sheetMaterialIdFormKey = "sheetMaterialId";
	const tubeMaterialIdFormKey = "tubeMaterialId";

	const dialogResult = showFormWidget([
		createDropDownRow<SheetMaterial>({
			key: sheetMaterialIdFormKey,
			name: wsi4.tables.name("sheetMaterial"),
			values: getTable("sheetMaterial"),
			toId: sheetMaterialToId,
			toName: sheetMaterialToString,
			computeInitialValueIndex: values => {
				const settingsValue = readSetting("defaultSheetMaterialId");
				return Math.max(0, values.findIndex(value => settingsValue !== undefined && value.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 false;
	}

	const selectedSheetMaterialId = dialogResult.values[sheetMaterialIdFormKey];
	assert(selectedSheetMaterialId === undefined || typeof selectedSheetMaterialId === "string");

	const selectedTubeMaterialId = dialogResult.values[tubeMaterialIdFormKey];
	assert(selectedTubeMaterialId === undefined || typeof selectedTubeMaterialId === "string");

	writeSetting("defaultSheetMaterialId", selectedSheetMaterialId);
	writeSetting("defaultTubeMaterialId", selectedTubeMaterialId);

	let sharedData = wsi4.sharedData.read();
	sharedData = createUpdatedSharedData("defaultSheetMaterialId", selectedSheetMaterialId, sharedData);
	sharedData = createUpdatedSharedData("defaultTubeMaterialId", selectedTubeMaterialId, sharedData);
	wsi4.sharedData.write(sharedData);

	return true;
}

/**
 * Open files submitted by the user
 *
 * For both DXF and STEP files the resulting terminating node is
 * named according to the file path's base name.
 *
 * In both cases the resulting node can be mapped to the input path
 * via the add-result's id.  In the DXF case the name is also
 * submitted to the backend via the API's Input data structure as
 * the result-id-feature has not been there from be beginning.
 */
export function openFilesImpl(inputs: NormalizedInputEntry[], importIdFilePathMap: Map<string, string> = new Map()): FileHandlingResult {
	const graphCreatorInputs: GraphCreatorInput[] = [];
	const importIdNameProposalMap = new Map<string, string>();

	let wsi4FilePath: string | null = null;
	for (const input of inputs) {
		if (input.multiplicity <= 0) {
			wsi4.throwError("Input multiplicity invalid: " + input.multiplicity.toString());
		}

		const buffer = input.data;
		const type = wsi4.classifier.classify(buffer);
		switch (type) {
			case "twoDimRep":
			case "undefined": {
				showPathError(importIdFilePathMap.get(input.importId));
				break;
			}
			case "documentGraph": {
				const importedGraph = wsi4.sl.graph.deserialize(buffer).graph;
				if (importedGraph === undefined) {
					showPathError(importIdFilePathMap.get(input.importId));
					break;
				}
				if (wsi4FilePath === null) {
					const filePath = importIdFilePathMap.get(input.importId);
					if (filePath !== undefined) {
						wsi4FilePath = filePath;
					}
				}
				const graph = finalizeImportedGraph(importedGraph, input.importId);
				wsi4.graphManipulator.merge(graph);
				break;
			}
			case "layered": {
				const dirtyLayered = wsi4.geo.util.createLayered(buffer);
				if (dirtyLayered === undefined) {
					showPathError(importIdFilePathMap.get(input.importId));
					break;
				}
				const filePath = importIdFilePathMap.get(input.importId);
				// Note: currently the input's sheet thickness is ignored.
				// It could be used as another source for the dialogs initial value.
				const dialogResult = showLayeredImportDialog(
					dirtyLayered,
					wsi4.geo.util.createLayeredExtraData(buffer),
					wsi4.util.translate("ImportLayered") + (filePath === undefined ? "" : ": " + filePath),
				);
				if (dialogResult === undefined) {
					// User canceled
					break;
				}
				const articleName = input.terminalArticleAttributes.articleName;
				const content: GraphCreatorInputTwoDimRep = {
					importId: input.importId,
					twoDimRep: dialogResult.twoDimRep,
					thickness: dialogResult.thickness,
					assemblyName: articleName ?? "",
					multiplicity: input.multiplicity,
				};
				graphCreatorInputs.push({
					type: "twoDimRep",
					content: content,
				});
				if (articleName !== undefined) {
					importIdNameProposalMap.set(input.importId, articleName);
				}
				break;
			}
			case "assembly": {
				const articleName = input.terminalArticleAttributes.articleName;
				const content: GraphCreatorInputStep = {
					importId: input.importId,
					data: buffer,
					multiplicity: input.multiplicity,
				};
				graphCreatorInputs.push({
					type: "step",
					content: content,
				});
				if (articleName !== undefined) {
					importIdNameProposalMap.set(input.importId, articleName);
				}
				break;
			}
		}

	}

	if (graphCreatorInputs.length === 0) {
		return {
			success: true,
			path: "",
			graphUuid: wsi4.graph.currentUuid(),
		};
	}

	if (readSetting("initialUserDataValueMode") === "query") {
		const accepted = handleFixedUserDataQuery();
		if (!accepted) {
			return {
				success: true,
				path: "",
				graphUuid: wsi4.graph.currentUuid(),
			};
		}
	}

	const cadImportConfig: CadImportConfig = {
		sheetUpperSideStrategy: readSetting("sheetUpperSideStrategy"),
	};

	const result = addToGraph(graphCreatorInputs, cadImportConfig, importIdNameProposalMap);
	const failed = result.filter(r => r.status !== AddResultStatus.success);
	if (failed.length > 0) {
		showError(wsi4.util.translate("FileOpenErrorTitle"), wsi4.util.translate("FileOpenErrorMessage") + "\n\n" + failed.map(f => importIdFilePathMap.get(f.importId)).join("\n"));
	}

	return {
		success: true,
		path: wsi4FilePath ?? "",
		graphUuid: wsi4.graph.currentUuid(),
	};
}

export function openFiles(paths: string[]): FileHandlingResult {
	const externalInputEntries: ExternalInputEntry[] = [];
	const importIdFilePathMap: Map<string, string> = new Map();
	for (const path of paths) {
		if (!wsi4.io.fs.exists(path)) {
			showPathError(path);
			continue;
		}
		const importId = wsi4.util.createUuid();
		externalInputEntries.push({
			dataSource: {
				tag: "filePath",
				path: path,
			},
			importId: importId,
			articleName: removeFileExtention(extractFileName(path)),
		});
		importIdFilePathMap.set(importId, path);
	}
	const loadedEntries = loadExternalInput(externalInputEntries);
	return openFilesImpl(loadedEntries, importIdFilePathMap);
}

// Serialize graph and write serialization to file system
export function saveFile(path: string): FileHandlingResult {
	const success = wsi4.io.fs.writeFile(path, wsi4.graph.serialize());
	return {
		success: success,
		path: path,
		graphUuid: wsi4.graph.currentUuid(),
	};
}

export function loadInputInGui(externalInput: Readonly<ExternalInput>): FileHandlingResult {
	const loadedInputs = loadExternalInput(externalInput.entries);
	return openFilesImpl(loadedInputs);
}
