/**
 * Utility functions for patching variant classes in HTML.
 * Handles class patterns like "is-style-outline--N" and "is-style-ext-preset--...--N"
 * that WordPress may reorder when parsing blocks.
 */

// escape for regex building
export const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

export const getVariantNumbersInTree = (rootEl, base) => {
	if (!rootEl) return [];
	// Match classes ending with --{number}
	const re = new RegExp(`^${esc(base)}.*--(\\d+)$`, 'i');
	const out = [];
	const seen = new Set();
	// include root + descendants
	const all = [rootEl, ...rootEl.querySelectorAll(`[class*="${base}"]`)];
	for (const el of all) {
		for (const cls of el.classList) {
			const m = cls.match(re);
			if (!m) continue;

			const n = Number(m[1]);
			if (seen.has(n)) continue;

			seen.add(n);
			out.push(n);
		}
	}
	return out;
};

/**
 * Extracts variant class info as { prefix, number } pairs.
 * prefix = everything before the final --N (used for matching element types)
 */
export const getVariantClassInfo = (rootEl, base) => {
	if (!rootEl) return [];
	const re = new RegExp(`^(${esc(base)}.*)--(\\d+)$`, 'i');
	const out = [];
	const seenNumbers = new Set();
	const all = [rootEl, ...rootEl.querySelectorAll(`[class*="${base}"]`)];
	for (const el of all) {
		for (const cls of el.classList) {
			const m = cls.match(re);
			if (!m) continue;
			const prefix = m[1];
			const number = Number(m[2]);
			// Skip duplicate numbers (same variant on multiple elements)
			if (seenNumbers.has(number)) continue;
			seenNumbers.add(number);
			out.push({ prefix, number });
		}
	}
	return out;
};

export const getVariantClassInfoFromHtml = (html, base) => {
	const wrapper = document.createElement('div');
	wrapper.innerHTML = html;
	return getVariantClassInfo(wrapper, base);
};

export const getVariantNumbersInHtml = (html, base) => {
	const wrapper = document.createElement('div');
	wrapper.innerHTML = html;
	return getVariantNumbersInTree(wrapper, base);
};

export const applyVariantNumberMapToHtml = (html, base, numberMap) => {
	if (!numberMap || !numberMap.size) return html;
	const wrapper = document.createElement('div');
	wrapper.innerHTML = html;

	// Match classes ending with --{number}
	const reToken = new RegExp(`^${esc(base)}.*--(\\d+)$`, 'i');

	// target every element that *could* contain the class
	const els = wrapper.querySelectorAll(`[class*="${base}"]`);
	for (const el of els) {
		const classes = Array.from(el.classList);
		let changed = false;

		for (const [i, cls] of classes.entries()) {
			const m = cls.match(reToken);
			if (!m) continue;

			const oldN = Number(m[1]);
			if (!numberMap.has(oldN)) continue;

			const newN = numberMap.get(oldN);
			// Replace the variant number at the end
			const nextCls = cls.replace(/--(\d+)$/, `--${newN}`);
			if (nextCls === cls) continue;

			classes[i] = nextCls;
			changed = true;
		}

		// keep the base style class too
		if (!classes.includes(base)) {
			classes.push(base);
			changed = true;
		}

		if (changed) el.className = classes.join(' ');
	}

	return wrapper.innerHTML;
};

/**
 * Patches variant classes in HTML to match the DOM element's numbering.
 * Handles patterns like "is-style-outline--N" and "is-style-ext-preset--...--N"
 * Note that that variant numbers are globally unique in each page and are autogenerated by WP.
 */
export const patchVariantClasses = (html, el, bases) => {
	let patchedHtml = html;
	for (const base of bases) {
		// Get variant info with prefixes for matching by element type
		const targetInfo = getVariantClassInfo(el, base);
		const currentInfo = getVariantClassInfoFromHtml(patchedHtml, base);

		if (!targetInfo.length || !currentInfo.length) continue;

		// Group by prefix - allows matching by type, with position-based fallback
		// for multiple elements of the same type
		const targetByPrefix = new Map();
		for (const { prefix, number } of targetInfo) {
			if (!targetByPrefix.has(prefix)) targetByPrefix.set(prefix, []);
			targetByPrefix.get(prefix).push(number);
		}

		const currentByPrefix = new Map();
		for (const { prefix, number } of currentInfo) {
			if (!currentByPrefix.has(prefix)) currentByPrefix.set(prefix, []);
			currentByPrefix.get(prefix).push(number);
		}

		// Map by matching prefixes, then by position within each prefix group
		const map = new Map();
		for (const [prefix, currentNums] of currentByPrefix) {
			const targetNums = targetByPrefix.get(prefix);
			if (!targetNums) continue;

			const count = Math.min(currentNums.length, targetNums.length);
			for (const [i, cur] of currentNums.entries()) {
				if (i >= count) break;
				if (cur === targetNums[i]) continue;
				map.set(cur, targetNums[i]);
			}
		}

		if (!map.size) continue;

		patchedHtml = applyVariantNumberMapToHtml(patchedHtml, base, map);
	}
	return patchedHtml;
};
