import { a as GlobalHooks, c as debug_default, i as CliParser, n as createTestGroup, o as Planner, r as ConfigManager, s as validator_default, t as createTest } from "./create_test-C7_T6lrm.js";
import { a as Emitter, c as Runner, i as printPinnedTests, l as Suite, n as dateTimeDoubles, t as colors } from "./helpers-C1bD1eod.js";
import "./main-CjKU6911.js";
import { fileURLToPath } from "node:url";
import { ErrorsPrinter } from "@japa/errors-printer";
import { join } from "node:path";
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
import findCacheDirectory from "find-cache-directory";
const CACHE_DIR = findCacheDirectory({ name: "@japa/runner" });
const SUMMARY_FILE = CACHE_DIR ? join(CACHE_DIR, "summary.json") : void 0;
async function getFailedTests() {
	try {
		const summary = await readFile(SUMMARY_FILE, "utf-8");
		return JSON.parse(summary);
	} catch (error) {
		if (error.code === "ENOENT") return {};
		throw new Error("Unable to read failed tests cache file", { cause: error });
	}
}
async function cacheFailedTests(tests) {
	await mkdir(CACHE_DIR, { recursive: true });
	await writeFile(SUMMARY_FILE, JSON.stringify({ tests }));
}
const retryPlugin = async function retry({ config, cliArgs: cliArgs$1 }) {
	if (!SUMMARY_FILE) return;
	config.teardown.push(async (runner) => {
		await cacheFailedTests(runner.getSummary().failedTestsTitles);
	});
	if (cliArgs$1.failed) try {
		const { tests } = await getFailedTests();
		if (!tests || !tests.length) {
			console.log(colors.bgYellow().black(" No failing tests found. Running all the tests "));
			return;
		}
		config.filters.tests = tests;
	} catch (error) {
		console.log(colors.bgRed().black(" Unable to read failed tests. Running all the tests "));
		console.log(colors.red(error));
	}
};
var ExceptionsManager = class {
	#exceptionsBuffer = [];
	#rejectionsBuffer = [];
	#state = "watching";
	#errorsPrinter = new ErrorsPrinter({
		stackLinesCount: 2,
		framesMaxLimit: 4
	});
	hasErrors = false;
	monitor() {
		process.on("uncaughtException", async (error) => {
			debug_default("received uncaught exception %O", error);
			this.hasErrors = true;
			if (this.#state === "watching") this.#exceptionsBuffer.push(error);
			else {
				this.#errorsPrinter.printSectionBorder("[Unhandled Error]");
				await this.#errorsPrinter.printError(error);
				process.exitCode = 1;
			}
		});
		process.on("unhandledRejection", async (error) => {
			debug_default("received unhandled rejection %O", error);
			this.hasErrors = true;
			if (this.#state === "watching") this.#rejectionsBuffer.push(error);
			else {
				this.#errorsPrinter.printSectionBorder("[Unhandled Rejection]");
				await this.#errorsPrinter.printError(error);
				process.exitCode = 1;
			}
		});
	}
	async report() {
		if (this.#state === "reporting") return;
		this.#state = "reporting";
		if (this.#exceptionsBuffer.length) {
			let exceptionsCount = this.#exceptionsBuffer.length;
			let exceptionsIndex = this.#exceptionsBuffer.length;
			this.#errorsPrinter.printSectionHeader("Unhandled Errors");
			for (let exception of this.#exceptionsBuffer) {
				await this.#errorsPrinter.printError(exception);
				this.#errorsPrinter.printSectionBorder(`[${++exceptionsIndex}/${exceptionsCount}]`);
			}
			this.#exceptionsBuffer = [];
		}
		if (this.#rejectionsBuffer.length) {
			let rejectionsCount = this.#exceptionsBuffer.length;
			let rejectionsIndex = this.#exceptionsBuffer.length;
			this.#errorsPrinter.printSectionBorder("Unhandled Rejections");
			for (let rejection of this.#rejectionsBuffer) {
				await this.#errorsPrinter.printError(rejection);
				this.#errorsPrinter.printSectionBorder(`[${++rejectionsIndex}/${rejectionsCount}]`);
			}
			this.#rejectionsBuffer = [];
		}
	}
};
const emitter = new Emitter();
let activeTest;
let cliArgs = {};
let runnerConfig;
const executionPlanState = { phase: "idle" };
function test(title, callback) {
	validator_default.ensureIsInPlanningPhase(executionPlanState.phase);
	const debuggingError = /* @__PURE__ */ new Error();
	const testInstance = createTest(title, emitter, runnerConfig.refiner, debuggingError, executionPlanState);
	testInstance.setup((t) => {
		activeTest = t;
		return () => {
			activeTest = void 0;
		};
	});
	if (callback) testInstance.run(callback, debuggingError);
	return testInstance;
}
test.group = function(title, callback) {
	validator_default.ensureIsInPlanningPhase(executionPlanState.phase);
	const group = createTestGroup(title, emitter, runnerConfig.refiner, executionPlanState);
	executionPlanState.group = group;
	if (cliArgs.bail && cliArgs.bailLayer === "group") executionPlanState.group.bail(true);
	callback(executionPlanState.group);
	executionPlanState.group = void 0;
	return group;
};
test.macro = function(callback) {
	return (...args) => {
		if (!activeTest) throw new Error("Cannot invoke macro outside of the test callback");
		return callback(activeTest, ...args);
	};
};
function getActiveTest() {
	return activeTest;
}
function getActiveTestOrFail() {
	if (!activeTest) throw new Error("Cannot access active test outside of a test callback");
	return activeTest;
}
function processCLIArgs(argv) {
	cliArgs = new CliParser().parse(argv);
}
function configure(options) {
	runnerConfig = new ConfigManager(options, cliArgs).hydrate();
}
async function run() {
	if (cliArgs.help) {
		console.log(new CliParser().getHelp());
		return;
	}
	validator_default.ensureIsConfigured(runnerConfig);
	executionPlanState.phase = "planning";
	const runner = new Runner(emitter);
	if (cliArgs.bail && cliArgs.bailLayer === "") runner.bail(true);
	const globalHooks = new GlobalHooks();
	const exceptionsManager = new ExceptionsManager();
	try {
		await retryPlugin({
			config: runnerConfig,
			runner,
			emitter,
			cliArgs
		});
		for (let plugin of runnerConfig.plugins) {
			debug_default("executing \"%s\" plugin", plugin.name || "anonymous");
			await plugin({
				runner,
				emitter,
				cliArgs,
				config: runnerConfig
			});
		}
		const { config, reporters, suites, refinerFilters } = await new Planner(runnerConfig).plan();
		reporters.forEach((reporter) => {
			debug_default("registering \"%s\" reporter", reporter.name);
			runner.registerReporter(reporter);
		});
		refinerFilters.forEach((filter) => {
			debug_default("apply %s filters \"%O\" ", filter.layer, filter.filters);
			config.refiner.add(filter.layer, filter.filters);
		});
		config.refiner.matchAllTags(cliArgs.matchAll ?? false);
		runner.onSuite(config.configureSuite);
		debug_default("executing global hooks");
		globalHooks.apply(config);
		if (!cliArgs.listPinned) await globalHooks.setup(runner);
		for (let suite of suites) {
			debug_default("initiating suite %s", suite.name);
			executionPlanState.suite = new Suite(suite.name, emitter, config.refiner);
			executionPlanState.retries = suite.retries;
			executionPlanState.timeout = suite.timeout;
			if (typeof suite.configure === "function") suite.configure(executionPlanState.suite);
			if (cliArgs.bail && cliArgs.bailLayer === "suite") {
				debug_default("enabling bail mode for the suite %s", suite.name);
				executionPlanState.suite.bail(true);
			}
			runner.add(executionPlanState.suite);
			for (let fileURL of suite.filesURLs) {
				executionPlanState.file = fileURLToPath(fileURL);
				debug_default("importing test file %s", executionPlanState.file);
				await config.importer(fileURL);
			}
			executionPlanState.suite = void 0;
		}
		if (cliArgs.listPinned) {
			printPinnedTests(runner);
			if (config.forceExit) {
				debug_default("force exiting process");
				process.exit();
			}
			return;
		}
		executionPlanState.phase = "executing";
		exceptionsManager.monitor();
		await runner.start();
		await runner.exec();
		await globalHooks.teardown(null, runner);
		await runner.end();
		await exceptionsManager.report();
		const summary = runner.getSummary();
		if (summary.hasError || exceptionsManager.hasErrors) {
			debug_default("updating exit code to 1. summary.hasError %s, process.hasError", summary.hasError, exceptionsManager.hasErrors);
			process.exitCode = 1;
		}
		if (config.forceExit) {
			debug_default("force exiting process");
			process.exit();
		}
	} catch (error) {
		debug_default("error running tests %O", error);
		await globalHooks.teardown(error, runner);
		await new ErrorsPrinter().printError(error);
		await exceptionsManager.report();
		process.exitCode = 1;
		if (runnerConfig.forceExit) {
			debug_default("force exiting process");
			process.exit();
		}
	}
}
const timeTravel = test.macro(($test, durationOrTime) => {
	$test.cleanup(() => {
		dateTimeDoubles.reset();
	});
	dateTimeDoubles.travelTo(durationOrTime);
});
const freezeTime = test.macro(($test, date) => {
	$test.cleanup(() => {
		dateTimeDoubles.reset();
	});
	dateTimeDoubles.freeze(date);
});
export { configure, freezeTime, getActiveTest, getActiveTestOrFail, processCLIArgs, run, test, timeTravel };
