import { O as getEnvString, S as safeJsonStringify, T as getEnvBool, o as logger } from "./logger-CtGm8dC_.js";
import { n as getWrapperDir } from "./esm-CxsIm9IU.js";
import { n as removeSecureTempDirectory, r as writeSecureTempFile, t as createSecureTempDirectory } from "./secureTempFiles-BtI3jQ7b.js";
import fs from "fs/promises";
import path from "path";
import { promisify } from "util";
import { execFile } from "child_process";
import { PythonShell } from "python-shell";
import { StringDecoder } from "string_decoder";
var PythonStderrLogger = class {
	buffer = "";
	decoder = new StringDecoder("utf8");
	inTraceback = false;
	constructor(prefix = "") {
		this.prefix = prefix;
	}
	handleData(data) {
		const text = typeof data === "string" ? data : this.decoder.write(data);
		this.buffer += text;
		const carry = this.buffer.endsWith("\r") ? "\r" : "";
		const lines = (carry ? this.buffer.slice(0, -1) : this.buffer).replace(/\r\n?/g, "\n").split("\n");
		this.buffer = (lines.pop() ?? "") + carry;
		for (const line of lines) this.logLine(line);
		if (this.buffer.length >= 16384) {
			this.logLine(this.buffer);
			this.buffer = "";
		}
	}
	flush() {
		const remaining = this.buffer + this.decoder.end();
		this.buffer = "";
		if (remaining) for (const line of remaining.split(/\r\n|[\r\n]/)) this.logLine(line);
		this.inTraceback = false;
		this.decoder = new StringDecoder("utf8");
	}
	logLine(line) {
		if (!line.trim()) {
			this.inTraceback = false;
			return;
		}
		if (this.inTraceback && /^\s/.test(line)) {
			this.writeLog("error", line);
			return;
		}
		const trimmedStart = line.trimStart();
		if (/^Traceback \(most recent call last\):/i.test(trimmedStart)) {
			this.inTraceback = true;
			this.writeLog("error", line);
			return;
		}
		const prefixMatch = /^(DEBUG|INFO|WARN|WARNING|ERROR|CRITICAL|FATAL)\b[: ]?/i.exec(trimmedStart);
		if (prefixMatch) {
			this.inTraceback = false;
			this.writeLog(this.normalizeLevel(prefixMatch[1]), line);
			return;
		}
		if (this.inTraceback) {
			this.inTraceback = false;
			this.writeLog("error", line);
			return;
		}
		if (/^(During handling of the above exception|The above exception was the direct cause)/i.test(trimmedStart)) {
			this.writeLog("error", line);
			return;
		}
		this.writeLog("warn", line);
	}
	normalizeLevel(level) {
		switch (level.toUpperCase()) {
			case "DEBUG": return "debug";
			case "INFO": return "info";
			case "WARN":
			case "WARNING": return "warn";
			case "ERROR":
			case "CRITICAL":
			case "FATAL": return "error";
			default: return "warn";
		}
	}
	writeLog(level, line) {
		logger[level](`${this.prefix}${line}`);
	}
};
//#endregion
//#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.debug("Windows 'where python' returned empty output");
			return null;
		}
		const paths = output.split("\n").filter((path) => path.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.debug(`Windows 'where python' failed: ${errorMsg}`);
		if (errorMsg.includes("Access is denied") || errorMsg.includes("EACCES")) logger.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.debug(`Python command "${cmd}" failed: ${errorMsg}`);
		if (errorMsg.includes("Access is denied") || errorMsg.includes("EACCES") || errorMsg.includes("EPERM")) logger.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.debug(`Direct command "${cmd}" failed: ${errorMsg}`);
		if (errorMsg.includes("Access is denied") || errorMsg.includes("EACCES") || errorMsg.includes("EPERM")) logger.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) {
	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, ["--version"]), timeoutPromise]);
		if (timeoutId) clearTimeout(timeoutId);
		if (result.stdout.trim().startsWith("Python")) return path;
		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 = /* @__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;
			}
			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 customPath = getConfiguredPythonPath(options.pythonExecutable);
	let pythonPath = customPath || "python";
	let tempDirectory;
	pythonPath = await validatePythonPath(pythonPath, typeof customPath === "string");
	try {
		tempDirectory = await createSecureTempDirectory("promptfoo-python-");
		const tempJsonPath = await writeSecureTempFile(tempDirectory, "input.json", safeJsonStringify(args));
		const outputPath = await writeSecureTempFile(tempDirectory, "output.json", "");
		const pythonOptions = {
			args: [
				absPath,
				method,
				tempJsonPath,
				outputPath
			],
			env: process.env,
			mode: "binary",
			pythonPath,
			scriptPath: getWrapperDir("python"),
			...getEnvBool("PROMPTFOO_PYTHON_DEBUG_ENABLED") && { stdio: "inherit" }
		};
		logger.debug("[Python] Running script", {
			scriptPath: absPath,
			method
		});
		await new Promise((resolve, reject) => {
			try {
				const pyshell = new PythonShell("wrapper.py", pythonOptions);
				const stderrLogger = new PythonStderrLogger();
				pyshell.stdout?.on("data", (chunk) => {
					logger.debug(chunk.toString("utf-8").trim());
				});
				pyshell.stderr?.on("data", (chunk) => {
					stderrLogger.handleData(chunk);
				});
				pyshell.end((err) => {
					stderrLogger.flush();
					if (err) reject(err);
					else resolve();
				});
			} catch (error) {
				reject(error);
			}
		});
		const output = await fs.readFile(outputPath, "utf-8");
		logger.debug("[Python] Script returned a result", { scriptPath: absPath });
		let result;
		try {
			result = JSON.parse(output);
		} catch (error) {
			throw new Error(`Invalid JSON returned by Python script: ${error.message}\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) {
		const message = `Error running Python script: ${error.message}\nStack Trace: ${error.stack?.replace("--- Python Traceback ---", "Python Traceback: ") || "No Python traceback available"}`;
		logger.error(message);
		throw new Error(message);
	} finally {
		if (tempDirectory) try {
			await removeSecureTempDirectory(tempDirectory);
		} catch (error) {
			logger.error(`Error removing temporary Python directory: ${error}`);
		}
	}
}
//#endregion
export { PythonStderrLogger as a, validatePythonPath as i, getEnvInt as n, runPython as r, getConfiguredPythonPath as t };

//# sourceMappingURL=pythonUtils-D75i-07l.js.map