import type { Token } from "../types";

/**
 * Creates a unique key for a token (chainId + address)
 */
function getTokenKey(token: Token): string {
	return `${token.chainId}-${token.address.toLowerCase()}`;
}

/**
 * Calculates search relevance score for a token
 * Score 5: Exact symbol match
 * Score 4: Exact name match
 * Score 3: Partial symbol match
 * Score 2: Partial name match
 * Score 1: Partial address match
 * Score 0: No match
 */
function calculateRelevanceScore(token: Token, searchTerm: string): number {
	if (!searchTerm) return 0;

	const term = searchTerm.toLowerCase();
	const symbolLower = token.symbol?.toLowerCase() || "";
	const nameLower = token.name?.toLowerCase() || "";
	const addressLower = token.address.toLowerCase();

	// Exact matches (highest priority)
	if (symbolLower === term) return 5;
	if (nameLower === term) return 4;

	// Partial matches
	if (symbolLower.includes(term)) return 3;
	if (nameLower.includes(term)) return 2;
	if (addressLower.includes(term)) return 1;

	return 0;
}

/**
 * Options for token sorting
 */
export interface SortOptions {
	searchKeyword?: string;
	activeNetwork?: number | "all";
	filteredTokensSet?: Set<string>;
	relevanceScoresMap?: Map<string, number>;
}

/**
 * Creates an optimized token sorter function with pre-computed metadata
 * This function uses pre-computed relevance scores and Set for O(1) filteredTokens lookup
 */
export function createTokenSorter(
	options: SortOptions,
): (a: Token, b: Token) => number {
	const { activeNetwork, filteredTokensSet, relevanceScoresMap } = options;

	return (a: Token, b: Token) => {
		const aKey = getTokenKey(a);
		const bKey = getTokenKey(b);

		// 1. Verification status (verified tokens first)
		if (a.isVerified !== b.isVerified) {
			return a.isVerified ? -1 : 1;
		}

		// 2. Presence in filteredTokens (if provided)
		if (filteredTokensSet && filteredTokensSet.size > 0) {
			const aInFilteredList = filteredTokensSet.has(aKey);
			const bInFilteredList = filteredTokensSet.has(bKey);

			if (aInFilteredList !== bInFilteredList) {
				return aInFilteredList ? -1 : 1; // Tokens in filtered list come first
			}
		}

		// 3. Search relevance score (if provided)
		// Use pre-computed scores from Map for O(1) lookup
		if (relevanceScoresMap) {
			const aRelevanceScore = relevanceScoresMap.get(aKey) || 0;
			const bRelevanceScore = relevanceScoresMap.get(bKey) || 0;

			if (aRelevanceScore !== bRelevanceScore) {
				return bRelevanceScore - aRelevanceScore; // Higher score first
			}
		}

		// 4. USD balance (descending)
		if (a.balanceInUsd !== b.balanceInUsd) {
			return (b.balanceInUsd || 0) - (a.balanceInUsd || 0);
		}

		// 5. Active network priority
		if (activeNetwork !== undefined && activeNetwork !== "all") {
			if (a.chainId === activeNetwork && b.chainId !== activeNetwork) return -1;
			if (a.chainId !== activeNetwork && b.chainId === activeNetwork) return 1;
		}

		// 6. Chain ID (ascending)
		if (a.chainId !== b.chainId) {
			return a.chainId - b.chainId;
		}

		// 7. Alphabetical (symbol, name, address)
		if (a.symbol !== b.symbol) {
			return a.symbol.localeCompare(b.symbol);
		}
		if (a.name !== b.name) {
			return a.name.localeCompare(b.name);
		}
		if (a.address !== b.address) {
			return a.address.localeCompare(b.address);
		}

		return 0;
	};
}

/**
 * Pre-computes relevance scores for a list of tokens
 * Returns a Map with token keys as keys and relevance scores as values
 */
export function precomputeRelevanceScores(
	tokens: Token[],
	searchKeyword: string,
): Map<string, number> {
	const scores = new Map<string, number>();
	const searchTerm = searchKeyword.toLowerCase();

	for (const token of tokens) {
		const key = getTokenKey(token);
		const score = calculateRelevanceScore(token, searchTerm);
		scores.set(key, score);
	}

	return scores;
}

/**
 * Creates a Set of token keys from a filteredTokens array
 * Used for O(1) lookup during sorting
 */
export function createFilteredTokensSet(filteredTokens: Token[]): Set<string> {
	return new Set(filteredTokens.map(getTokenKey));
}

/**
 * Sorts tokens using the unified sort function with pre-computed metadata
 * This is the main entry point for optimized token sorting
 * Pre-computes relevance scores for optimal performance
 */
export function sortTokens(tokens: Token[], options: SortOptions): Token[] {
	// Auto-compute relevance scores if searchKeyword is provided and not already set
	// This is the key optimization - calculate once, lookup many times
	const relevanceScoresMap =
		options.relevanceScoresMap ??
		(options.searchKeyword
			? precomputeRelevanceScores(tokens, options.searchKeyword)
			: undefined);

	// Create the sorter with pre-computed data
	const sorter = createTokenSorter({
		...options,
		relevanceScoresMap,
	});

	// Sort the tokens
	return [...tokens].sort(sorter);
}
