import { createUnplugin } from "unplugin";
import { Visitor, parseSync } from "oxc-parser";
import MagicString from "magic-string";
import { decodeNamedCharacterReference } from "decode-named-character-reference";
import { interpolateName } from "@formatjs/ts-transformer";
import { parse } from "@formatjs/icu-messageformat-parser";
import { hoistSelectors } from "@formatjs/icu-messageformat-parser/manipulator.js";
import { printAST } from "@formatjs/icu-messageformat-parser/printer.js";
//#region packages/unplugin/transform.ts
const DEFAULT_ID_INTERPOLATION_PATTERN = "[sha512:contenthash:base64:6]";
function decodeJSXAttributeValue(value) {
	return value.replace(/&(#(?:x[0-9a-fA-F]+|\d+)|[a-zA-Z][\w.-]*);/g, (match, entity) => {
		if (entity[0] === "#") {
			const radix = entity[1]?.toLowerCase() === "x" ? 16 : 10;
			const digits = radix === 16 ? entity.slice(2) : entity.slice(1);
			const codePoint = Number.parseInt(digits, radix);
			if (!Number.isFinite(codePoint) || codePoint <= 0 || codePoint > 1114111) return match;
			return String.fromCodePoint(codePoint);
		}
		const decoded = decodeNamedCharacterReference(entity);
		return decoded === false ? match : decoded;
	});
}
function transform(code, id, options) {
	const { overrideIdFn, idInterpolationPattern = DEFAULT_ID_INTERPOLATION_PATTERN, removeDefaultMessage = false, additionalComponentNames = [], additionalFunctionNames = [], ast: preParseAst = false, preserveWhitespace = false, flatten = true } = options;
	const componentNames = new Set(["FormattedMessage", ...additionalComponentNames]);
	const functionNames = new Set([
		"formatMessage",
		"$t",
		"$formatMessage",
		"defineMessage",
		"defineMessages",
		...additionalFunctionNames
	]);
	const jsxRuntimeFunctions = new Set([
		"jsx",
		"_jsx",
		"jsxs",
		"_jsxs",
		"jsxDEV",
		"_jsxDEV",
		"createElement"
	]);
	const program = parseSync(id, code, { sourceType: "module" }).program;
	let s;
	function unwrapTransparentTypeScriptExpression(node) {
		let current = node;
		while (current?.type === "TSAsExpression" || current?.type === "TSSatisfiesExpression" || current?.type === "TSTypeAssertion" || current?.type === "TSNonNullExpression") current = current.expression;
		return current;
	}
	function unwrapObjectExpression(node) {
		const expression = unwrapTransparentTypeScriptExpression(node);
		return expression?.type === "ObjectExpression" ? expression : void 0;
	}
	function getStaticValue(node) {
		node = unwrapTransparentTypeScriptExpression(node);
		if (!node) return void 0;
		if (node.type === "StringLiteral" || node.type === "Literal") {
			if (typeof node.value === "string") return node.value;
			return;
		}
		if (node.type === "TemplateLiteral") {
			if (node.quasis.length === 1 && node.expressions.length === 0) return node.quasis[0].value.cooked ?? node.quasis[0].value.raw;
			return;
		}
		if (node.type === "BinaryExpression" && node.operator === "+") {
			const left = getStaticValue(node.left);
			const right = getStaticValue(node.right);
			if (left !== void 0 && right !== void 0) return left + right;
			return;
		}
	}
	function extractDescriptor(objNode) {
		const properties = objNode.properties;
		const descriptor = {};
		const locations = {};
		locations.insertionPoint = objNode.start + 1;
		for (const prop of properties) {
			if (prop.type !== "Property" && prop.type !== "ObjectProperty") continue;
			const key = prop.key;
			let name;
			if (key.type === "Identifier") name = key.name;
			else if (key.type === "StringLiteral" || key.type === "Literal") name = key.value;
			if (!name) continue;
			if (name !== "id" && name !== "defaultMessage" && name !== "description") continue;
			const keyName = name;
			const val = getStaticValue(unwrapTransparentTypeScriptExpression(prop.value));
			if (val === void 0) continue;
			descriptor[keyName] = val;
			locations[keyName] = {
				start: prop.value.start,
				end: prop.value.end,
				value: val
			};
		}
		if (!descriptor.defaultMessage && !descriptor.id) return void 0;
		return {
			descriptor,
			locations
		};
	}
	function extractJSXDescriptor(elementNode) {
		const attributes = elementNode.attributes || [];
		const descriptor = {};
		const locations = {};
		locations.insertionPoint = elementNode.name.end;
		for (const attr of attributes) {
			if (attr.type !== "JSXAttribute") continue;
			const attrName = attr.name;
			if (!attrName || attrName.type !== "JSXIdentifier") continue;
			const n = attrName.name;
			if (n !== "id" && n !== "defaultMessage" && n !== "description") continue;
			const keyName = n;
			let val;
			const valueNode = attr.value;
			if (!valueNode) continue;
			if (valueNode.type === "StringLiteral" || valueNode.type === "Literal") val = decodeJSXAttributeValue(valueNode.value);
			else if (valueNode.type === "JSXExpressionContainer") val = getStaticValue(valueNode.expression);
			if (val === void 0) continue;
			descriptor[keyName] = val;
			locations[keyName] = {
				start: attr.value.start,
				end: attr.value.end,
				value: val
			};
		}
		if (!descriptor.defaultMessage && !descriptor.id) return void 0;
		return {
			descriptor,
			locations
		};
	}
	function processDescriptor(descriptor, locations, isJSX) {
		let { defaultMessage, description } = descriptor;
		let existingId = descriptor.id;
		if (defaultMessage && !preserveWhitespace) defaultMessage = defaultMessage.trim().replace(/\s+/gm, " ");
		if (flatten && defaultMessage) try {
			defaultMessage = printAST(hoistSelectors(parse(defaultMessage)));
		} catch {}
		let newId = existingId;
		if (overrideIdFn) newId = overrideIdFn(existingId, defaultMessage, description, id);
		else if (!existingId && idInterpolationPattern && defaultMessage) newId = interpolateName({ resourcePath: id }, idInterpolationPattern, { content: description ? `${defaultMessage}#${description}` : defaultMessage });
		if (!newId && !defaultMessage) return;
		if (!s) s = new MagicString(code);
		if (newId) {
			if (locations.id) s.overwrite(locations.id.start, locations.id.end, JSON.stringify(newId));
			else if (locations.insertionPoint != null) if (isJSX) s.appendLeft(locations.insertionPoint, ` id=${JSON.stringify(newId)}`);
			else s.appendRight(locations.insertionPoint, `id: ${JSON.stringify(newId)}, `);
		}
		if (locations.description) if (isJSX) removeJSXAttribute(locations.description);
		else removeObjectProperty(locations.description);
		if (locations.defaultMessage) {
			if (removeDefaultMessage) if (isJSX) removeJSXAttribute(locations.defaultMessage);
			else removeObjectProperty(locations.defaultMessage);
			else if (defaultMessage) {
				if (preParseAst) {
					const parsed = parse(defaultMessage);
					const jsonStr = JSON.stringify(parsed);
					s.overwrite(locations.defaultMessage.start, locations.defaultMessage.end, isJSX ? `{${jsonStr}}` : jsonStr);
				} else if (!isJSX && defaultMessage !== locations.defaultMessage.value) s.overwrite(locations.defaultMessage.start, locations.defaultMessage.end, JSON.stringify(defaultMessage));
			}
		}
	}
	function removeObjectProperty(loc) {
		if (!s) return;
		let propStart = loc.start;
		let i = loc.start - 1;
		while (i >= 0 && (code[i] === " " || code[i] === "	" || code[i] === "\n" || code[i] === "\r")) i--;
		if (i >= 0 && code[i] === ":") i--;
		while (i >= 0 && (code[i] === " " || code[i] === "	" || code[i] === "\n" || code[i] === "\r")) i--;
		if (i >= 0 && (code[i] === "\"" || code[i] === "'")) {
			const quote = code[i];
			i--;
			while (i >= 0 && code[i] !== quote) i--;
			if (i >= 0) i--;
		} else while (i >= 0 && /[a-zA-Z0-9_$]/.test(code[i])) i--;
		propStart = i + 1;
		let propEnd = loc.end;
		let j = loc.end;
		while (j < code.length && (code[j] === " " || code[j] === "	")) j++;
		if (j < code.length && code[j] === ",") {
			j++;
			while (j < code.length && (code[j] === " " || code[j] === "	")) j++;
			if (j < code.length && code[j] === "\n") j++;
			else if (j < code.length && code[j] === "\r") {
				j++;
				if (j < code.length && code[j] === "\n") j++;
			}
		}
		propEnd = j;
		let k = propStart - 1;
		while (k >= 0 && (code[k] === " " || code[k] === "	")) k--;
		if (k >= 0 && (code[k] === "\n" || code[k] === ",")) propStart = k + 1;
		s.remove(propStart, propEnd);
	}
	function removeJSXAttribute(loc) {
		if (!s) return;
		let i = loc.start - 1;
		if (i >= 0 && code[i] === "=") i--;
		while (i >= 0 && /[a-zA-Z0-9_$]/.test(code[i])) i--;
		const attrStart = i + 1;
		const attrEnd = loc.end;
		let k = attrStart - 1;
		while (k >= 0 && (code[k] === " " || code[k] === "	")) k--;
		if (k >= 0 && code[k] === "\n") k--;
		if (k >= 0 && code[k] === "\r") k--;
		const removeStart = k + 1;
		s.remove(removeStart, attrEnd);
	}
	function getCalleeName(node) {
		if (node.type === "Identifier") return node.name;
		if (node.type === "MemberExpression" || node.type === "OptionalMemberExpression") {
			if (node.property?.type === "Identifier") return node.property.name;
		}
	}
	function handleCallExpression(node) {
		const calleeName = getCalleeName(node.callee);
		if (calleeName && jsxRuntimeFunctions.has(calleeName)) {
			const firstArg = node.arguments?.[0];
			const componentName = firstArg?.type === "Identifier" ? firstArg.name : void 0;
			if (componentName && componentNames.has(componentName)) {
				const propsArg = unwrapObjectExpression(node.arguments?.[1]);
				if (propsArg) {
					const result = extractDescriptor(propsArg);
					if (result) processDescriptor(result.descriptor, result.locations, false);
				}
			}
		}
		if (calleeName && functionNames.has(calleeName)) if (calleeName === "defineMessages") {
			const arg = unwrapObjectExpression(node.arguments?.[0]);
			if (arg) {
				for (const prop of arg.properties) if (prop.type === "Property") {
					const value = unwrapObjectExpression(prop.value);
					if (!value) continue;
					const result = extractDescriptor(value);
					if (result) processDescriptor(result.descriptor, result.locations, false);
				}
			}
		} else {
			const arg = unwrapObjectExpression(node.arguments?.[0]);
			if (arg) {
				const result = extractDescriptor(arg);
				if (result) processDescriptor(result.descriptor, result.locations, false);
			}
		}
	}
	function handleJSXOpeningElement(node) {
		const name = node.name?.type === "JSXIdentifier" ? node.name.name : void 0;
		if (name && componentNames.has(name)) {
			const result = extractJSXDescriptor(node);
			if (result) processDescriptor(result.descriptor, result.locations, true);
		}
	}
	new Visitor({
		CallExpression: handleCallExpression,
		JSXOpeningElement: handleJSXOpeningElement
	}).visit(program);
	if (!s) return void 0;
	return {
		code: s.toString(),
		map: s.generateMap({ hires: "boundary" })
	};
}
//#endregion
//#region packages/unplugin/index.ts
const DEFAULT_TRANSFORM_FILTER = { id: {
	include: /\.[jt]sx?(?:[?#].*)?$/,
	exclude: /node_modules/
} };
const unpluginFactory = (options = {}) => ({
	name: "formatjs",
	enforce: "pre",
	transform: {
		filter: options.filter ?? DEFAULT_TRANSFORM_FILTER,
		handler(code, id) {
			return transform(code, id, options);
		}
	}
});
const unplugin = /* @__PURE__ */ createUnplugin(unpluginFactory);
//#endregion
export { unplugin as default, unplugin, unpluginFactory };

//# sourceMappingURL=index.js.map