import { _ as getEnvBool, b as getEnvString, i as logger_default, m as safeJsonStringify } from "./logger-BudIH0qK.js";
import { n as getWrapperDir } from "./esm-C5zIToiC.js";
import fs from "fs";
import path from "path";
import os from "os";
import { execFile } from "child_process";
import { promisify } from "util";
import { PythonShell } from "python-shell";

//#region src/python/pythonUtils.ts
const execFileAsync = promisify(execFile);
/**
* Gets an integer value from an environment variable.
* @param key - The environment variable name
* @returns The parsed integer value, or undefined if not set or not a valid integer
*/
function getEnvInt(key) {
	const value = process.env[key];
	if (value === void 0) return;
	const parsed = parseInt(value, 10);
	return isNaN(parsed) ? void 0 : parsed;
}
/**
* Resolves the Python executable path from explicit config and environment.
* This centralizes the fallback logic: configPath > PROMPTFOO_PYTHON env var.
*
* Note: Does NOT apply the final 'python' default - that's handled by
* validatePythonPath. This preserves the distinction between "explicitly
* configured" (should fail if invalid) and "using system default" (should
* try fallback detection).
*
* @param configPath - Explicitly configured Python path from provider config
* @returns The configured path, or undefined if neither config nor env var is set
*/
function getConfiguredPythonPath(configPath) {
	if (configPath) return configPath;
	return getEnvString("PROMPTFOO_PYTHON") || void 0;
}
const state = {
	cachedPythonPath: null,
	validationPromise: null
};
/**
* Try to find Python using Windows 'where' command, filtering out Microsoft Store stubs.
*/
async function tryWindowsWhere() {
	try {
		const output = (await execFileAsync("where", ["python"])).stdout.trim();
		if (!output) {
			logger_default.debug("Windows 'where python' returned empty output");
			return null;
		}
		const paths = output.split("\n").filter((path$1) => path$1.trim());
		for (const pythonPath of paths) {
			const trimmedPath = pythonPath.trim();
			if (trimmedPath.includes("WindowsApps") || !trimmedPath.endsWith(".exe")) continue;
			const validated = await tryPath(trimmedPath);
			if (validated) return validated;
		}
	} catch (error) {
		const errorMsg = error instanceof Error ? error.message : String(error);
		logger_default.debug(`Windows 'where python' failed: ${errorMsg}`);
		if (errorMsg.includes("Access is denied") || errorMsg.includes("EACCES")) logger_default.warn(`Permission denied when searching for Python: ${errorMsg}`);
	}
	return null;
}
/**
* Try Python commands to get sys.executable path.
*/
async function tryPythonCommands(commands) {
	for (const cmd of commands) try {
		const executablePath = (await execFileAsync(cmd, ["-c", "import sys; print(sys.executable)"])).stdout.trim();
		if (executablePath && executablePath !== "None") {
			if (process.platform === "win32" && !executablePath.toLowerCase().endsWith(".exe")) {
				if (executablePath.includes("\\") || /^[A-Za-z]:/.test(executablePath)) return executablePath + ".exe";
			}
			return executablePath;
		}
	} catch (error) {
		const errorMsg = error instanceof Error ? error.message : String(error);
		logger_default.debug(`Python command "${cmd}" failed: ${errorMsg}`);
		if (errorMsg.includes("Access is denied") || errorMsg.includes("EACCES") || errorMsg.includes("EPERM")) logger_default.warn(`Permission denied when trying Python command "${cmd}": ${errorMsg}`);
	}
	return null;
}
/**
* Try direct command validation as final fallback.
*/
async function tryDirectCommands(commands) {
	for (const cmd of commands) try {
		const validated = await tryPath(cmd);
		if (validated) return validated;
	} catch (error) {
		const errorMsg = error instanceof Error ? error.message : String(error);
		logger_default.debug(`Direct command "${cmd}" failed: ${errorMsg}`);
		if (errorMsg.includes("Access is denied") || errorMsg.includes("EACCES") || errorMsg.includes("EPERM")) logger_default.warn(`Permission denied when trying Python command "${cmd}": ${errorMsg}`);
	}
	return null;
}
/**
* Attempts to get the Python executable path using platform-appropriate strategies.
* @returns The Python executable path if successful, or null if failed.
*/
async function getSysExecutable() {
	if (process.platform === "win32") {
		const whereResult = await tryWindowsWhere();
		if (whereResult) return whereResult;
		const sysResult = await tryPythonCommands(["py", "py -3"]);
		if (sysResult) return sysResult;
		return await tryDirectCommands(["python"]);
	} else return await tryPythonCommands(["python3", "python"]);
}
/**
* Attempts to validate a Python executable path.
* @param path - The path to the Python executable to test.
* @returns The validated path if successful, or null if invalid.
*/
async function tryPath(path$1) {
	let timeoutId;
	try {
		const timeoutPromise = new Promise((_, reject) => {
			timeoutId = setTimeout(() => reject(/* @__PURE__ */ new Error("Command timed out")), 2500);
		});
		const result = await Promise.race([execFileAsync(path$1, ["--version"]), timeoutPromise]);
		if (timeoutId) clearTimeout(timeoutId);
		if (result.stdout.trim().startsWith("Python")) return path$1;
		return null;
	} catch {
		if (timeoutId) clearTimeout(timeoutId);
		return null;
	}
}
/**
* Validates and caches the Python executable path.
*
* @param pythonPath - Path to the Python executable.
* @param isExplicit - If true, only tries the provided path.
* @returns Validated Python executable path.
* @throws {Error} If no valid Python executable is found.
*/
async function validatePythonPath(pythonPath, isExplicit) {
	if (state.cachedPythonPath) return state.cachedPythonPath;
	if (!state.validationPromise) state.validationPromise = (async () => {
		try {
			const primaryPath = await tryPath(pythonPath);
			if (primaryPath) {
				state.cachedPythonPath = primaryPath;
				state.validationPromise = null;
				return primaryPath;
			}
			if (isExplicit) {
				const error$1 = /* @__PURE__ */ new Error(`Python 3 not found. Tried "${pythonPath}" Please ensure Python 3 is installed and set the PROMPTFOO_PYTHON environment variable to your Python 3 executable path (e.g., '${process.platform === "win32" ? "C:\\Python39\\python.exe" : "/usr/bin/python3"}').`);
				state.validationPromise = null;
				throw error$1;
			}
			const detectedPath = await getSysExecutable();
			if (detectedPath) {
				state.cachedPythonPath = detectedPath;
				state.validationPromise = null;
				return detectedPath;
			}
			const error = /* @__PURE__ */ new Error(`Python 3 not found. Tried "${pythonPath}", sys.executable detection, and fallback commands. Please ensure Python 3 is installed and set the PROMPTFOO_PYTHON environment variable to your Python 3 executable path (e.g., '${process.platform === "win32" ? "C:\\Python39\\python.exe" : "/usr/bin/python3"}').`);
			state.validationPromise = null;
			throw error;
		} catch (error) {
			state.validationPromise = null;
			throw error;
		}
	})();
	return state.validationPromise;
}
/**
* Runs a Python script with the specified method and arguments.
*
* @param scriptPath - The path to the Python script to run.
* @param method - The name of the method to call in the Python script.
* @param args - An array of arguments to pass to the Python script.
* @param options - Optional settings for running the Python script.
* @param options.pythonExecutable - Optional path to the Python executable.
* @returns A promise that resolves to the output of the Python script.
* @throws An error if there's an issue running the Python script or parsing its output.
*/
async function runPython(scriptPath, method, args, options = {}) {
	const absPath = path.resolve(scriptPath);
	const tempJsonPath = path.join(os.tmpdir(), `promptfoo-python-input-json-${Date.now()}-${Math.random().toString(16).slice(2)}.json`);
	const outputPath = path.join(os.tmpdir(), `promptfoo-python-output-json-${Date.now()}-${Math.random().toString(16).slice(2)}.json`);
	const customPath = getConfiguredPythonPath(options.pythonExecutable);
	let pythonPath = customPath || "python";
	pythonPath = await validatePythonPath(pythonPath, typeof customPath === "string");
	const pythonOptions = {
		args: [
			absPath,
			method,
			tempJsonPath,
			outputPath
		],
		env: process.env,
		mode: "binary",
		pythonPath,
		scriptPath: getWrapperDir("python"),
		...getEnvBool("PROMPTFOO_PYTHON_DEBUG_ENABLED") && { stdio: "inherit" }
	};
	try {
		fs.writeFileSync(tempJsonPath, safeJsonStringify(args), "utf-8");
		logger_default.debug(`Running Python wrapper with args: ${safeJsonStringify(args)}`);
		await new Promise((resolve, reject) => {
			try {
				const pyshell = new PythonShell("wrapper.py", pythonOptions);
				pyshell.stdout?.on("data", (chunk) => {
					logger_default.debug(chunk.toString("utf-8").trim());
				});
				pyshell.stderr?.on("data", (chunk) => {
					logger_default.error(chunk.toString("utf-8").trim());
				});
				pyshell.end((err) => {
					if (err) reject(err);
					else resolve();
				});
			} catch (error) {
				reject(error);
			}
		});
		const output = fs.readFileSync(outputPath, "utf-8");
		logger_default.debug(`Python script ${absPath} returned: ${output}`);
		let result;
		try {
			result = JSON.parse(output);
			logger_default.debug(`Python script ${absPath} parsed output type: ${typeof result}, structure: ${result ? JSON.stringify(Object.keys(result)) : "undefined"}`);
		} catch (error) {
			throw new Error(`Invalid JSON: ${error.message} when parsing result: ${output}\nStack Trace: ${error.stack}`);
		}
		if (result?.type !== "final_result") throw new Error("The Python script `call_api` function must return a dict with an `output`");
		return result.data;
	} catch (error) {
		logger_default.error(`Error running Python script: ${error.message}\nStack Trace: ${error.stack?.replace("--- Python Traceback ---", "Python Traceback: ") || "No Python traceback available"}`);
		throw new Error(`Error running Python script: ${error.message}\nStack Trace: ${error.stack?.replace("--- Python Traceback ---", "Python Traceback: ") || "No Python traceback available"}`);
	} finally {
		[tempJsonPath, outputPath].forEach((file) => {
			try {
				fs.unlinkSync(file);
			} catch (error) {
				logger_default.error(`Error removing ${file}: ${error}`);
			}
		});
	}
}

//#endregion
export { validatePythonPath as i, getEnvInt as n, runPython as r, getConfiguredPythonPath as t };
//# sourceMappingURL=pythonUtils-BiVLWZ6g.js.map