import { b as getEnvString, i as logger_default, m as safeJsonStringify } from "./logger-CATn0kWr.js";
import { n as getWrapperDir } from "./esm-tl6dENiB.js";
import fs from "fs";
import path from "path";
import os from "os";
import { execFile } from "child_process";
import { promisify } from "util";

//#region src/ruby/rubyUtils.ts
const execFileAsync = promisify(execFile);
/**
* Global state for Ruby executable path caching.
* Ensures consistent Ruby executable usage across multiple provider instances.
*/
const state = {
	cachedRubyPath: null,
	validationPromise: null,
	validatingPath: null
};
/**
* Attempts to find Ruby using Windows 'where' command.
* Only applicable on Windows platforms.
* @returns The validated Ruby executable path, or null if not found
*/
async function tryWindowsWhere() {
	try {
		const output = (await execFileAsync("where", ["ruby"])).stdout.trim();
		if (!output) {
			logger_default.debug("Windows 'where ruby' returned empty output");
			return null;
		}
		const paths = output.split("\n").filter((path$1) => path$1.trim());
		for (const rubyPath of paths) {
			const trimmedPath = rubyPath.trim();
			if (!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 ruby' failed: ${errorMsg}`);
		if (errorMsg.includes("Access is denied") || errorMsg.includes("EACCES")) logger_default.warn(`Permission denied when searching for Ruby: ${errorMsg}`);
	}
	return null;
}
/**
* Attempts to get Ruby executable path by running Ruby commands.
* Uses RbConfig.ruby to get the actual Ruby executable path.
* @param commands - Array of Ruby command names to try (e.g., ['ruby'])
* @returns The Ruby executable path, or null if all commands fail
*/
async function tryRubyCommands(commands) {
	for (const cmd of commands) try {
		const executablePath = (await execFileAsync(cmd, ["-e", "puts RbConfig.ruby"])).stdout.trim();
		if (executablePath && executablePath !== "None") return executablePath;
	} catch (error) {
		const errorMsg = error instanceof Error ? error.message : String(error);
		logger_default.debug(`Ruby command "${cmd}" failed: ${errorMsg}`);
		if (errorMsg.includes("Access is denied") || errorMsg.includes("EACCES") || errorMsg.includes("EPERM")) logger_default.warn(`Permission denied when trying Ruby command "${cmd}": ${errorMsg}`);
	}
	return null;
}
/**
* Attempts to validate Ruby commands directly as a final fallback.
* Validates each command by running it with --version.
* @param commands - Array of Ruby command names to try (e.g., ['ruby'])
* @returns The validated Ruby executable path, or null if all commands fail
*/
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 Ruby command "${cmd}": ${errorMsg}`);
	}
	return null;
}
/**
* Attempts to get the Ruby executable path using platform-appropriate strategies.
* @returns The Ruby 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 tryRubyCommands(["ruby"]);
		if (sysResult) return sysResult;
		return await tryDirectCommands(["ruby"]);
	} else return await tryRubyCommands(["ruby"]);
}
/**
* Attempts to validate a Ruby executable path.
* @param path - The path to the Ruby 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().toLowerCase().includes("ruby")) return path$1;
		return null;
	} catch {
		if (timeoutId) clearTimeout(timeoutId);
		return null;
	}
}
/**
* Validates and caches the Ruby executable path.
*
* @param rubyPath - Path to the Ruby executable.
* @param isExplicit - If true, only tries the provided path.
* @returns Validated Ruby executable path.
* @throws {Error} If no valid Ruby executable is found.
*/
async function validateRubyPath(rubyPath, isExplicit) {
	if (state.cachedRubyPath && state.validatingPath === rubyPath) return state.cachedRubyPath;
	if (state.validatingPath !== rubyPath) {
		state.cachedRubyPath = null;
		state.validationPromise = null;
		state.validatingPath = rubyPath;
	}
	if (!state.validationPromise) state.validationPromise = (async () => {
		try {
			const primaryPath = await tryPath(rubyPath);
			if (primaryPath) {
				state.cachedRubyPath = primaryPath;
				state.validationPromise = null;
				return primaryPath;
			}
			if (isExplicit) {
				const error$1 = /* @__PURE__ */ new Error(`Ruby not found. Tried "${rubyPath}" Please ensure Ruby is installed and set the PROMPTFOO_RUBY environment variable to your Ruby executable path (e.g., '${process.platform === "win32" ? "C:\\Ruby32\\bin\\ruby.exe" : "/usr/bin/ruby"}').`);
				state.validationPromise = null;
				throw error$1;
			}
			const detectedPath = await getSysExecutable();
			if (detectedPath) {
				state.cachedRubyPath = detectedPath;
				state.validationPromise = null;
				return detectedPath;
			}
			const error = /* @__PURE__ */ new Error(`Ruby not found. Tried "${rubyPath}", ruby executable detection, and fallback commands. Please ensure Ruby is installed and set the PROMPTFOO_RUBY environment variable to your Ruby executable path (e.g., '${process.platform === "win32" ? "C:\\Ruby32\\bin\\ruby.exe" : "/usr/bin/ruby"}').`);
			state.validationPromise = null;
			throw error;
		} catch (error) {
			state.validationPromise = null;
			throw error;
		}
	})();
	return state.validationPromise;
}
/**
* Runs a Ruby script with the specified method and arguments.
*
* @param scriptPath - The path to the Ruby script to run.
* @param method - The name of the method to call in the Ruby script.
* @param args - An array of arguments to pass to the Ruby script.
* @param options - Optional settings for running the Ruby script.
* @param options.rubyExecutable - Optional path to the Ruby executable.
* @returns A promise that resolves to the output of the Ruby script.
* @throws An error if there's an issue running the Ruby script or parsing its output.
*/
async function runRuby(scriptPath, method, args, options = {}) {
	const absPath = path.resolve(scriptPath);
	const tempJsonPath = path.join(os.tmpdir(), `promptfoo-ruby-input-json-${Date.now()}-${Math.random().toString(16).slice(2)}.json`);
	const outputPath = path.join(os.tmpdir(), `promptfoo-ruby-output-json-${Date.now()}-${Math.random().toString(16).slice(2)}.json`);
	const customPath = options.rubyExecutable || getEnvString("PROMPTFOO_RUBY");
	let rubyPath = customPath || "ruby";
	rubyPath = await validateRubyPath(rubyPath, typeof customPath === "string");
	const wrapperPath = path.join(getWrapperDir("ruby"), "wrapper.rb");
	try {
		fs.writeFileSync(tempJsonPath, safeJsonStringify(args), "utf-8");
		logger_default.debug(`Running Ruby wrapper with args: ${safeJsonStringify(args)}`);
		const { stdout, stderr } = await execFileAsync(rubyPath, [
			wrapperPath,
			absPath,
			method,
			tempJsonPath,
			outputPath
		]);
		if (stdout) logger_default.debug(stdout.trim());
		if (stderr) logger_default.error(stderr.trim());
		const output = fs.readFileSync(outputPath, "utf-8");
		logger_default.debug(`Ruby script ${absPath} returned: ${output}`);
		let result;
		try {
			result = JSON.parse(output);
			logger_default.debug(`Ruby 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 Ruby script `call_api` function must return a hash with an `output`");
		return result.data;
	} catch (error) {
		logger_default.error(`Error running Ruby script: ${error.message}\nStack Trace: ${error.stack || "No Ruby traceback available"}`);
		throw new Error(`Error running Ruby script: ${error.message}\nStack Trace: ${error.stack || "No Ruby traceback available"}`);
	} finally {
		[tempJsonPath, outputPath].forEach((file) => {
			try {
				fs.unlinkSync(file);
			} catch (error) {
				logger_default.error(`Error removing ${file}: ${error}`);
			}
		});
	}
}

//#endregion
export { validateRubyPath as a, tryPath as i, runRuby as n, state as r, getSysExecutable as t };
//# sourceMappingURL=rubyUtils-Vv9m0O3v.js.map