import path from "node:path";
import { Volume, createFsFromVolume } from "memfs";
import { stringify } from "yaml";
import { loadResolvedSteps } from "../config/loader.js";
import { parsePlan, pipelineDocumentSchemaId } from "../plan/parser.js";
import { runPipeline } from "../run/pipeline.js";
function createSimulationFs(options) {
    const planPath = "/repo/docs/plans/plan.md";
    const files = {
        [planPath]: [
            "---",
            stringify({
                $schema: pipelineDocumentSchemaId,
                kind: "pipeline",
                version: 1,
                ...options.plan
            }).trimEnd(),
            "---",
            ""
        ].join("\n"),
        ...Object.fromEntries(Object.entries(options.files ?? {}).map(([filePath, content]) => [
            path.join("/repo", filePath),
            content
        ]))
    };
    if (options.globalSteps || options.globalStepsSetup || options.globalStepsTeardown) {
        files["/home/test/.poe-code/pipeline/steps/default.yaml"] = stringify({
            ...(options.globalStepsSetup ? { setup: options.globalStepsSetup } : {}),
            ...(options.globalStepsTeardown ? { teardown: options.globalStepsTeardown } : {}),
            ...(options.globalSteps ? { steps: options.globalSteps } : {})
        });
    }
    if (options.projectSteps || options.projectStepsSetup || options.projectStepsTeardown) {
        files["/repo/.poe-code/pipeline/steps/default.yaml"] = stringify({
            ...(options.projectStepsSetup ? { setup: options.projectStepsSetup } : {}),
            ...(options.projectStepsTeardown ? { teardown: options.projectStepsTeardown } : {}),
            ...(options.projectSteps ? { steps: options.projectSteps } : {})
        });
    }
    const volume = Volume.fromJSON(files, "/");
    const rawFs = createFsFromVolume(volume).promises;
    const fs = {
        readFile: (filePath, encoding) => rawFs.readFile(filePath, encoding),
        writeFile: (filePath, data, writeOptions) => rawFs.writeFile(filePath, data, writeOptions),
        readdir: (filePath) => rawFs.readdir(filePath),
        stat: async (filePath) => {
            const stat = await rawFs.stat(filePath);
            return {
                isFile: () => stat.isFile(),
                isDirectory: () => stat.isDirectory(),
                mtimeMs: Number(stat.mtimeMs)
            };
        },
        lstat: async (filePath) => {
            const stat = await rawFs.lstat(filePath);
            return { isSymbolicLink: () => stat.isSymbolicLink() };
        },
        mkdir: (filePath, mkdirOptions) => rawFs.mkdir(filePath, mkdirOptions),
        realpath: (filePath) => rawFs.realpath(filePath),
        rmdir: (filePath) => rawFs.rmdir(filePath),
        rename: (oldPath, newPath) => rawFs.rename(oldPath, newPath),
        unlink: (filePath) => rawFs.unlink(filePath)
    };
    return { fs, planPath };
}
async function applyFileChanges(fs, changes) {
    for (const [filePath, content] of Object.entries(changes)) {
        const absolutePath = path.join("/repo", filePath);
        await fs.mkdir(path.dirname(absolutePath), { recursive: true });
        await fs.writeFile(absolutePath, content, { encoding: "utf8" });
    }
}
function normalizeAgentResult(output) {
    return {
        stdout: output.stdout,
        stderr: output.stderr ?? "",
        exitCode: output.exitCode ?? 0,
        ...(output.usage ? { usage: output.usage } : {})
    };
}
export function successTurn(assertPrompt, fileChanges) {
    return {
        ...(assertPrompt ? { assertPrompt } : {}),
        ...(fileChanges ? { fileChanges } : {}),
        output: {
            stdout: "",
            exitCode: 0
        }
    };
}
export function failTurn(stderr, assertPrompt) {
    return {
        ...(assertPrompt ? { assertPrompt } : {}),
        output: {
            stdout: "",
            stderr,
            exitCode: 1
        }
    };
}
export function createPipelineSimulation(options) {
    return {
        async run() {
            const { fs, planPath } = createSimulationFs(options);
            const turns = [...options.turns];
            const prompts = [];
            const runs = [];
            const taskCompletions = [];
            const readPlan = async () => {
                try {
                    const content = await fs.readFile(planPath, "utf8");
                    const draftPlan = parsePlan(content);
                    const stepsConfig = await loadResolvedSteps({
                        cwd: "/repo",
                        homeDir: "/home/test",
                        fs,
                        name: draftPlan.extends,
                        stepOverrides: draftPlan.stepOverrides
                    });
                    return parsePlan(content, { availableSteps: stepsConfig.steps });
                }
                catch {
                    const archivePath = path.join(path.dirname(planPath), "archive", path.basename(planPath));
                    const content = await fs.readFile(archivePath, "utf8");
                    const draftPlan = parsePlan(content);
                    const stepsConfig = await loadResolvedSteps({
                        cwd: "/repo",
                        homeDir: "/home/test",
                        fs,
                        name: draftPlan.extends,
                        stepOverrides: draftPlan.stepOverrides
                    });
                    return parsePlan(content, { availableSteps: stepsConfig.steps });
                }
            };
            const readFile = async (filePath) => fs.readFile(path.join("/repo", filePath), "utf8");
            const writeFile = async (filePath, content) => {
                const absolutePath = path.join("/repo", filePath);
                await fs.mkdir(path.dirname(absolutePath), { recursive: true });
                await fs.writeFile(absolutePath, content, { encoding: "utf8" });
            };
            const result = await runPipeline({
                agent: "codex",
                cwd: "/repo",
                homeDir: "/home/test",
                plan: "docs/plans/plan.md",
                maxRuns: options.config?.maxRuns,
                logDir: options.config?.logDir,
                onPlanReloadError: options.onPlanReloadError,
                fs,
                onTaskComplete: (progress) => {
                    taskCompletions.push(progress);
                },
                runAgent: async (input) => {
                    const turn = turns.shift();
                    if (!turn) {
                        throw new Error("Pipeline simulation ran out of turns.");
                    }
                    prompts.push(input.prompt);
                    runs.push(input);
                    if (turn.assertPrompt) {
                        await turn.assertPrompt(input.prompt, {
                            fs,
                            readFile,
                            writeFile,
                            readPlan
                        });
                    }
                    if (turn.fileChanges) {
                        await applyFileChanges(fs, turn.fileChanges);
                    }
                    return normalizeAgentResult(turn.output);
                }
            });
            return {
                result,
                prompts,
                runs,
                taskCompletions,
                fs,
                readFile,
                readPlan,
                getTask: async (taskId) => (await readPlan()).tasks.find((task) => task.id === taskId)
            };
        }
    };
}
