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;
}

function openFileTelemetry(importIdFilePathMap: ReadonlyMap<string, string>) {
	// File types for which we log whether they were opened or not.
	// Should be extended if further file types are supported in the future.
	// We map them to the list of extensions for which we consider them a match.
	// Note that all file extensions need to be lower case here since comparison is done case insensitively by bringing the file name to lower case.
	const fileTypeKeys: [string, string[]][] = [
		[
			"file_extension_dxf",
			[ ".dxf" ],
		],
		[
			"file_extension_geo",
			[ ".geo" ],
		],
		[
			"file_extension_step",
			[
				".step",
				".stp",
			],
		],
		[
			"file_extension_wsi4",
			[ ".wsi4" ],
		],
	];
	const kv: readonly KeyValue[] = fileTypeKeys.map((p: [string, string[]]) => {
		let present = false;
		for (const v of importIdFilePathMap.values()) {
			const s = v.toLowerCase();
			if (p[1].some(ext => s.endsWith(ext))) {
				present = true;
				break;
			}
		}
		return {
			key: p[0],
			value: present ? "true" : "false",
		};
	});
	wsi4.util.telemetry("File open", kv);
}

/**
 * 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: ReadonlyMap<string, string> = new Map()): FileHandlingResult {
	openFileTelemetry(importIdFilePathMap);

	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);
}
