//#region packages/icu-messageformat-parser/types.ts
let TYPE = /* @__PURE__ */ function(TYPE) {
	/**
	* Raw text
	*/
	TYPE[TYPE["literal"] = 0] = "literal";
	/**
	* Variable w/o any format, e.g `var` in `this is a {var}`
	*/
	TYPE[TYPE["argument"] = 1] = "argument";
	/**
	* Variable w/ number format
	*/
	TYPE[TYPE["number"] = 2] = "number";
	/**
	* Variable w/ date format
	*/
	TYPE[TYPE["date"] = 3] = "date";
	/**
	* Variable w/ time format
	*/
	TYPE[TYPE["time"] = 4] = "time";
	/**
	* Variable w/ select format
	*/
	TYPE[TYPE["select"] = 5] = "select";
	/**
	* Variable w/ plural format
	*/
	TYPE[TYPE["plural"] = 6] = "plural";
	/**
	* Only possible within plural argument.
	* This is the `#` symbol that will be substituted with the count.
	*/
	TYPE[TYPE["pound"] = 7] = "pound";
	/**
	* XML-like tag
	*/
	TYPE[TYPE["tag"] = 8] = "tag";
	return TYPE;
}({});
function isArgumentElement(el) {
	return el.type === 1;
}
function isNumberElement(el) {
	return el.type === 2;
}
function isDateElement(el) {
	return el.type === 3;
}
function isTimeElement(el) {
	return el.type === 4;
}
function isSelectElement(el) {
	return el.type === 5;
}
function isPluralElement(el) {
	return el.type === 6;
}
function isPoundElement(el) {
	return el.type === 7;
}
function isTagElement(el) {
	return el.type === 8;
}
//#endregion
//#region packages/icu-messageformat-parser/manipulator.ts
function cloneDeep(obj) {
	if (Array.isArray(obj)) return obj.map(cloneDeep);
	if (obj !== null && typeof obj === "object") return Object.keys(obj).reduce((cloned, k) => {
		cloned[k] = cloneDeep(obj[k]);
		return cloned;
	}, {});
	return obj;
}
/**
* Replace pound elements with number elements referencing the given variable.
* This is needed when nesting plurals - the # in the outer plural should become
* an explicit variable reference when nested inside another plural.
* GH #4202
*/
function replacePoundWithArgument(ast, variableName) {
	return ast.map((el) => {
		if (isPoundElement(el)) return {
			type: 2,
			value: variableName,
			style: null,
			location: el.location
		};
		if (isPluralElement(el) || isSelectElement(el)) {
			const newOptions = {};
			for (const key of Object.keys(el.options)) newOptions[key] = { value: replacePoundWithArgument(el.options[key].value, variableName) };
			return {
				...el,
				options: newOptions
			};
		}
		if (isTagElement(el)) return {
			...el,
			children: replacePoundWithArgument(el.children, variableName)
		};
		return el;
	});
}
function hoistPluralOrSelectElement(ast, el, positionToInject) {
	const cloned = cloneDeep(el);
	const { options } = cloned;
	const afterElements = ast.slice(positionToInject + 1);
	const hasSubsequentPluralOrSelect = afterElements.some(isPluralOrSelectElement);
	cloned.options = Object.keys(options).reduce((all, k) => {
		let optionValue = options[k].value;
		if (hasSubsequentPluralOrSelect && isPluralElement(el)) optionValue = replacePoundWithArgument(optionValue, el.value);
		all[k] = { value: hoistSelectors([
			...ast.slice(0, positionToInject),
			...optionValue,
			...afterElements
		]) };
		return all;
	}, {});
	return cloned;
}
function isPluralOrSelectElement(el) {
	return isPluralElement(el) || isSelectElement(el);
}
function findPluralOrSelectElement(ast) {
	return !!ast.find((el) => {
		if (isPluralOrSelectElement(el)) return true;
		if (isTagElement(el)) return findPluralOrSelectElement(el.children);
		return false;
	});
}
/**
* Hoist all selectors to the beginning of the AST & flatten the
* resulting options. E.g:
* "I have {count, plural, one{a dog} other{many dogs}}"
* becomes "{count, plural, one{I have a dog} other{I have many dogs}}".
* If there are multiple selectors, the order of which one is hoisted 1st
* is non-deterministic.
* The goal is to provide as many full sentences as possible since fragmented
* sentences are not translator-friendly
* @param ast AST
*/
function hoistSelectors(ast) {
	for (let i = 0; i < ast.length; i++) {
		const el = ast[i];
		if (isPluralOrSelectElement(el)) return [hoistPluralOrSelectElement(ast, el, i)];
		if (isTagElement(el) && findPluralOrSelectElement([el])) throw new Error("Cannot hoist plural/select within a tag element. Please put the tag element inside each plural/select option");
	}
	return ast;
}
/**
* Collect all variables in an AST to Record<string, TYPE>
* @param ast AST to collect variables from
* @param vars Record of variable name to variable type
*/
function collectVariables(ast, vars = /* @__PURE__ */ new Map()) {
	ast.forEach((el) => {
		if (isArgumentElement(el) || isDateElement(el) || isTimeElement(el) || isNumberElement(el)) if (vars.has(el.value)) {
			const existingType = vars.get(el.value);
			if (existingType !== el.type && existingType !== 6 && existingType !== 5) throw new Error(`Variable ${el.value} has conflicting types`);
		} else vars.set(el.value, el.type);
		if (isPluralElement(el) || isSelectElement(el)) {
			vars.set(el.value, el.type);
			Object.keys(el.options).forEach((k) => {
				collectVariables(el.options[k].value, vars);
			});
		}
		if (isTagElement(el)) {
			vars.set(el.value, el.type);
			collectVariables(el.children, vars);
		}
	});
}
/**
* Check if 2 ASTs are structurally the same. This primarily means that
* they have the same variables with the same type
* @param a
* @param b
* @returns
*/
function isStructurallySame(a, b) {
	const aVars = /* @__PURE__ */ new Map();
	const bVars = /* @__PURE__ */ new Map();
	collectVariables(a, aVars);
	collectVariables(b, bVars);
	if (aVars.size !== bVars.size) return {
		success: false,
		error: /* @__PURE__ */ new Error(`Different number of variables: [${Array.from(aVars.keys()).join(", ")}] vs [${Array.from(bVars.keys()).join(", ")}]`)
	};
	return Array.from(aVars.entries()).reduce((result, [key, type]) => {
		if (!result.success) return result;
		const bType = bVars.get(key);
		if (bType == null) return {
			success: false,
			error: /* @__PURE__ */ new Error(`Missing variable ${key} in message`)
		};
		if (bType !== type) return {
			success: false,
			error: /* @__PURE__ */ new Error(`Variable ${key} has conflicting types: ${TYPE[type]} vs ${TYPE[bType]}`)
		};
		return result;
	}, { success: true });
}
//#endregion
export { hoistSelectors, isStructurallySame };

//# sourceMappingURL=manipulator.js.map