/*
 * Functions that are called when adding new data to wsi4
 *
 * Note: All API calls must conform to DocumentGraphHandler's script engine
 */

import {
	WorkStepType,
} from "qrc:/js/lib/generated/enum";
import {
	isStringIndexedInterface,
} from "qrc:/js/lib/generated/typeguard";
import {
	migrateDeducedData,
} from "qrc:/js/lib/deduceddata_utils";
import {
	isSignatureVertex,
} from "qrc:/js/lib/graph_utils";
import {
	isArticleUserDataEntry,
} from "qrc:/js/lib/userdata_config";
import {
	computeInitialArticleUserData,
	insertInitialValuesIfMissing,
} from "qrc:/js/lib/userdata_initial_values";
import {
	createUpdatedNodeUserData,
	getNodeUserDataEntryOrThrow,
	isCompatibleToNodeUserDataEntry,
	UserData,
} from "qrc:/js/lib/userdata_utils";
import {
	assert,
	assertDebug,
	getKeysOfObject,
	isBoolean,
	isNumber,
	isString,
} from "qrc:/js/lib/utils";

interface UserDataMapEntry {
	vertex: Vertex;
	userData: UserData;
}

function migrateTestReport(inputVertex: Vertex, inputUserData: Readonly<StringIndexedInterface>): StringIndexedInterface {
	const testReportKey = "testReportRequired";
	if (!isCompatibleToNodeUserDataEntry("testReportRequired", inputVertex) || isBoolean(inputUserData[testReportKey])) {
		// Node is either not relevant or has the respective user data entry already set.
		// As this function is expected to be called *before* insertion of missing user data
		// entries, this sub-section of the graph already is considered up to date with respect
		// to the testReportRequired user data entry.
		return inputUserData;
	} else {
		const sheetVertex = wsi4.graph.reaching(inputVertex)
			.find(vertex => wsi4.node.workStepType(vertex) === WorkStepType.sheet);
		if (sheetVertex === undefined) {
			return inputUserData;
		}

		const sheetNodeUserData = wsi4.node.userData(sheetVertex);
		if (isBoolean(sheetNodeUserData[testReportKey])) {
			const result: StringIndexedInterface = Object.assign({}, inputUserData);
			result[testReportKey] = sheetNodeUserData[testReportKey];
			return result;
		} else {
			return inputUserData;
		}
	}
}

function completeArticleUserData(userData: StringIndexedInterface): StringIndexedInterface {
	const articleUserData = userData["articleUserData"];
	if (isStringIndexedInterface(articleUserData)) {
		const initialValues = computeInitialArticleUserData();
		getKeysOfObject(initialValues)
			.forEach(key => {
				if (!isArticleUserDataEntry(key, articleUserData[key])) {
					articleUserData[key] = initialValues[key];
				}
			});
	}
	return userData;
}

function migrateArticleName(vertex: Vertex, userData: Readonly<StringIndexedInterface>): StringIndexedInterface {
	if (!isSignatureVertex(vertex)) {
		return userData;
	}

	const articleUserData = getNodeUserDataEntryOrThrow("articleUserData", vertex, userData);
	if (articleUserData.name.length > 0) {
		return userData;
	}

	const nameFromAttributes = (() => {
		const articleAttributes = wsi4.node.articleAttributes(vertex);
		return articleAttributes.userData["name"];
	})();

	if (!isString(nameFromAttributes)) {
		return userData;
	}

	articleUserData.name = nameFromAttributes;
	return createUpdatedNodeUserData(
		"articleUserData",
		vertex,
		articleUserData,
		userData,
	);
}

function migrateCalcParams(vertex: Vertex, userData: StringIndexedInterface): StringIndexedInterface {
	const materialCostsPerPiece = userData["materialCosts"];
	if (isNumber(materialCostsPerPiece)) {
		userData = createUpdatedNodeUserData("materialCostsPerPiece", vertex, materialCostsPerPiece, userData);
	}

	const unitTimePerPiece = userData["unitTime"];
	if (isNumber(unitTimePerPiece)) {
		userData = createUpdatedNodeUserData("unitTimePerPiece", vertex, unitTimePerPiece, userData);
	}

	return userData;
}

export function updateUserData(): Array<UserDataMapEntry> {
	return wsi4.graph.vertices()
		.map(vertex => {
			let userData = wsi4.node.userData(vertex);
			// Note:  migrateTestReport() must be called before insertInitialValuesIfMissing() (see implementation).
			userData = migrateTestReport(vertex, userData);
			// Note:  completeArticleUserData() must be called before insertInitialValuesIfMissing()
			userData = completeArticleUserData(userData);
			userData = insertInitialValuesIfMissing(vertex, userData);
			userData = migrateArticleName(vertex, userData);
			userData = migrateCalcParams(vertex, userData);
			return {
				vertex: vertex,
				userData: userData,
			};
		});
}

interface SheetFilterMapEntry {
	sheetVertex: Vertex;
	sheetFilterSheetIds: string[];
}

export function migrateSheetFilterSheetIds(map: SheetFilterMapEntry[]): Array<UserDataMapEntry> {
	const userDataMapEntries: Array<UserDataMapEntry> = [];
	for (const entry of map) {
		assertDebug(() => wsi4.node.workStepType(entry.sheetVertex) === WorkStepType.sheet, "Wrong workStepType.");
		assert(entry.sheetFilterSheetIds !== undefined, "Missing sheet filter.");
		if (entry.sheetFilterSheetIds.length === 0) {
			continue;
		}
		for (const t of wsi4.graph.reachable(entry.sheetVertex)) {
			if (!isCompatibleToNodeUserDataEntry("sheetFilterSheetIds", t)) {
				continue;
			}
			const sheetFilterSheetIds = getNodeUserDataEntryOrThrow("sheetFilterSheetIds", t);
			assert(sheetFilterSheetIds.length === 0);
			userDataMapEntries.push({
				vertex: t,
				userData: createUpdatedNodeUserData("sheetFilterSheetIds", t, entry.sheetFilterSheetIds),
			});
		}
	}
	return userDataMapEntries;
}

export function updateDeducedData(): Array<UserDataMapEntry> {
	return wsi4.graph.vertices()
		.map(vertex => {
			let deducedData = wsi4.node.deducedData(vertex);
			// insert values if missing
			deducedData = migrateDeducedData(vertex, deducedData);
			return {
				vertex: vertex,
				userData: deducedData,
			};
		});
}
