/**
 * Sandbox configuration and lifecycle extensions for agent harness tasks.
 *
 * Builds Promptfoo beforeEach/afterEach hooks for provisioning and
 * tearing down sandbox working directories.
 */
import { resolve } from "path";
// ---------------------------------------------------------------------------
// Sandbox configuration
// ---------------------------------------------------------------------------
/**
 * Build sandbox configuration from a task definition.
 *
 * Fixture paths are resolved to absolute at compile time using callerCwd
 * (the directory the pipeline was invoked from), because promptfoo runs
 * with cwd set to packages/eval/ — not the monorepo root where apps/ lives.
 */
export function buildSandboxConfig(task, callerCwd) {
    const cwd = callerCwd ?? process.cwd();
    return {
        type: task.sandbox?.type ?? "tempdir",
        image: task.sandbox?.image,
        fixtures: (task.fixtures ?? []).map((f) => {
            const stripped = f.startsWith("file://") ? f.slice(7) : f;
            return resolve(cwd, stripped);
        }),
        limits: task.sandbox?.limits
            ? {
                cpus: task.sandbox.limits.cpus,
                memoryBytes: task.sandbox.limits.memoryBytes,
                networkAccess: task.sandbox.limits.networkAccess,
            }
            : undefined,
    };
}
// ---------------------------------------------------------------------------
// Lifecycle extensions
// ---------------------------------------------------------------------------
export function buildLifecycleExtensions(task, sandboxConfig) {
    const extensions = [];
    // beforeEach: provision sandbox + inject fixtures
    extensions.push({
        type: "beforeEach",
        code: buildBeforeEachHook(task.id, sandboxConfig),
    });
    // afterEach: collect artifacts + teardown
    extensions.push({
        type: "afterEach",
        code: buildAfterEachHook(task.id),
    });
    return extensions;
}
export function buildBeforeEachHook(taskId, config) {
    // Promptfoo extension hooks receive (hookName, context).
    // beforeEach context is { test } — vars live at context.test.vars.
    // Must return context for mutations to persist.
    //
    // The sandbox directory is created by the YAML writer at config-gen time
    // (deterministic path in results/latest/sandbox-{taskId}/) so it exists
    // before the provider is initialized. This hook copies fixtures into it.
    //
    // @see https://www.promptfoo.dev/docs/configuration/reference/ — extensions
    return (`// beforeEach: copy fixtures into sandbox for ${taskId}\n` +
        `async function(context) {\n` +
        `  const { cpSync, existsSync, mkdirSync } = require('fs');\n` +
        `  const { resolve } = require('path');\n` +
        `  const workDir = context.test.vars?.__workingDir;\n` +
        `  if (!workDir) return context;\n` +
        `  mkdirSync(workDir, { recursive: true });\n` +
        `  // Copy fixtures into sandbox\n` +
        `  const fixtures = ${JSON.stringify(config.fixtures)};\n` +
        `  for (const fixture of fixtures) {\n` +
        `    const src = resolve(process.cwd(), fixture);\n` +
        `    if (existsSync(src)) {\n` +
        `      cpSync(src, workDir, { recursive: true });\n` +
        `    }\n` +
        `  }\n` +
        `  return context;\n` +
        `}`);
}
export function buildAfterEachHook(taskId) {
    // Promptfoo extension hooks receive (hookName, context).
    // afterEach context is { test, result } — vars live at context.test.vars.
    // @see https://www.promptfoo.dev/docs/configuration/reference/ — extensions
    return (`// afterEach: collect artifacts + teardown for ${taskId}\n` +
        `async function(context) {\n` +
        `  const { rmSync, readdirSync, existsSync } = require('fs');\n` +
        `  const vars = context.test.vars || {};\n` +
        `  const workDir = vars.__workingDir;\n` +
        `  if (workDir && existsSync(workDir)) {\n` +
        `    try {\n` +
        `      // Collect modified files list\n` +
        `      const files = [];\n` +
        `      function collect(dir, prefix) {\n` +
        `        for (const e of readdirSync(dir, { withFileTypes: true })) {\n` +
        `          const rel = prefix ? prefix + '/' + e.name : e.name;\n` +
        `          if (e.isDirectory()) collect(require('path').resolve(dir, e.name), rel);\n` +
        `          else files.push(rel);\n` +
        `        }\n` +
        `      }\n` +
        `      collect(workDir, '');\n` +
        `      vars.__artifacts = { modifiedFiles: files };\n` +
        `    } finally {\n` +
        `      // Guard: only delete directories under os.tmpdir()\n` +
        `      const tmp = require('os').tmpdir();\n` +
        `      if (require('path').resolve(workDir).startsWith(require('path').resolve(tmp))) {\n` +
        `        rmSync(workDir, { recursive: true, force: true });\n` +
        `      }\n` +
        `    }\n` +
        `  }\n` +
        `}`);
}
