import {
	Color,
	FileType,
	ProcessType,
	TableType, TubeProfileGeometryType,
} from "qrc:/js/lib/generated/enum";
import {
	isTubeProfileGeometryCircular,
	isTubeProfileGeometryRectangular,
} from "qrc:/js/lib/generated/typeguard";
import {
	mappedSheetCuttingMaterialId,
} from "qrc:/js/lib/node_utils";
import {
	getActiveProcessTypes,
} from "qrc:/js/lib/process_utils";
import {
	getSettingOrDefault,
} from "qrc:/js/lib/settings_table";
import {
	computeValidLaserCuttingGasIds,
	getSheetsInStock,
	getTable,
	isTubeInStock,
	parseTubeProfileGeometry,
} from "qrc:/js/lib/table_utils";
import {
	assert,
	computeArrayIntersection,
	exhaustiveStringTuple,
} from "qrc:/js/lib/utils";
import {
	createCircTubeProfilePath,
	createRectTubeProfilePath,
} from "qrc:/js/lib/part_creation";
import {
	isAvailableProcess,
} from "qrc:/js/lib/process";

import {
	AdditionalProcesses,
	AllowedAdditionalProcesses,
	ConfigFlags,
	getAdditionalProcesses,
	MinLowerDieOpeningWidth,
	ShopConfig,
	ShopConfigCuttingGasConfiguration,
	ShopConfigSettings,
	TubeProfileEntry,
} from "./cli_shop_interface";

export function getAllowedAdditionalProcesses(): AllowedAdditionalProcesses {
	const availableTypes = getActiveProcessTypes();
	const ap: AdditionalProcesses = getAdditionalProcesses();
	return exhaustiveStringTuple<keyof AdditionalProcesses>()(
		"mechanicalDeburring",
		"threading",
		"countersinking",
		"slideGrinding",
		"tapping",
	).filter(key => {
		const specificTypes = ap[key];
		return computeArrayIntersection([
			availableTypes,
			specificTypes,
		]).length > 0;
	});
}

export function allowedCuttingGasConfigurations(): Array<ShopConfigCuttingGasConfiguration> {
	const sheetMaterials = getTable(TableType.sheetMaterial);
	const sheets = getSheetsInStock();
	return sheets.reduce((res: Array<ShopConfigCuttingGasConfiguration>, row: Sheet) => {
		if (res.some(r => r.thickness === row.thickness && r.sheetMaterial.identifier === row.sheetMaterialId)) {
			return res;
		}
		const laserMaterial = mappedSheetCuttingMaterialId(row.sheetMaterialId);
		if (laserMaterial === undefined) {
			return res;
		}
		const sheetMaterial = sheetMaterials.find(m => m.identifier === row.sheetMaterialId);
		assert(sheetMaterial !== undefined);
		const validGasIds = computeValidLaserCuttingGasIds(row.thickness, laserMaterial);
		for (const gasId of validGasIds) {
			res.push({
				thickness: row.thickness,
				cuttingGasId: gasId,
				sheetMaterial: sheetMaterial,
			});
		}
		return res;
	}, []);
}

function getMinLowerDieOpeningWidths(): Array<MinLowerDieOpeningWidth> {
	const bendDeductions = getTable(TableType.bendDeduction);
	const lowerDieGroups = getTable(TableType.lowerDieGroup);
	const thicknesses = allowedCuttingGasConfigurations()
		.filter((g, index, self) => {
			const i = self.findIndex(g1 => g1.thickness === g.thickness);
			return i === index;
		})
		.map(g => g.thickness);
	return thicknesses.reduce((res: Array<MinLowerDieOpeningWidth>, t) => {
		const deductions = bendDeductions.filter(d => d.thickness === t);
		if (deductions.length === 0) {
			return res;
		}
		const dieGroups = lowerDieGroups.filter(l => deductions.some(d => d.lowerDieGroupId === l.identifier));
		if (dieGroups.length === 0) {
			return res;
		}
		res.push({
			thickness: t,
			openingWidth: Math.min(...dieGroups.map(l => l.openingWidth)),
		});
		return res;
	}, []);
}

function settings(): ShopConfigSettings {
	return {
		fixedRotationsEnabled: getSettingOrDefault("sheetCuttingFixedRotationsEnabled"),
		testReportRequired: getSettingOrDefault("sheetTestReportEnabled"),
	};
}

function createTubeProfileSvg(type: TubeProfileGeometryType, geometry: TubeProfileGeometryRectangular | TubeProfileGeometryCircular): ArrayBuffer {
	const segments = (() => {
		switch (type) {
			case TubeProfileGeometryType.circular: {
				assert(isTubeProfileGeometryCircular(geometry), "Tube profile geometry inconsistent");
				return createCircTubeProfilePath(geometry);

			}
			case TubeProfileGeometryType.rectangular: {
				assert(isTubeProfileGeometryRectangular(geometry), "Tube profile geometry inconsistent");
				return createRectTubeProfilePath(geometry);

			}
		}
	})();

	const iop = wsi4.geo.util.createIop(segments);
	assert(iop !== undefined, "Tube profile geometry invalid");

	const scene = (() => {
		const emptyScene = wsi4.geo.util.createScene({
			material: "",
			thickness: 0.,
			identifier: "",
			comment: "",
			globalMaterial: "",
		});

		return wsi4.geo.util.addInnerOuterPolygonsToScene(
			emptyScene,
			[ iop ],
			{
				strokeWidth: 1.,
				strokeColor: Color.black,
			},
		);
	})();

	return wsi4.geo.util.renderScene(scene, FileType.svg, {});
}

function computeTubeProfileEntries(): TubeProfileEntry[] {
	// For now only rectangular tube profiles are supported
	return getTable(TableType.tubeProfile)
		.filter(row => parseTubeProfileGeometry(row.geometryJson)[0] === TubeProfileGeometryType.rectangular)
		.map(row => {
			const [
				type,
				geometry,
			] = parseTubeProfileGeometry(row.geometryJson);
			return {

				identifier: row.identifier,
				name: row.name,
				description: row.description,
				// Note:  As of now SVGs binary data is converted directly to string in the shop interface
				svg: wsi4.util.arrayBufferToString(createTubeProfileSvg(type, geometry)),
				geometryType: type,
				geometry: geometry,
			};
		});
}

/**
 * Get the config with the values relevant for the shop
 *
 * Note: Return value must not contain any sensible data such as internal prices or calculation parameters.
 */
export function shopConfiguration(configFlags: Readonly<ConfigFlags>): ShopConfig {
	const materialTable = Array.from(getTable(TableType.sheetMaterial));
	const sheetCuttingMaterialMappings = Array.from(getTable(TableType.sheetCuttingMaterialMapping));
	const sheetsInStock = Array.from(getSheetsInStock());
	const screwThreads = getTable(TableType.screwThread);

	const tubeStock = getTable(TableType.tubeStock);
	const tubeProfiles = configFlags.tubesEnabled ? computeTubeProfileEntries() : [];
	const tubes = configFlags.tubesEnabled ? getTable(TableType.tube)
		.filter(tube => tubeProfiles.some(profile => profile.identifier === tube.tubeProfileId)
					&& isTubeInStock(tube.identifier, tubeStock)) : [];
	const tubeMaterials = configFlags.tubesEnabled ? getTable(TableType.tubeMaterial)
		.filter(material => tubes.some(tube => tube.tubeMaterialId === material.identifier)) : [];
	const tubeSpecifications = configFlags.tubesEnabled ? getTable(TableType.tubeSpecification)
		.filter(specification => tubes.some(tube => tube.tubeSpecificationId === specification.identifier)) : [];
	const tubeCuttingProcesses = getTable(TableType.process)
		.filter((row, _, self) => row.type === ProcessType.tubeCutting && isAvailableProcess(row, self));

	return {
		allowedAdditionalProcesses: getAllowedAdditionalProcesses(),
		allowedCuttingGasConfigurations: allowedCuttingGasConfigurations(),
		laserSheetCuttingMaterial: sheetCuttingMaterialMappings,
		material: materialTable,
		settings: settings(),
		sheets: sheetsInStock,
		laserSheetCuttingMinAreas: Array.from(getTable(TableType.laserSheetCuttingMinArea)),
		minLowerDieOpeningWidths: getMinLowerDieOpeningWidths(),
		tubeProfiles: tubeProfiles,
		tubeMaterials: tubeMaterials,
		tubeSpecifications: tubeSpecifications,
		tubes: tubes,
		tubeCuttingProcesses: tubeCuttingProcesses,
		minimalScrewDiameter: screwThreads.length === 0 ? 0. : screwThreads.reduce((d: number, s) => d < s.coreHoleDiameter ? d : s.coreHoleDiameter, screwThreads[0]!.coreHoleDiameter),
	};
}
