import module$1, { isBuiltin } from 'node:module';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { automockModule, createManualModuleSource, collectModuleExports } from '@vitest/mocker/transforms';
import { cleanUrl, createDefer } from '@vitest/utils/helpers';
import { p as parse } from './acorn.B2iPLyUM.js';
import { isAbsolute } from 'pathe';
import { t as toBuiltin } from './modules.BJuCwlRJ.js';
import { B as BareModuleMocker, n as normalizeModuleId } from './startVitestModuleRunner.C3ZR-4J3.js';
import 'node:fs';
import './utils.BX5Fg8C4.js';
import '@vitest/utils/timers';
import '../path.js';
import 'node:path';
import '../module-evaluator.js';
import 'node:vm';
import 'vite/module-runner';
import './traces.CCmnQaNT.js';
import '@vitest/mocker';
import '@vitest/mocker/redirect';

class NativeModuleMocker extends BareModuleMocker {
	wrapDynamicImport(moduleFactory) {
		if (typeof moduleFactory === "function") return new Promise((resolve, reject) => {
			this.resolveMocks().finally(() => {
				moduleFactory().then(resolve, reject);
			});
		});
		return moduleFactory;
	}
	resolveMockedModule(url, parentURL) {
		// don't mock modules inside of packages because there is
		// a high chance that it uses `require` which is not mockable
		// because we use top-level await in "manual" mocks.
		// for the sake of consistency we don't support mocking anything at all
		if (parentURL.includes("/node_modules/")) return;
		const moduleId = normalizeModuleId(url.startsWith("file://") ? fileURLToPath(url) : url);
		const mockedModule = this.getDependencyMock(moduleId);
		if (!mockedModule) return;
		if (mockedModule.type === "redirect") return {
			url: pathToFileURL(mockedModule.redirect).toString(),
			shortCircuit: true
		};
		if (mockedModule.type === "automock" || mockedModule.type === "autospy") return {
			url: injectQuery(url, parentURL, `mock=${mockedModule.type}`),
			shortCircuit: true
		};
		if (mockedModule.type === "manual") return {
			url: injectQuery(url, parentURL, "mock=manual"),
			shortCircuit: true
		};
	}
	loadAutomock(url, result) {
		const moduleId = cleanUrl(normalizeModuleId(url.startsWith("file://") ? fileURLToPath(url) : url));
		let source;
		if (isBuiltin(moduleId)) {
			const builtinModule = getBuiltinModule(moduleId);
			const exports$1 = Object.keys(builtinModule);
			source = `
import * as builtinModule from '${toBuiltin(moduleId)}?mock=actual'

${exports$1.map((key, index) => {
				return `
const __${index} = builtinModule["${key}"]
export { __${index} as "${key}" }
`;
			}).join("")}`;
		} else source = result.source?.toString();
		if (source == null) return;
		const mockType = url.includes("mock=automock") ? "automock" : "autospy";
		const transformedCode = transformCode(source, result.format || "module", moduleId);
		try {
			const ms = automockModule(transformedCode, mockType, (code) => parse(code, {
				sourceType: "module",
				ecmaVersion: "latest"
			}), { id: moduleId });
			return {
				format: "module",
				source: `${ms.toString()}\n//# sourceMappingURL=${genSourceMapUrl(ms.generateMap({
					hires: "boundary",
					source: moduleId
				}))}`,
				shortCircuit: true
			};
		} catch (cause) {
			throw new Error(`Cannot automock '${url}' because it failed to parse.`, { cause });
		}
	}
	loadManualMock(url, result) {
		const moduleId = cleanUrl(normalizeModuleId(url.startsWith("file://") ? fileURLToPath(url) : url));
		// should not be possible
		if (this.getDependencyMock(moduleId)?.type !== "manual") {
			console.warn(`Vitest detected unregistered manual mock ${moduleId}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
			return;
		}
		if (isBuiltin(moduleId)) {
			const builtinModule = getBuiltinModule(toBuiltin(moduleId));
			return {
				format: "module",
				source: createManualModuleSource(moduleId, Object.keys(builtinModule)),
				shortCircuit: true
			};
		}
		if (!result.source) return;
		const transformedCode = transformCode(result.source.toString(), result.format || "module", moduleId);
		if (transformedCode == null) return;
		const format = result.format?.startsWith("module") ? "module" : "commonjs";
		try {
			return {
				format: "module",
				source: createManualModuleSource(moduleId, collectModuleExports(moduleId, transformedCode, format)),
				shortCircuit: true
			};
		} catch (cause) {
			throw new Error(`Failed to mock '${url}'. See the cause for more information.`, { cause });
		}
	}
	processedModules = /* @__PURE__ */ new Map();
	checkCircularManualMock(url) {
		const id = cleanUrl(normalizeModuleId(url.startsWith("file://") ? fileURLToPath(url) : url));
		this.processedModules.set(id, (this.processedModules.get(id) ?? 0) + 1);
		// the module is mocked and requested a second time, let's resolve
		// the factory function that will redefine the exports later
		if (this.originalModulePromises.has(id)) {
			const factoryPromise = this.factoryPromises.get(id);
			this.originalModulePromises.get(id)?.resolve({ __factoryPromise: factoryPromise });
		}
	}
	originalModulePromises = /* @__PURE__ */ new Map();
	factoryPromises = /* @__PURE__ */ new Map();
	// potential performance improvement:
	// store by URL, not ids, no need to call url.*to* methods and normalizeModuleId
	getFactoryModule(id) {
		const mock = this.getMockerRegistry().getById(id);
		if (!mock || mock.type !== "manual") throw new Error(`Mock ${id} wasn't registered. This is probably a Vitest error. Please, open a new issue with reproduction.`);
		const mockResult = mock.resolve();
		if (mockResult instanceof Promise) {
			// to avoid circular dependency, we resolve this function as {__factoryPromise} in `checkCircularManualMock`
			// when it's requested the second time. then the exports are exposed as `undefined`,
			// but later redefined when the promise is actually resolved
			const promise = createDefer();
			promise.finally(() => {
				this.originalModulePromises.delete(id);
			});
			mockResult.then(promise.resolve, promise.reject).finally(() => {
				this.factoryPromises.delete(id);
			});
			this.factoryPromises.set(id, mockResult);
			this.originalModulePromises.set(id, promise);
			// Node.js on windows processes all the files first, and then runs them
			// unlike Node.js logic on Mac and Unix where it also runs the code while evaluating
			// So on Linux/Mac this `if` won't be hit because `checkCircularManualMock` will resolve it
			// And on Windows, the `checkCircularManualMock` will never have `originalModulePromises`
			// because `getFactoryModule` is not called until the evaluation phase
			// But if we track how many times the module was transformed,
			// we can deduce when to return `__factoryPromise` to support circular modules
			if ((this.processedModules.get(id) ?? 0) > 1) {
				this.processedModules.set(id, (this.processedModules.get(id) ?? 1) - 1);
				promise.resolve({ __factoryPromise: mockResult });
			}
			return promise;
		}
		return mockResult;
	}
	importActual(rawId, importer) {
		const resolvedId = import.meta.resolve(rawId, pathToFileURL(importer).toString());
		const url = new URL(resolvedId);
		url.searchParams.set("mock", "actual");
		return import(url.toString());
	}
	importMock(rawId, importer) {
		const resolvedId = import.meta.resolve(rawId, pathToFileURL(importer).toString());
		// file is already mocked
		if (resolvedId.includes("mock=")) return import(resolvedId);
		const filename = fileURLToPath(resolvedId);
		const external = !isAbsolute(filename) || this.isModuleDirectory(resolvedId) ? normalizeModuleId(rawId) : null;
		// file is not mocked, automock or redirect it
		const redirect = this.findMockRedirect(filename, external);
		if (redirect) return import(pathToFileURL(redirect).toString());
		const url = new URL(resolvedId);
		url.searchParams.set("mock", "automock");
		return import(url.toString());
	}
}
const replacePercentageRE = /%/g;
function injectQuery(url, importer, queryToInject) {
	const { search, hash } = new URL(url.replace(replacePercentageRE, "%25"), importer);
	return `${cleanUrl(url)}?${queryToInject}${search ? `&${search.slice(1)}` : ""}${hash ?? ""}`;
}
let __require;
function getBuiltinModule(moduleId) {
	__require ??= module$1.createRequire(import.meta.url);
	return __require(`${moduleId}?mock=actual`);
}
function genSourceMapUrl(map) {
	if (typeof map !== "string") map = JSON.stringify(map);
	return `data:application/json;base64,${Buffer.from(map).toString("base64")}`;
}
function transformCode(code, format, filename) {
	if (format.includes("typescript")) {
		if (!module$1.stripTypeScriptTypes) throw new Error(`Cannot parse '${filename}' because "module.stripTypeScriptTypes" is not supported. Module mocking requires Node.js 22.15 or higher. This is NOT a bug of Vitest.`);
		return module$1.stripTypeScriptTypes(code);
	}
	return code;
}

export { NativeModuleMocker };
