/**
 * Expect-style interactions: send_and_wait, run on session, query shell environment.
 */
import { randomUUID } from "node:crypto";
import { rewriteCommandWithRtk } from "../shared/rtk.js";
// ── Query Shell Environment ────────────────────────────────────────────────
export async function queryShellEnv(bg, timeout, signal) {
	const sentinel = `__SF_ENV_${randomUUID().slice(0, 8)}__`;
	const startIndex = bg.output.length;
	const cmd = [
		`echo "${sentinel}_START"`,
		`echo "CWD=$(pwd)"`,
		`echo "SHELL=$SHELL"`,
		`echo "PATH=$PATH"`,
		`echo "VIRTUAL_ENV=$VIRTUAL_ENV"`,
		`echo "NODE_ENV=$NODE_ENV"`,
		`echo "HOME=$HOME"`,
		`echo "USER=$USER"`,
		`echo "NVM_DIR=$NVM_DIR"`,
		`echo "GOPATH=$GOPATH"`,
		`echo "CARGO_HOME=$CARGO_HOME"`,
		`echo "PYTHONPATH=$PYTHONPATH"`,
		`echo "${sentinel}_END"`,
	].join(" && ");
	bg.proc.stdin?.write(cmd + "\n");
	const start = Date.now();
	while (Date.now() - start < timeout) {
		if (signal?.aborted) return null;
		if (!bg.alive) return null;
		const newEntries = bg.output.slice(startIndex);
		const endIdx = newEntries.findIndex((e) =>
			e.line.includes(`${sentinel}_END`),
		);
		if (endIdx >= 0) {
			const startIdx = newEntries.findIndex((e) =>
				e.line.includes(`${sentinel}_START`),
			);
			if (startIdx >= 0) {
				const envLines = newEntries.slice(startIdx + 1, endIdx);
				const env = {};
				let cwd = "";
				let shell = "";
				for (const entry of envLines) {
					const match = entry.line.match(/^([A-Z_]+)=(.*)$/);
					if (match) {
						const [, key, value] = match;
						if (key === "CWD") {
							cwd = value;
						} else if (key === "SHELL") {
							shell = value;
						} else if (value) {
							env[key] = value;
						}
					}
				}
				return { cwd, env, shell };
			}
		}
		await new Promise((r) => setTimeout(r, 100));
	}
	return null;
}
// ── Send and Wait ──────────────────────────────────────────────────────────
export async function sendAndWait(bg, input, waitPattern, timeout, signal) {
	// Snapshot the current position in the unified buffer before sending
	const startIndex = bg.output.length;
	bg.proc.stdin?.write(input + "\n");
	let re;
	try {
		re = new RegExp(waitPattern, "i");
	} catch {
		return { matched: false, output: "Invalid wait pattern regex" };
	}
	const start = Date.now();
	while (Date.now() - start < timeout) {
		if (signal?.aborted) {
			const newEntries = bg.output.slice(startIndex);
			return {
				matched: false,
				output: newEntries.map((e) => e.line).join("\n") || "(cancelled)",
			};
		}
		const newEntries = bg.output.slice(startIndex);
		for (const entry of newEntries) {
			if (re.test(entry.line)) {
				return {
					matched: true,
					output: newEntries.map((e) => e.line).join("\n"),
				};
			}
		}
		await new Promise((r) => setTimeout(r, 100));
	}
	const newEntries = bg.output.slice(startIndex);
	return {
		matched: false,
		output: newEntries.map((e) => e.line).join("\n") || "(no output)",
	};
}
// ── Run on Session ─────────────────────────────────────────────────────────
export async function runOnSession(bg, command, timeout, signal) {
	const sentinel = randomUUID().slice(0, 8);
	const startMarker = `__SF_SENTINEL_${sentinel}_START__`;
	const endMarker = `__SF_SENTINEL_${sentinel}_END__`;
	const exitVar = `__SF_EXIT_${sentinel}__`;
	// Snapshot current output buffer position
	const startIndex = bg.output.length;
	// Write the sentinel-wrapped command to stdin
	const rewrittenCommand = rewriteCommandWithRtk(command);
	const wrappedCommand = [
		`echo ${startMarker}`,
		rewrittenCommand,
		`${exitVar}=$?`,
		`echo ${endMarker} $${exitVar}`,
	].join("\n");
	bg.proc.stdin?.write(wrappedCommand + "\n");
	const start = Date.now();
	while (Date.now() - start < timeout) {
		if (signal?.aborted) {
			const newEntries = bg.output.slice(startIndex);
			return {
				exitCode: -1,
				output: newEntries.map((e) => e.line).join("\n") || "(cancelled)",
				timedOut: false,
			};
		}
		// Process died while waiting
		if (!bg.alive) {
			const newEntries = bg.output.slice(startIndex);
			const lines = newEntries.map((e) => e.line);
			return {
				exitCode: bg.proc.exitCode ?? -1,
				output: lines.join("\n") || "(process exited)",
				timedOut: false,
			};
		}
		const newEntries = bg.output.slice(startIndex);
		for (let i = 0; i < newEntries.length; i++) {
			if (newEntries[i].line.includes(endMarker)) {
				// Parse exit code from the END sentinel line
				const endLine = newEntries[i].line;
				const exitMatch = endLine.match(new RegExp(`${endMarker}\\s+(\\d+)`));
				const exitCode = exitMatch ? parseInt(exitMatch[1], 10) : -1;
				// Extract output between START and END sentinels
				const outputLines = [];
				let capturing = false;
				for (let j = 0; j < newEntries.length; j++) {
					if (newEntries[j].line.includes(startMarker)) {
						capturing = true;
						continue;
					}
					if (newEntries[j].line.includes(endMarker)) {
						break;
					}
					if (capturing) {
						outputLines.push(newEntries[j].line);
					}
				}
				return { exitCode, output: outputLines.join("\n"), timedOut: false };
			}
		}
		await new Promise((r) => setTimeout(r, 100));
	}
	// Timed out
	const newEntries = bg.output.slice(startIndex);
	const outputLines = [];
	let capturing = false;
	for (const entry of newEntries) {
		if (entry.line.includes(startMarker)) {
			capturing = true;
			continue;
		}
		if (capturing) {
			outputLines.push(entry.line);
		}
	}
	return {
		exitCode: -1,
		output: outputLines.join("\n") || "(no output)",
		timedOut: true,
	};
}
