// pcre-to-regexp converts a PCRE string to a regular expression. It also extracts the named
// capture group keys, which is useful for matching and replacing parameters.
// This is the same library used by Vercel in the build output, and is used here to ensure
// consistency and proper support.
import createPCRE from 'pcre-to-regexp/dist/index.js';

export type MatchPCREResult = {
	match: RegExpMatchArray | null;
	captureGroupKeys: string[];
};

/**
 * Checks if a value matches with a PCRE-compatible string, and extract the capture group keys.
 *
 * @param expr PCRE-compatible string.
 * @param val String to check with the regular expression.
 * @param caseSensitive Whether the regular expression should be case sensitive.
 * @returns The result of the matcher and the named capture group keys.
 */
export function matchPCRE(
	expr: string,
	val: string | undefined | null,
	caseSensitive?: boolean,
): MatchPCREResult {
	if (val === null || val === undefined) {
		return { match: null, captureGroupKeys: [] };
	}

	const flag = caseSensitive ? '' : 'i';
	const captureGroupKeys: string[] = [];

	const matcher = createPCRE(`%${expr}%${flag}`, captureGroupKeys);
	const match = matcher.exec(val);

	return { match, captureGroupKeys };
}

/**
 * Processes the value and replaced any matched parameters (index or named capture groups).
 *
 * @param rawStr String to process.
 * @param match Matches from the PCRE matcher.
 * @param captureGroupKeys Named capture group keys from the PCRE matcher.
 * @param opts Options for applying the PCRE matches.
 * @returns The processed string with replaced parameters.
 */
export function applyPCREMatches(
	rawStr: string,
	match: RegExpMatchArray,
	captureGroupKeys: string[],
	{ namedOnly }: { namedOnly?: boolean } = {},
): string {
	return rawStr.replace(/\$([a-zA-Z0-9_]+)/g, (originalValue, key) => {
		const index = captureGroupKeys.indexOf(key);

		// If we only want named capture groups, and the key is not found, return the original value.
		if (namedOnly && index === -1) {
			return originalValue;
		}

		// If the extracted key does not exist as a named capture group from the matcher, we can
		// reasonably assume it's a number and return the matched index. Fallback to an empty string.
		return (index === -1 ? match[parseInt(key, 10)] : match[index + 1]) || '';
	});
}
