import {
	DocumentAlignment,
	DocumentImageType,
	DocumentItemType,
	DocXTableCellType,
	FileType,
	TableType,
} from "qrc:/js/lib/generated/enum";
import {
	computeNodeMass,
	getArticleName,
} from "qrc:/js/lib/graph_utils";
import {
	nodeLaserSheetCuttingGasId,
	VertexAnd,
} from "qrc:/js/lib/node_utils";
import {
	getTable,
} from "qrc:/js/lib/table_utils";
import {
	computeWcsDimensions,
} from "qrc:/js/lib/geometry_utils";
import {
	isEqual,
} from "qrc:/js/lib/utils";
import {
	getFutureResult,
} from "qrc:/js/lib/future";
import {
	accessAllUserData,
	collectNodeUserDataEntries,
	isCompatibleToNodeUserDataEntry,
	nodeUserDatum,
	userDataAccess,
} from "qrc:/js/lib/userdata_utils";
import {
	back,
} from "qrc:/js/lib/array_util";
import {
	sceneForVertex,
} from "qrc:/js/lib/scene_utils";

export function currencyString(sum: number, digits = 2): string {
	return wsi4.util.toCurrencyString(sum, digits);
}

type TimeUnit = "h" | "min" | "s";

export function secondsToString(seconds: number, digits = 2, enforcedUnit?: TimeUnit): string {
	if (enforcedUnit === "h" || (enforcedUnit === undefined && seconds > 3600)) {
		return wsi4.util.toNumberString(seconds / 3600, digits) + " h";
	} else if (enforcedUnit === "min" || (enforcedUnit === undefined && seconds > 60)) {
		return wsi4.util.toNumberString(seconds / 60, digits) + " min";
	} else if (enforcedUnit === "s" || enforcedUnit === undefined) {
		return wsi4.util.toNumberString(seconds, digits) + " s";
	} else {
		// Compile-time assert enforcedUnit is of type never, i.e. this is a dead code path.
		const c = (_a: never) => undefined;
		c(enforcedUnit);
		return "";
	}
}

export function mmToString(mm: number, digits = 2): string {
	return wsi4.util.toNumberString(mm, digits) + " mm";
}

export function squareMmToString(squareMm: number, digits = 2): string {
	if (squareMm > 100000) {
		return wsi4.util.toNumberString(squareMm / 1000000, digits) + " m²";
	} else {
		return wsi4.util.toNumberString(squareMm, digits) + " mm²";
	}
}

export function kgToString(kg: number, digits = 2): string {
	if (kg > 1000) {
		return wsi4.util.toNumberString(kg / 1000, digits) + " t";
	} else if (kg > 1) {
		return wsi4.util.toNumberString(kg, digits) + " kg";
	} else {
		return wsi4.util.toNumberString(kg * 1000, digits) + " g";
	}
}

export function createSpacerItem(width: number): DocumentItem {
	return {
		type: DocumentItemType.paragraph,
		content: {
			width: width,
			text: "",
			alignment: DocumentAlignment.left,
		},
	};
}

export function createPngImageItem(assembly: Assembly, width: number, alignment: DocumentAlignment = DocumentAlignment.center, camera?: Camera3, uuid?: string): DocumentItem {
	const imageData = wsi4.geo.assembly.renderIntoPng(assembly, camera === undefined ? wsi4.geo.assembly.computeDefaultCamera(assembly) : camera);
	return {
		type: DocumentItemType.image,
		content: {
			width: width,
			alignment: alignment,
			uuid: uuid === undefined ? "assembly" + wsi4.util.toKey(assembly) : uuid,
			type: DocumentImageType.png,
			data: imageData,
		},
	};
}

export function createImageItem(vertex: Vertex, imageType: DocumentImageType, width: number, alignment: DocumentAlignment = DocumentAlignment.center): DocumentItem {
	const createItem = (uuid: string, imageData: ArrayBuffer) => ({
		type: DocumentItemType.image,
		content: {
			width: width,
			alignment: alignment,
			uuid: uuid,
			type: imageType,
			data: imageData,
		},
	});

	if (imageType === DocumentImageType.svg) {
		const scene = sceneForVertex(
			vertex,
			{
				bendLineShowLabels: true,
				bendLineShowAffectedSegments: true,
				tubeCuttingShowTubeContours: false,
			},
		);
		return createItem("twoDimRep_" + wsi4.util.toKey(vertex) + "_" + wsi4.node.processType(vertex), wsi4.geo.util.renderScene(scene, FileType.svg, {}),
		);
	} else if (imageType === DocumentImageType.png) {
		const assembly = (() => {
			const inputAssembly = wsi4.node.inputAssembly(vertex);
			const outputAssembly = wsi4.node.assembly(vertex);
			return outputAssembly !== undefined ? outputAssembly : inputAssembly;
		})();
		return assembly === undefined ? createSpacerItem(width) : createPngImageItem(assembly, width, alignment);
	}
	return createSpacerItem(width);
}

export function pngItemFromFuture(
	vertex: Vertex,
	width: number,
	alignment: DocumentAlignment = DocumentAlignment.center,
	futures: readonly Readonly<VertexAnd<ArrayBufferFuture>>[],
): DocumentItem {
	const vab = futures.find(obj => isEqual(obj.vertex, vertex));
	return vab === undefined ? createImageItem(vertex, DocumentImageType.png, width, alignment) : {
		type: DocumentItemType.image,
		content: {
			width: width,
			alignment: alignment,
			uuid: "assembly" + wsi4.util.toKey(vertex),
			type: DocumentImageType.png,
			data: getFutureResult<"arrayBuffer">(vab.data),
		},
	};
}

export function articleImageItem(articleVertex: Vertex, imageType: DocumentImageType, width: number): DocumentItem {
	const vertices = wsi4.graph.article(articleVertex);
	if (vertices.length === 0) {
		return createSpacerItem(width);
	}
	const vertex = back(vertices);
	if (!wsi4.node.assembly(vertex) && !wsi4.node.inputAssembly(vertex)) {
		return createSpacerItem(width);
	}
	return createImageItem(vertex, imageType, width);
}

export function createHeading(text: string, level: number): Array<DocumentItem> {
	return [
		{
			type: DocumentItemType.heading,
			content: {text: text,
				level: level},
		},
	];
}

export function createTitle(text: string, level = 1): Array<Array<DocumentItem>> {
	const imageData = wsi4.io.fs.readFile(":/logo/documentLogo");
	return [
		[
			createSpacerItem(10),
			{
				type: DocumentItemType.image,
				content: {
					width: 2,
					type: DocumentImageType.svg,
					uuid: "wsi4DocumentLogo",
					data: imageData,
					alignment: DocumentAlignment.center,
				},
			},
		],
		createHeading(text, level),
	];
}

export function createSeparator(): Array<DocumentItem> {
	return [
		{
			type: DocumentItemType.separator,
			content: {},
		},
	];
}

export function createPageBreak(): Array<DocumentItem> {
	return [
		{
			type: DocumentItemType.pageBreak,
			content: {},
		},
	];
}

export function getLaserSheetCuttingGasName(vertex: Vertex): string {
	const laserSheetCuttingGasId = nodeLaserSheetCuttingGasId(vertex);
	if (laserSheetCuttingGasId === undefined) {
		return "N/A";
	}
	const table = getTable(TableType.laserSheetCuttingGas);
	const row = table.find(element => element.identifier === laserSheetCuttingGasId);
	if (row === undefined) {
		return "N/A";
	}
	return row.name;
}

export function getArticleSheetMaterialName(vertex: Vertex): string|undefined {
	const materials = collectNodeUserDataEntries("sheetMaterialId", vertex, userDataAccess.article | userDataAccess.reachable)
		.filter((lhs, index, self) => lhs !== undefined && self.findIndex(rhs => rhs !== undefined && lhs === rhs) === index);
	if (materials.length !== 1) {
		return undefined;
	}
	const table = getTable(TableType.sheetMaterial);
	const row = table.find(element => element.identifier === materials[0]!);
	if (row === undefined) {
		return "N/A";
	}
	return row.name;
}

export function getArticleTubeMaterialName(vertex: Vertex): string | undefined {
	const materials = collectNodeUserDataEntries("tubeMaterialId", vertex, userDataAccess.article | userDataAccess.reachable)
		.filter((item): item is string => item !== undefined)
		.filter((lhs, index, self) => self.findIndex(rhs => lhs === rhs) === index);
	if (materials.length !== 1) {
		return undefined;
	}
	return getTable(TableType.tubeMaterial)
		.find(element => element.identifier === materials[0]!)
		?.name;
}

export function createArticleOverviewTable(vertex: Vertex, width: number): DocumentItem {
	const dimensions = (() => {
		const assembly = wsi4.node.assembly(vertex) ?? wsi4.node.inputAssembly(vertex);
		if (assembly === undefined) {
			return undefined;
		} else {
			return computeWcsDimensions(assembly);
		}
	})();

	const multiplicity = wsi4.node.multiplicity(vertex);
	// [kg]
	const mass = computeNodeMass(vertex);

	const materials = collectNodeUserDataEntries("sheetMaterialId", vertex, accessAllUserData)
		.filter((lhs, index, self) => lhs !== undefined && index === self.findIndex(rhs => rhs !== undefined && lhs === rhs))
		.map(material => getTable(TableType.sheetMaterial)
			.find(row => material !== undefined && row.identifier === material))
		.filter((material): material is SheetMaterial => material !== undefined)
		.map(material => material.name);

	return {
		type: DocumentItemType.table,
		content: {
			width: width,
			alignment: DocumentAlignment.left,
			columnWidths: [
				50,
				50,
			],
			columnHeaders: [],
			rows: [
				[
					{
						text: wsi4.util.translate("Name"),
						alignment: DocumentAlignment.left,
					},
					{
						text: getArticleName(vertex),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("materials"),
						alignment: DocumentAlignment.left,
					},
					{
						text: materials.length === 0 ? wsi4.util.translate("Unknown") : materials.join(", "),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("Mass"),
						alignment: DocumentAlignment.left,
					},
					{
						text: mass === undefined ? wsi4.util.translate("Unknown") : kgToString(mass),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("Length"),
						alignment: DocumentAlignment.left,
					},
					{
						text: dimensions === undefined ? "N/A" : mmToString(dimensions.x, 2),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("Width"),
						alignment: DocumentAlignment.left,
					},
					{
						text: dimensions === undefined ? "N/A" : mmToString(dimensions.y, 2),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("Height"),
						alignment: DocumentAlignment.left,
					},
					{
						text: dimensions === undefined ? "N/A" : mmToString(dimensions.z, 2),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("Multiplicity"),
						alignment: DocumentAlignment.left,
					},
					{
						text: String(multiplicity),
						alignment: DocumentAlignment.right,
					},
				],
			],
		},
	};
}

export function createCommentOnDemand(vertex: Vertex): Array<Array<DocumentItem>> {
	if (!isCompatibleToNodeUserDataEntry("comment", vertex)) {
		return [];
	}

	const comment = nodeUserDatum("comment", vertex);
	if (comment === undefined || comment.length === 0) {
		return [];
	}

	return [
		createHeading(wsi4.util.translate("Comment"), 4),
		[
			{
				type: DocumentItemType.paragraph,
				content: {
					width: 12,
					text: comment.replace(/\n/g, "<br>"),
					alignment: DocumentAlignment.left,
				},
			},
		],
	];
}

export interface DocXImageContent {
	content: ArrayBuffer;
	imageType: DocXImageType;
}

export function docXImageCell(placeholder: string, image: DocXImageContent | undefined): DocXTableCell {
	return image === undefined ? {
		type: DocXTableCellType.text,
		content: {
			placeholder: placeholder,
			text: "",
		},
	} : {
		type: DocXTableCellType.image,
		content: {
			placeholder: placeholder,
			content: image.content,
			type: image.imageType,
		},
	};
}
