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";
import * as stringifyNs from "json-stable-stringify";
import * as typescript from "typescript";
import { format, styleText } from "node:util";
import { createHash } from "crypto";
import * as path from "path";
//#region packages/ts-transformer/console_utils.ts
const LEVEL_COLORS = {
	debug: "green",
	warn: "yellow",
	error: "red"
};
function label(level, message) {
	return `[@formatjs/ts-transformer] [${styleText(LEVEL_COLORS[level], level.toUpperCase())}] ${message}`;
}
async function debug(message, ...args) {
	if (process.env.LOG_LEVEL !== "debug") return;
	console.error(format(label("debug", message), ...args));
	console.error("\n");
}
//#endregion
//#region packages/ts-transformer/interpolate-name.ts
function getHashDigest(content, hashType = "md5", digestType = "hex", length = 9999) {
	const hasher = createHash(hashType);
	hasher.update(content);
	return hasher.digest(digestType).slice(0, length);
}
function interpolateName(loaderContext, name, options) {
	let filename;
	const hasQuery = loaderContext.resourceQuery && loaderContext.resourceQuery.length > 1;
	if (typeof name === "function") filename = name(loaderContext.resourcePath, hasQuery ? loaderContext.resourceQuery : void 0);
	else filename = name || "[hash].[ext]";
	const context = options.context;
	const content = options.content;
	const regExp = options.regExp;
	let ext = "bin";
	let basename = "file";
	let directory = "";
	let folder = "";
	let query = "";
	if (loaderContext.resourcePath) {
		const parsed = path.parse(loaderContext.resourcePath);
		let resourcePath = loaderContext.resourcePath;
		if (parsed.ext) ext = parsed.ext.slice(1);
		if (parsed.dir) {
			basename = parsed.name;
			resourcePath = parsed.dir + path.sep;
		}
		if (typeof context !== "undefined") {
			directory = path.relative(context, resourcePath + "_").replace(/\\/g, "/").replace(/\.\.(\/)?/g, "_$1");
			directory = directory.slice(0, -1);
		} else directory = resourcePath.replace(/\\/g, "/").replace(/\.\.(\/)?/g, "_$1");
		if (directory.length === 1) directory = "";
		else if (directory.length > 1) folder = path.basename(directory);
	}
	if (loaderContext.resourceQuery && loaderContext.resourceQuery.length > 1) {
		query = loaderContext.resourceQuery;
		const hashIdx = query.indexOf("#");
		if (hashIdx >= 0) query = query.slice(0, hashIdx);
	}
	let url = filename;
	if (content) url = url.replace(/\[(?:([^:\]]+):)?(?:hash|contenthash)(?::([a-z]+\d*[a-z]*))?(?::(\d+))?\]/gi, (_, hashType, digestType, maxLength) => getHashDigest(content, hashType, digestType, parseInt(maxLength, 10)));
	url = url.replace(/\[ext\]/gi, () => ext).replace(/\[name\]/gi, () => basename).replace(/\[path\]/gi, () => directory).replace(/\[folder\]/gi, () => folder).replace(/\[query\]/gi, () => query);
	if (regExp && loaderContext.resourcePath) {
		const match = loaderContext.resourcePath.match(new RegExp(regExp));
		if (match) match.forEach((matched, i) => {
			url = url.replace(new RegExp("\\[" + i + "\\]", "ig"), matched);
		});
	}
	if (typeof loaderContext.options === "object" && typeof loaderContext.options.customInterpolateName === "function") url = loaderContext.options.customInterpolateName.call(loaderContext, url, name, options);
	return url;
}
//#endregion
//#region packages/ts-transformer/transform.ts
const stringify = stringifyNs.default || stringifyNs;
const MESSAGE_DESC_KEYS = [
	"id",
	"defaultMessage",
	"description"
];
function primitiveToTSNode(factory, v) {
	return typeof v === "string" ? factory.createStringLiteral(v) : typeof v === "number" ? factory.createNumericLiteral(v + "") : typeof v === "boolean" ? v ? factory.createTrue() : factory.createFalse() : void 0;
}
function isValidIdentifier(k) {
	try {
		new Function(`return {${k}:1}`);
		return true;
	} catch {
		return false;
	}
}
function objToTSNode(factory, obj) {
	if (typeof obj === "object" && !obj) return factory.createNull();
	const props = Object.entries(obj).filter(([_, v]) => typeof v !== "undefined").map(([k, v]) => factory.createPropertyAssignment(isValidIdentifier(k) ? k : factory.createStringLiteral(k), primitiveToTSNode(factory, v) || (Array.isArray(v) ? factory.createArrayLiteralExpression(v.filter((n) => typeof n !== "undefined").map((n) => objToTSNode(factory, n))) : objToTSNode(factory, v))));
	return factory.createObjectLiteralExpression(props);
}
function messageASTToTSNode(factory, ast) {
	return factory.createArrayLiteralExpression(ast.map((el) => objToTSNode(factory, el)));
}
function literalToObj(ts, n) {
	if (ts.isNumericLiteral(n)) return +n.text;
	if (ts.isStringLiteral(n)) return n.text;
	if (n.kind === ts.SyntaxKind.TrueKeyword) return true;
	if (n.kind === ts.SyntaxKind.FalseKeyword) return false;
}
function objectLiteralExpressionToObj(ts, obj) {
	return obj.properties.reduce((all, prop) => {
		if (ts.isPropertyAssignment(prop) && prop.name) {
			if (ts.isIdentifier(prop.name)) all[prop.name.escapedText.toString()] = literalToObj(ts, prop.initializer);
			else if (ts.isStringLiteral(prop.name)) all[prop.name.text] = literalToObj(ts, prop.initializer);
		}
		return all;
	}, {});
}
const DEFAULT_OPTS = {
	onMsgExtracted: () => void 0,
	onMetaExtracted: () => void 0
};
function isMultipleMessageDecl(ts, node) {
	return ts.isIdentifier(node.expression) && node.expression.text === "defineMessages";
}
function isSingularMessageDecl(ts, node, additionalComponentNames) {
	const compNames = new Set([
		"FormattedMessage",
		"defineMessage",
		"formatMessage",
		"$formatMessage",
		"$t",
		...additionalComponentNames
	]);
	let fnName = "";
	if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) fnName = node.expression.text;
	else if (ts.isJsxOpeningElement(node) && ts.isIdentifier(node.tagName)) fnName = node.tagName.text;
	else if (ts.isJsxSelfClosingElement(node) && ts.isIdentifier(node.tagName)) fnName = node.tagName.text;
	return compNames.has(fnName);
}
function evaluateStringConcat(ts, node) {
	const right = unwrapTransparentTypeScriptExpression(ts, node.right);
	const left = unwrapTransparentTypeScriptExpression(ts, node.left);
	if (!ts.isStringLiteral(right)) return ["", false];
	if (ts.isStringLiteral(left)) return [left.text + right.text, true];
	if (ts.isBinaryExpression(left)) {
		const [result, isStatic] = evaluateStringConcat(ts, left);
		return [result + right.text, isStatic];
	}
	return ["", false];
}
function unwrapTransparentTypeScriptExpression(ts, node) {
	let current = node;
	while (ts.isAsExpression(current) || ts.isSatisfiesExpression(current) || ts.isNonNullExpression(current) || ts.isTypeAssertionExpression(current)) current = current.expression;
	return current;
}
function unwrapObjectLiteralExpression(ts, node) {
	const expression = unwrapTransparentTypeScriptExpression(ts, node);
	return ts.isObjectLiteralExpression(expression) ? expression : void 0;
}
function extractMessageDescriptor(ts, node, { overrideIdFn, extractSourceLocation, preserveWhitespace, flatten, throws = true, onMsgError }, sf) {
	let extractionError = null;
	function handleError(errorMsg, errorNode) {
		let locationMsg = errorMsg;
		if (errorNode) {
			const { line, character } = ts.getLineAndCharacterOfPosition(sf, errorNode.getStart(sf));
			locationMsg = `${sf.fileName}:${line + 1}:${character + 1} ${errorMsg}`;
		}
		const error = new Error(locationMsg);
		if (throws) throw error;
		extractionError = error;
		if (onMsgError) onMsgError(sf.fileName, error);
	}
	let properties = void 0;
	if (ts.isObjectLiteralExpression(node)) properties = node.properties;
	else if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) properties = node.attributes.properties;
	const msg = { id: "" };
	if (!properties) return;
	properties.forEach((prop) => {
		const { name } = prop;
		const initializer = ts.isPropertyAssignment(prop) || ts.isJsxAttribute(prop) ? prop.initializer : void 0;
		if (name && ts.isIdentifier(name) && initializer) {
			const value = ts.isPropertyAssignment(prop) ? unwrapTransparentTypeScriptExpression(ts, prop.initializer) : initializer;
			if (ts.isStringLiteral(value)) switch (name.text) {
				case "id":
					msg.id = value.text;
					break;
				case "defaultMessage":
					msg.defaultMessage = value.text;
					break;
				case "description":
					msg.description = value.text;
					break;
			}
			else if (ts.isNoSubstitutionTemplateLiteral(value)) switch (name.text) {
				case "id":
					msg.id = value.text;
					break;
				case "defaultMessage":
					msg.defaultMessage = value.text;
					break;
				case "description":
					msg.description = value.text;
					break;
			}
			else if (ts.isTaggedTemplateExpression(value)) {
				if (!(name.text === "id" || name.text === "defaultMessage" || name.text === "description")) return;
				const { template } = value;
				if (!ts.isNoSubstitutionTemplateLiteral(template)) {
					handleError("[FormatJS] Tagged template expression must be no substitution", prop);
					return;
				}
				switch (name.text) {
					case "id":
						msg.id = template.text;
						break;
					case "defaultMessage":
						msg.defaultMessage = template.text;
						break;
					case "description":
						msg.description = template.text;
						break;
				}
			} else if (ts.isJsxExpression(value) && value.expression) {
				const expression = unwrapTransparentTypeScriptExpression(ts, value.expression);
				if (ts.isStringLiteral(expression)) switch (name.text) {
					case "id":
						msg.id = expression.text;
						break;
					case "defaultMessage":
						msg.defaultMessage = expression.text;
						break;
					case "description":
						msg.description = expression.text;
						break;
				}
				else if (ts.isObjectLiteralExpression(expression) && name.text === "description") msg.description = objectLiteralExpressionToObj(ts, expression);
				else if (ts.isNoSubstitutionTemplateLiteral(expression)) switch (name.text) {
					case "id":
						msg.id = expression.text;
						break;
					case "defaultMessage":
						msg.defaultMessage = expression.text;
						break;
					case "description":
						msg.description = expression.text;
						break;
				}
				else if (ts.isTaggedTemplateExpression(expression)) {
					if (!(name.text === "id" || name.text === "defaultMessage" || name.text === "description")) return;
					const { template } = expression;
					if (!ts.isNoSubstitutionTemplateLiteral(template)) {
						handleError("[FormatJS] Tagged template expression must be no substitution", prop);
						return;
					}
					switch (name.text) {
						case "id":
							msg.id = template.text;
							break;
						case "defaultMessage":
							msg.defaultMessage = template.text;
							break;
						case "description":
							msg.description = template.text;
							break;
					}
				} else if (ts.isBinaryExpression(expression)) {
					const [result, isStatic] = evaluateStringConcat(ts, expression);
					if (isStatic) switch (name.text) {
						case "id":
							msg.id = result;
							break;
						case "defaultMessage":
							msg.defaultMessage = result;
							break;
						case "description":
							msg.description = result;
							break;
					}
					else if (MESSAGE_DESC_KEYS.includes(name.text) && name.text !== "description") {
						handleError(`[FormatJS] \`${name.text}\` must be a string literal or statically evaluable expression to be extracted.`, prop);
						return;
					}
				} else if (MESSAGE_DESC_KEYS.includes(name.text) && name.text !== "description") {
					handleError(`[FormatJS] \`${name.text}\` must be a string literal to be extracted.`, prop);
					return;
				}
			} else if (ts.isBinaryExpression(value)) {
				const [result, isStatic] = evaluateStringConcat(ts, value);
				if (isStatic) switch (name.text) {
					case "id":
						msg.id = result;
						break;
					case "defaultMessage":
						msg.defaultMessage = result;
						break;
					case "description":
						msg.description = result;
						break;
				}
				else if (MESSAGE_DESC_KEYS.includes(name.text) && name.text !== "description") {
					handleError(`[FormatJS] \`${name.text}\` must be a string literal or statically evaluable expression to be extracted.`, prop);
					return;
				}
			} else if (ts.isObjectLiteralExpression(value) && name.text === "description") msg.description = objectLiteralExpressionToObj(ts, value);
			else if (MESSAGE_DESC_KEYS.includes(name.text) && name.text !== "description") {
				handleError(`[FormatJS] \`${name.text}\` must be a string literal to be extracted.`, prop);
				return;
			}
		}
	});
	if (extractionError) return;
	if (!msg.defaultMessage && !msg.id) return;
	if (msg.defaultMessage && !preserveWhitespace) msg.defaultMessage = msg.defaultMessage.trim().replace(/\s+/gm, " ");
	if (flatten && msg.defaultMessage) try {
		msg.defaultMessage = printAST(hoistSelectors(parse(msg.defaultMessage)));
	} catch (e) {
		const { line, character } = sf.getLineAndCharacterOfPosition(node.pos);
		throw new Error(`[formatjs] Cannot flatten message in file "${sf.fileName}" at line ${line + 1}, column ${character + 1}${msg.id ? ` with id "${msg.id}"` : ""}: ${e.message}\nMessage: ${msg.defaultMessage}`);
	}
	if (msg.defaultMessage && overrideIdFn) switch (typeof overrideIdFn) {
		case "string":
			if (!msg.id) msg.id = interpolateName({ resourcePath: sf.fileName }, overrideIdFn, { content: msg.description ? `${msg.defaultMessage}#${typeof msg.description === "string" ? msg.description : stringify(msg.description)}` : msg.defaultMessage });
			break;
		case "function":
			msg.id = overrideIdFn(msg.id, msg.defaultMessage, msg.description, sf.fileName);
			break;
	}
	if (extractSourceLocation) return {
		...msg,
		file: sf.fileName,
		start: node.pos,
		end: node.end
	};
	return msg;
}
/**
* Check if node is `foo.bar.formatMessage` node
* @param node
* @param sf
*/
function isMemberMethodFormatMessageCall(ts, node, additionalFunctionNames) {
	const fnNames = new Set([
		"formatMessage",
		"$formatMessage",
		...additionalFunctionNames
	]);
	const method = node.expression;
	if (ts.isPropertyAccessExpression(method)) return fnNames.has(method.name.text);
	if (ts.isExpressionWithTypeArguments && ts.isExpressionWithTypeArguments(method)) {
		const expr = method.expression;
		if (ts.isPropertyAccessExpression(expr)) return fnNames.has(expr.name.text);
	}
	return ts.isIdentifier(method) && fnNames.has(method.text);
}
function extractMessageFromJsxComponent(ts, factory, node, opts, sf) {
	const { onMsgExtracted } = opts;
	if (!isSingularMessageDecl(ts, node, opts.additionalComponentNames || [])) return node;
	const msg = extractMessageDescriptor(ts, node, opts, sf);
	if (!msg) return node;
	if (typeof onMsgExtracted === "function") onMsgExtracted(sf.fileName, [msg]);
	const newProps = generateNewProperties(ts, factory, node.attributes, {
		defaultMessage: opts.removeDefaultMessage ? void 0 : msg.defaultMessage,
		id: msg.id
	}, opts.ast);
	if (ts.isJsxOpeningElement(node)) return factory.updateJsxOpeningElement(node, node.tagName, node.typeArguments, factory.createJsxAttributes(newProps));
	return factory.updateJsxSelfClosingElement(node, node.tagName, node.typeArguments, factory.createJsxAttributes(newProps));
}
function setAttributesInObject(ts, factory, node, msg, ast) {
	const newProps = [factory.createPropertyAssignment("id", factory.createStringLiteral(msg.id)), ...msg.defaultMessage ? [factory.createPropertyAssignment("defaultMessage", ast ? messageASTToTSNode(factory, parse(msg.defaultMessage)) : factory.createStringLiteral(msg.defaultMessage))] : []];
	for (const prop of node.properties) {
		if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && MESSAGE_DESC_KEYS.includes(prop.name.text)) continue;
		if (ts.isPropertyAssignment(prop)) newProps.push(prop);
	}
	return factory.createObjectLiteralExpression(factory.createNodeArray(newProps));
}
function generateNewProperties(ts, factory, node, msg, ast) {
	const newProps = [factory.createJsxAttribute(factory.createIdentifier("id"), factory.createStringLiteral(msg.id)), ...msg.defaultMessage ? [factory.createJsxAttribute(factory.createIdentifier("defaultMessage"), ast ? factory.createJsxExpression(void 0, messageASTToTSNode(factory, parse(msg.defaultMessage))) : factory.createStringLiteral(msg.defaultMessage))] : []];
	for (const prop of node.properties) {
		if (ts.isJsxAttribute(prop) && ts.isIdentifier(prop.name) && MESSAGE_DESC_KEYS.includes(prop.name.text)) continue;
		if (ts.isJsxAttribute(prop)) newProps.push(prop);
	}
	return newProps;
}
function extractMessagesFromCallExpression(ts, factory, node, opts, sf) {
	const { onMsgExtracted, additionalFunctionNames } = opts;
	if (isMultipleMessageDecl(ts, node)) {
		const [arg, ...restArgs] = node.arguments;
		const descriptorsObj = unwrapObjectLiteralExpression(ts, arg);
		if (descriptorsObj) {
			const properties = descriptorsObj.properties;
			const msgs = properties.filter((prop) => ts.isPropertyAssignment(prop)).map((prop) => {
				const descriptor = unwrapObjectLiteralExpression(ts, prop.initializer);
				return descriptor && extractMessageDescriptor(ts, descriptor, opts, sf);
			}).filter((msg) => !!msg);
			if (!msgs.length) return node;
			debug("Multiple messages extracted from \"%s\": %s", sf.fileName, msgs);
			if (typeof onMsgExtracted === "function") onMsgExtracted(sf.fileName, msgs);
			const clonedProperties = factory.createNodeArray(properties.map((prop, i) => {
				if (!ts.isPropertyAssignment(prop) || !ts.isObjectLiteralExpression(prop.initializer)) return prop;
				return factory.createPropertyAssignment(prop.name, setAttributesInObject(ts, factory, prop.initializer, {
					defaultMessage: opts.removeDefaultMessage ? void 0 : msgs[i].defaultMessage,
					id: msgs[i] ? msgs[i].id : ""
				}, opts.ast));
			}));
			const clonedDescriptorsObj = factory.createObjectLiteralExpression(clonedProperties);
			return factory.updateCallExpression(node, node.expression, node.typeArguments, [clonedDescriptorsObj, ...restArgs]);
		}
	} else if (isSingularMessageDecl(ts, node, opts.additionalComponentNames || []) || isMemberMethodFormatMessageCall(ts, node, additionalFunctionNames || [])) {
		const [descriptorsObj, ...restArgs] = node.arguments;
		const descriptor = unwrapObjectLiteralExpression(ts, descriptorsObj);
		if (descriptor) {
			const msg = extractMessageDescriptor(ts, descriptor, opts, sf);
			if (!msg) return node;
			debug("Message extracted from \"%s\": %s", sf.fileName, msg);
			if (typeof onMsgExtracted === "function") onMsgExtracted(sf.fileName, [msg]);
			return factory.updateCallExpression(node, node.expression, node.typeArguments, [setAttributesInObject(ts, factory, descriptor, {
				defaultMessage: opts.removeDefaultMessage ? void 0 : msg.defaultMessage,
				id: msg.id
			}, opts.ast), ...restArgs]);
		}
	}
	return node;
}
const PRAGMA_REGEX = /^\/\/ @([^\s]*) (.*)$/m;
function getVisitor(ts, ctx, sf, opts) {
	const visitor = (node) => {
		const newNode = ts.isCallExpression(node) ? extractMessagesFromCallExpression(ts, ctx.factory, node, opts, sf) : ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node) ? extractMessageFromJsxComponent(ts, ctx.factory, node, opts, sf) : node;
		return ts.visitEachChild(newNode, visitor, ctx);
	};
	return visitor;
}
function transformWithTs(ts, opts) {
	opts = {
		...DEFAULT_OPTS,
		...opts
	};
	debug("Transforming options", opts);
	const transformFn = (ctx) => {
		return (sf) => {
			const pragmaResult = PRAGMA_REGEX.exec(sf.text);
			if (pragmaResult) {
				debug("Pragma found", pragmaResult);
				const [, pragma, kvString] = pragmaResult;
				if (pragma === opts.pragma) {
					const kvs = kvString.split(" ");
					const result = {};
					for (const kv of kvs) {
						const [k, v] = kv.split(":");
						result[k] = v;
					}
					debug("Pragma extracted", result);
					if (typeof opts.onMetaExtracted === "function") opts.onMetaExtracted(sf.fileName, result);
				}
			}
			return ts.visitEachChild(sf, getVisitor(ts, ctx, sf, opts), ctx);
		};
	};
	return transformFn;
}
function transform(opts) {
	return transformWithTs(typescript, opts);
}
//#endregion
export { interpolateName, transform, transformWithTs };

//# sourceMappingURL=index.js.map