/* eslint-disable */

/**
 * Fuzzy search ripped from the internet.
 */
const FuzzySet: (
	arr?: string[],
	useLevenshtein?: boolean,
	gramSizeLower?: number,
	gramSizeUpper?: number,
) => any = function (
	arr: string[] = [],
	useLevenshtein?: boolean,
	gramSizeLower: number = 2,
	gramSizeUpper: number = 3,
) {
	var fuzzyset: any = {
		gramSizeLower: gramSizeLower,
		gramSizeUpper: gramSizeUpper,
		useLevenshtein: typeof useLevenshtein !== 'boolean' ? true : useLevenshtein,
		exactSet: {},
		matchDict: {},
		items: {},
	};

	var levenshtein = function (str1: string, str2: string) {
		var current: number[] = [];
		var prev: number;
		var value: number;

		for (var i = 0; i <= str2.length; i++) {
			for (var j = 0; j <= str1.length; j++) {
				if (i && j) {
					if (str1.charAt(j - 1) === str2.charAt(i - 1)) {
						// @ts-expect-error
						value = prev;
					} else {
						// @ts-expect-error
						value = Math.min(current[j], current[j - 1], prev) + 1;
					}
				} else {
					value = i + j;
				}

				prev = current[j];
				current[j] = value;
			}
		}

		return current.pop() as number;
	};

	// return an edit distance from 0 to 1
	var _distance = function (str1: string, str2: string) {
		if (str1 === null && str2 === null) {
			throw new Error('Trying to compare two null values');
		}
		if (str1 === null || str2 === null) {
			return 0;
		}
		str1 = String(str1);
		str2 = String(str2);

		var distance = levenshtein(str1, str2);
		if (str1.length > str2.length) {
			return 1 - distance / str1.length;
		} else {
			return 1 - distance / str2.length;
		}
	};
	var _nonWordRe = /[^a-zA-Z0-9\u00C0-\u00FF, ]+/g;

	var _iterateGrams = function (value: any, gramSize: any) {
		gramSize = gramSize || 2;
		var simplified = '-' + value.toLowerCase().replace(_nonWordRe, '') + '-',
			lenDiff = gramSize - simplified.length,
			results = [];
		if (lenDiff > 0) {
			for (var i = 0; i < lenDiff; ++i) {
				simplified += '-';
			}
		}
		for (var i = 0; i < simplified.length - gramSize + 1; ++i) {
			results.push(simplified.slice(i, i + gramSize));
		}
		return results;
	};

	var _gramCounter = function (value: string, gramSize: number) {
		// return an object where key=gram, value=number of occurrences
		gramSize = gramSize || 2;
		var result = {},
			grams = _iterateGrams(value, gramSize),
			i = 0;
		for (i; i < grams.length; ++i) {
			if (grams[i] in result) {
				// @ts-expect-error
				result[grams[i]] += 1;
			} else {
				// @ts-expect-error
				result[grams[i]] = 1;
			}
		}
		return result;
	};

	// the main functions
	fuzzyset.get = function (value: string, defaultValue: string, minMatchScore: number) {
		// check for value in set, returning defaultValue or null if none found
		if (minMatchScore === undefined) {
			minMatchScore = 0.33;
		}
		var result = this._get(value, minMatchScore);
		if (!result && typeof defaultValue !== 'undefined') {
			return defaultValue;
		}
		return result;
	};

	fuzzyset._get = function (value: string, minMatchScore: number) {
		var results = [];
		// start with high gram size and if there are no results, go to lower gram sizes
		for (var gramSize = this.gramSizeUpper; gramSize >= this.gramSizeLower; --gramSize) {
			results = this.__get(value, gramSize, minMatchScore);
			if (results && results.length > 0) {
				return results;
			}
		}
		return null;
	};

	fuzzyset.__get = function (value: string, gramSize: number, minMatchScore: number) {
		var normalizedValue = this._normalizeStr(value),
			matches = {},
			gramCounts = _gramCounter(normalizedValue, gramSize),
			items = this.items[gramSize],
			sumOfSquareGramCounts = 0,
			gram,
			gramCount,
			i,
			index,
			otherGramCount;

		for (gram in gramCounts) {
			// @ts-expect-error
			gramCount = gramCounts[gram];
			sumOfSquareGramCounts += Math.pow(gramCount, 2);
			if (gram in this.matchDict) {
				for (i = 0; i < this.matchDict[gram].length; ++i) {
					index = this.matchDict[gram][i][0];
					otherGramCount = this.matchDict[gram][i][1];
					if (index in matches) {
						// @ts-expect-error
						matches[index] += gramCount * otherGramCount;
					} else {
						// @ts-expect-error
						matches[index] = gramCount * otherGramCount;
					}
				}
			}
		}

		function isEmptyObject(obj: any) {
			for (var prop in obj) {
				if (obj.hasOwnProperty(prop)) {
					return false;
				}
			}
			return true;
		}

		if (isEmptyObject(matches)) {
			return null;
		}

		var vectorNormal = Math.sqrt(sumOfSquareGramCounts),
			results: string[] = [],
			matchScore;
		// build a results list of [score, str]
		for (var matchIndex in matches) {
			// @ts-expect-error
			matchScore = matches[matchIndex];
			// @ts-expect-error
			results.push([matchScore / (vectorNormal * items[matchIndex][0]), items[matchIndex][1]]);
		}
		var sortDescending = function (a: any, b: any) {
			if (a[0] < b[0]) {
				return 1;
			} else if (a[0] > b[0]) {
				return -1;
			} else {
				return 0;
			}
		};
		results.sort(sortDescending);
		if (this.useLevenshtein) {
			var newResults: string[] = [],
				endIndex = Math.min(50, results.length);
			// truncate somewhat arbitrarily to 50
			// @ts-expect-error
			for (var i = 0; i < endIndex; ++i) {
				// @ts-expect-error
				newResults.push([_distance(results[i][1], normalizedValue), results[i][1]]);
			}
			results = newResults;
			results.sort(sortDescending);
		}
		newResults = [];
		results.forEach(
			function (scoreWordPair: any) {
				if (scoreWordPair[0] >= minMatchScore) {
					// @ts-expect-error
					newResults.push([scoreWordPair[0], this.exactSet[scoreWordPair[1]]]);
				}
			}.bind(this),
		);
		return newResults;
	};

	fuzzyset.add = function (value: any) {
		var normalizedValue = this._normalizeStr(value);
		if (normalizedValue in this.exactSet) {
			return false;
		}

		var i = this.gramSizeLower;
		for (i; i < this.gramSizeUpper + 1; ++i) {
			this._add(value, i);
		}
	};

	fuzzyset._add = function (value: string, gramSize: number) {
		var normalizedValue = this._normalizeStr(value),
			items = this.items[gramSize] || [],
			index = items.length;

		items.push(0);
		var gramCounts = _gramCounter(normalizedValue, gramSize);
		var sumOfSquareGramCounts = 0;
		var gram: string;
		var gramCount: number;

		for (gram in gramCounts) {
			// @ts-expect-error
			gramCount = gramCounts[gram];
			sumOfSquareGramCounts += Math.pow(gramCount, 2);
			if (gram in this.matchDict) {
				this.matchDict[gram].push([index, gramCount]);
			} else {
				this.matchDict[gram] = [[index, gramCount]];
			}
		}
		var vectorNormal = Math.sqrt(sumOfSquareGramCounts);
		items[index] = [vectorNormal, normalizedValue];
		this.items[gramSize] = items;
		this.exactSet[normalizedValue] = value;
	};

	fuzzyset._normalizeStr = function (str: string) {
		if (Object.prototype.toString.call(str) !== '[object String]') {
			throw new Error('Must use a string as argument to FuzzySet functions');
		}
		return str.toLowerCase();
	};

	// return length of items in set
	fuzzyset.length = function () {
		var count = 0,
			prop;
		for (prop in this.exactSet) {
			if (this.exactSet.hasOwnProperty(prop)) {
				count += 1;
			}
		}
		return count;
	};

	// return is set is empty
	fuzzyset.isEmpty = function () {
		for (var prop in this.exactSet) {
			if (this.exactSet.hasOwnProperty(prop)) {
				return false;
			}
		}
		return true;
	};

	// return list of values loaded into set
	fuzzyset.values = function () {
		var values = [],
			prop;
		for (prop in this.exactSet) {
			if (this.exactSet.hasOwnProperty(prop)) {
				values.push(this.exactSet[prop]);
			}
		}
		return values;
	};

	// initialization
	var i = fuzzyset.gramSizeLower;
	for (i; i < fuzzyset.gramSizeUpper + 1; ++i) {
		fuzzyset.items[i] = [];
	}
	// add all the items to the set
	for (i = 0; i < arr.length; ++i) {
		fuzzyset.add(arr[i]);
	}

	return fuzzyset;
};

export default FuzzySet;
