import {
	TableType,
} from "qrc:/js/lib/generated/enum";
import {
	front,
} from "./array_util";

import {
	tolerances,
} from "./constants";
import {
	point3To2,
} from "./geometry_utils";
import {
	getTable,
} from "./table_utils";
import {
	SheetTappingDataEntry,
} from "./userdata_config";
import {
	assert,
} from "./utils";

export interface TappingCandidate {
	center2: Point2;
	matchingScrewThreads: ScrewThread[];
}

/**
 * @param iop The InnerOuterPolygon
 * @param thickness The sheet thickness
 * @returns List of core-holes where tapping can be applied to incl. matched screw threads
 *
 * The result is sorted according to the core hole positions
 */
export function computeTappingCandidates(iop: InnerOuterPolygon, thickness: number, screwThreadsInput?: readonly Readonly<ScrewThread>[]): TappingCandidate[] {
	const screwThreads = screwThreadsInput ?? getTable(TableType.screwThread);
	return wsi4.geo.util.innerPolygons(iop)
		.filter(polygon => wsi4.geo.util.isCircle(polygon))
		.map(polygon => {
			const diameter = 2 * wsi4.geo.util.circleRadius(polygon);
			const matchingScrewThreads = screwThreads
				.filter(screwThread => (thickness + tolerances.thickness) > screwThread.minDepth)
				.filter(screwThread => Math.abs(screwThread.coreHoleDiameter - diameter) < screwThread.symmetricTolerance);
			return {
				center2: wsi4.geo.util.circleCenter(polygon),
				matchingScrewThreads: matchingScrewThreads,
			};
		})
		.filter(params => params.matchingScrewThreads.length > 0)
		.sort((lhs, rhs) => {
			if (lhs.center2.entries[0] === rhs.center2.entries[0]) {
				return rhs.center2.entries[1] - lhs.center2.entries[1];
			} else {
				return lhs.center2.entries[0] - rhs.center2.entries[0];
			}
		});
}

interface TappingLabel {
	position: Point2;
	text: string;
}

function addSheetTappingLabelsToScene(inputScene: Scene,
	labels: readonly Readonly<TappingLabel>[]) {
	return wsi4.geo.util.addLabelsToScene(inputScene, labels);
}

function tappingLabel(screwThread: ScrewThread, center2d: Point2, text: string): TappingLabel {
	return {
		position: {
			// Position label top right of the core hole
			entries: [
				center2d.entries[0] + screwThread.coreHoleDiameter * 0.5,
				center2d.entries[1] + screwThread.coreHoleDiameter,
			],
		},
		text: text,
	};
}

/**
 * Add zero-based indices to scene for each tapping candidate
 */
export function addSheetTappingNumbersToScene(
	inputScene: Scene,
	tappingCandidates: readonly Readonly<TappingCandidate>[],
	screwThreadsInput?: readonly Readonly<ScrewThread>[],
): Scene {
	const screwThreads = screwThreadsInput ?? getTable(TableType.screwThread);

	const labels = tappingCandidates.map((candidate, index): TappingLabel => {
		const screwThread = screwThreads.find(st => st.identifier === front(candidate.matchingScrewThreads).identifier);
		assert(screwThread !== undefined, "Sheet tapping user data do not match table values.  Expecting valid screwThread.");
		const center2d = candidate.center2;
		return tappingLabel(screwThread, center2d, index.toFixed(0));
	});

	return addSheetTappingLabelsToScene(inputScene, labels);
}

/**
 * Add zero-based indices to scene for each tapping candidate
 */
export function addSheetTappingTextToScene(
	inputScene: Scene,
	entries: readonly Readonly<SheetTappingDataEntry>[],
	twoDimRepTransformation: CoordinateSystem3): Scene {
	const screwThreads = getTable(TableType.screwThread);

	const labels = entries.map((candidate): TappingLabel => {
		const screwThread = screwThreads.find(st => st.identifier === candidate.screwThread.identifier);
		assert(screwThread !== undefined, "Sheet tapping user data do not match table values.  Expecting valid screwThread.");
		const center2d = point3To2(candidate.center3, twoDimRepTransformation);
		return tappingLabel(screwThread, center2d, screwThread.name);
	});

	return addSheetTappingLabelsToScene(inputScene, labels);
}
