importScripts("../../lib/umap-js.982e8c0a0039cc898f03af42e1ce8ec5.js");

// @ts-ignore
const UMAP = self.UMAP;

/**
 * @typedef {{nComponents: number, nNeighbors: number, minDist: number}} UmapSettings
 */

/**
 *
 * @param {string} str1
 * @param {string} str2
 * @returns {number}
 */
function getLevenshteinDistance(str1, str2) {
	if (str1.length === 0) {
		return str2.length;
	}
	if (str2.length === 0) {
		return str1.length;
	}
	/** @type {number[][]} */
	const mtx = [];
	for (let i = 0; i <= str2.length; i++) {
		mtx[i] = [i];
		for (let j = 1; j <= str1.length; j++) {
			if (i === 0) {
				mtx[i][j] = j;
				continue;
			}
			const substitutionCost = str1[j - 1] === str2[i - 1] ? 0 : 1;
			mtx[i][j] = Math.min(
				mtx[i - 1][j] + 1,
				mtx[i][j - 1] + 1,
				mtx[i - 1][j - 1] + substitutionCost,
			);
		}
	}
	return mtx[str2.length][str1.length];
}

/**
 * @param {string[]} seqs
 * @returns {number[][]}
 */
function getDistanceMatrix(seqs) {
	const len = seqs.length;
	const distanceMatrix = [];
	for (let i = 0; i < len; ++i) {
		const row = new Array(len).fill(0);
		distanceMatrix.push(row);
	}
	for (let i = 1; i < len; ++i) {
		const seq1 = seqs[i];
		for (let j = 0; j < i; ++j) {
			const seq2 = seqs[j];
			const dist = getLevenshteinDistance(seq1, seq2);
			distanceMatrix[i][j] = dist;
			distanceMatrix[j][i] = dist;
		}
	}
	return distanceMatrix;
}

/**
 *
 * @param {number[][]} distanceMatrix
 * @param {number[]} vec1
 * @param {number[]} vec2
 * @returns {number}
 */
function getDistance(distanceMatrix, vec1, vec2) {
	console.assert(vec1.length === 1 && vec2.length === 1);
	const row = vec1[0];
	const col = vec2[0];
	return distanceMatrix[row][col];
}

/**
 *
 * @param {number} len
 * @returns {number[][]}
 */
function getVectors(len) {
	const vectors = [];
	for (let i = 0; i < len; ++i) {
		const vector = [i];
		vectors.push(vector);
	}
	return vectors;
}

/**
 * @param {UmapSettings} umapSettings
 * @param {number[][]} distanceMatrix
 * @returns {{x: number[], y: number[], z: number[]}}
 */
function getUmapResultByDistanceMatrix(umapSettings, distanceMatrix) {
	const len = distanceMatrix.length;
	const vectors = getVectors(len);
	/** @type {(vec1: number[], vec2: number[]) => number } */
	const distanceFn = (vec1, vec2) => {
		return getDistance(distanceMatrix, vec1, vec2);
	};
	const nComponents = umapSettings.nComponents;
	const nNeighbors = umapSettings.nNeighbors;
	const minDist = umapSettings.minDist;
	const settings = {
		nComponents,
		nNeighbors,
		minDist,
		distanceFn,
	};
	const umap = new UMAP(settings);
	const result = /** @type {number[][]} */ (umap.fit(vectors));
	/** @type {number[]} */
	const x = [];
	/** @type {number[]} */
	const y = [];
	/** @type {number[]} */
	const z = [];
	for (const coordinates of result) {
		x.push(coordinates[0]);
		y.push(coordinates[1]);
		z.push(nComponents === 3 ? coordinates[2] : 0);
	}
	return { x, y, z };
}

/**
 *
 * @param {UmapSettings} umapSettings
 * @param {string[]} seqs
 * @returns {{x: number[], y: number[]}}
 */
function getUmapResult(umapSettings, seqs) {
	const distanceMatric = getDistanceMatrix(seqs);
	const umapResult = getUmapResultByDistanceMatrix(
		umapSettings,
		distanceMatric,
	);
	return umapResult;
}

/**
 *
 * @param {MessageEvent} e
 */
function onMessage(e) {
	const data = e.data;
	const { umapSettings, seqs } = data;
	const umapResult = getUmapResult(umapSettings, seqs);
	self.postMessage(umapResult);
}

self.onmessage = onMessage;
