import path from "node:path";
import { Volume, createFsFromVolume } from "memfs";
import { resolveWorkflowPath } from "@poe-code/agent-harness-tools";
import { runRalph } from "../run/ralph.js";
function createSimulationFs(options) {
    const cwd = "/repo";
    const homeDir = "/home/test";
    const docPath = options.docPath ?? ".poe-code/ralph/plans/plan.md";
    const absoluteDocPath = resolveWorkflowPath(docPath, cwd, homeDir);
    const files = {
        [absoluteDocPath]: options.docContent ?? "Run the loop",
        ...Object.fromEntries(Object.entries(options.files ?? {}).map(([filePath, content]) => [
            path.join(cwd, filePath),
            content
        ]))
    };
    const volume = Volume.fromJSON(files, "/");
    const rawFs = createFsFromVolume(volume).promises;
    const fs = {
        readFile: (filePath, encoding) => rawFs.readFile(filePath, encoding),
        writeFile: async (filePath, content, options) => {
            await rawFs.mkdir(path.dirname(filePath), { recursive: true });
            await rawFs.writeFile(filePath, content, { encoding: "utf8", ...options });
        },
        readdir: (filePath) => rawFs.readdir(filePath),
        open: (filePath, flags) => rawFs.open(filePath, flags),
        lstat: async (filePath) => {
            const stat = await rawFs.lstat(filePath);
            return { isSymbolicLink: () => stat.isSymbolicLink() };
        },
        stat: async (filePath) => {
            const stat = await rawFs.stat(filePath);
            return {
                isFile: () => stat.isFile(),
                isDirectory: () => stat.isDirectory(),
                mtimeMs: Number(stat.mtimeMs)
            };
        },
        unlink: async (filePath) => {
            await rawFs.unlink(filePath);
        },
        mkdir: async (filePath, options) => {
            await rawFs.mkdir(filePath, options);
        },
        rmdir: async (filePath) => {
            await rawFs.rmdir(filePath);
        },
        realpath: (filePath) => rawFs.realpath(filePath),
        rename: async (oldPath, newPath) => {
            await rawFs.mkdir(path.dirname(newPath), { recursive: true });
            await rawFs.rename(oldPath, newPath);
        }
    };
    return {
        fs,
        docPath,
        cwd,
        homeDir,
        rawFs
    };
}
async function applyFileChanges(rawFs, cwd, changes) {
    for (const [filePath, content] of Object.entries(changes)) {
        const absolutePath = path.join(cwd, filePath);
        await fsWriteFile(rawFs, absolutePath, content);
    }
}
async function fsWriteFile(rawFs, absolutePath, content) {
    await rawFs.mkdir(path.dirname(absolutePath), { recursive: true });
    await rawFs.writeFile(absolutePath, content, { encoding: "utf8" });
}
function normalizeAgentResult(output) {
    return {
        stdout: output.stdout,
        stderr: output.stderr ?? "",
        exitCode: output.exitCode ?? 0
    };
}
export function successTurn(assertPrompt, fileChanges) {
    return {
        ...(assertPrompt ? { assertPrompt } : {}),
        ...(fileChanges ? { fileChanges } : {}),
        output: {
            stdout: "",
            exitCode: 0
        }
    };
}
export function failTurn(stderr, assertPrompt, fileChanges) {
    return {
        ...(assertPrompt ? { assertPrompt } : {}),
        ...(fileChanges ? { fileChanges } : {}),
        output: {
            stdout: "",
            stderr,
            exitCode: 1
        }
    };
}
export function createRalphSimulation(options) {
    return {
        async run() {
            const { fs, docPath, cwd, homeDir, rawFs } = createSimulationFs(options);
            const turns = [...options.turns];
            const prompts = [];
            const runs = [];
            const readFile = async (filePath) => fs.readFile(path.join(cwd, filePath), "utf8");
            const writeFile = async (filePath, content) => fsWriteFile(rawFs, path.join(cwd, filePath), content);
            const result = await runRalph({
                agent: options.agent ?? "codex",
                cwd,
                homeDir,
                docPath,
                maxIterations: options.maxIterations,
                ...(options.signal ? { signal: options.signal } : {}),
                fs,
                runAgent: async (input) => {
                    const turn = turns.shift();
                    if (!turn) {
                        throw new Error("Ralph simulation ran out of turns.");
                    }
                    prompts.push(input.prompt);
                    runs.push(input);
                    if (turn.assertPrompt) {
                        await turn.assertPrompt(input.prompt, {
                            fs,
                            readFile,
                            writeFile
                        });
                    }
                    if (turn.fileChanges) {
                        await applyFileChanges(rawFs, cwd, turn.fileChanges);
                    }
                    return normalizeAgentResult(turn.output);
                }
            });
            return {
                result,
                prompts,
                runs,
                fs,
                readFile
            };
        }
    };
}
