// Note: All functions in this module must conform to DocumentGraphHandler's script engine API.
import {
	AddResultStatus,
	InputType,
} from "qrc:/js/lib/generated/enum";
import {
	isInput,
	isInputContentAssembly,
	isInputContentDocumentGraph,
	isInputContentLayered,
} from "qrc:/js/lib/generated/typeguard";
import {
	addToGraph,
} from "qrc:/js/lib/graph_manipulator";
import {
	showError,
	showLayeredImportDialog,
} from "qrc:/js/lib/gui_utils";
import {
	assert,
	assertDebug,
} from "qrc:/js/lib/utils";

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

function showAddingError(path: string, status: AddResultStatus) {
	assertDebug(() => status !== AddResultStatus.success);
	let errorMessage = wsi4.util.translate("FileOpenErrorMessage") + ":\n\n" + path;
	switch (status) {
		case AddResultStatus.inconsistentGraph: {
			errorMessage += "\n\n" + wsi4.util.translate("file_adding_error_description_inconsistent_documentgraph");
			break;
		}
		case AddResultStatus.unsupportedFormat: {
			errorMessage += "\n\n" + wsi4.util.translate("file_adding_error_description_unsupported_format");
			break;
		}
		case AddResultStatus.unsupportedVersion: {
			errorMessage += "\n\n" + wsi4.util.translate("file_adding_error_description_unsupported_version");
			break;
		}
		case AddResultStatus.versionMismatch: {
			errorMessage += "\n\n" + wsi4.util.translate("file_adding_error_description_version_mismatch");
			break;
		}
		case AddResultStatus.undefinedError:break;
		case AddResultStatus.success:break;
	}
	showError(wsi4.util.translate("FileOpenErrorTitle"), errorMessage);
}

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.substr(0, lastDotIndex === -1 ? fileName.length : lastDotIndex);
}

/**
 * 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 openFiles(paths: string[]): FileHandlingResult {
	const processPath = (path: string): Input|undefined => {
		const buffer = wsi4.io.fs.readFile(path);
		if (buffer === undefined) {
			showPathError(path);
			return undefined;
		}
		const type = wsi4.classifier.classify(buffer);

		if (type === InputType.layered) {
			const dirtyLayered = wsi4.geo.util.createLayered(buffer);
			if (dirtyLayered === undefined) {
				showPathError(path);
				return undefined;
			}
			const requestResult = showLayeredImportDialog(dirtyLayered, wsi4.geo.util.createLayeredExtraData(buffer), wsi4.util.translate("ImportLayered") + ": " + path);
			if (requestResult === undefined) {
				// User canceled
				return undefined;
			}
			return {
				type: InputType.twoDimRep,
				content: {
					twoDimRep: requestResult.twoDimRep,
					thickness: requestResult.thickness,
					name: removeFileExtention(extractFileName(path)),
					id: path,
				},
			};
		} else if (type === InputType.documentGraph) {
			return {
				type: type,
				content: {
					data: buffer,
					id: path,
				},
			};
		} else if (type === InputType.assembly) {
			return {
				type: type,
				content: {
					data: buffer,
					id: path,
				},
			};
		} else {
			showPathError(path);
			return undefined;
		}
	};
	const objectsToAdd = paths.map(processPath)
		.filter(isInput);

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

	const importIdNameProposalMap = objectsToAdd.reduce((acc, obj) => {
		if (isInputContentAssembly(obj.content) || isInputContentLayered(obj.content)) {
			acc.set(obj.content.id, removeFileExtention(extractFileName(obj.content.id)));
		}
		return acc;
	}, new Map<string, string>());

	const addResultsWithRootIds = addToGraph(objectsToAdd, importIdNameProposalMap)
		.map(addResult => ({
			id: addResult.id,
			status: addResult.status,
			rootIds: addResult.vertices.map(vertex => wsi4.node.rootId(vertex)),
		}));

	// Handle import errors
	addResultsWithRootIds.forEach((addResult, index) => {
		const obj = objectsToAdd[index]!;
		if (addResult.status !== AddResultStatus.success) {
			switch (obj.type) {
				case InputType.undefined: {
					// Error is handled in processPath()
					return;
				}
				case InputType.assembly:
				case InputType.layered:
				case InputType.documentGraph: {
					assert(isInputContentAssembly(obj.content) || isInputContentDocumentGraph(obj.content) || isInputContentLayered(obj.content));
					showAddingError(obj.content.id, addResult.status);
				}
			}
		}
	});

	const success = addResultsWithRootIds.length === paths.length && addResultsWithRootIds.every(element => element.status === AddResultStatus.success);

	const wsi4FilePath = !success ? "" : objectsToAdd.length !== 1 ? "" : objectsToAdd[0]!.type !== InputType.documentGraph ? "" : paths[0]!;
	return {
		success: success,
		path: wsi4FilePath,
		graphUuid: wsi4.graph.currentUuid(),
	};
}

// 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(),
	};
}
