import path from "node:path";
import { Volume, createFsFromVolume } from "memfs";
import { resolveWorkflowPath } from "@poe-code/agent-harness-tools";
import { isMap, parseDocument } from "yaml";
import { parseSuperintendentDoc, superintendentDocumentSchemaId } from "../document/parse.js";
import { runLoop } from "../runtime/loop.js";
function createSimulationFs(options) {
    const cwd = "/repo";
    const homeDir = "/home/test";
    const docPath = options.docPath ?? "docs/plans/plan.md";
    const absoluteDocPath = resolveWorkflowPath(docPath, cwd, homeDir);
    const docContent = options.maxRounds === undefined
        ? options.docContent
        : writeMaxRounds(absoluteDocPath, options.docContent, options.maxRounds);
    const files = {
        [absoluteDocPath]: docContent,
        ...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),
        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() };
        },
        unlink: async (filePath) => {
            await rawFs.unlink(filePath);
        },
        mkdir: async (filePath, mkdirOptions) => {
            await rawFs.mkdir(filePath, mkdirOptions);
        },
        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,
        absoluteDocPath,
        cwd,
        homeDir,
        rawFs
    };
}
async function applyFileChanges(rawFs, cwd, changes) {
    for (const [filePath, content] of Object.entries(changes)) {
        await fsWriteFile(rawFs, path.join(cwd, filePath), 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) {
    const workflowToolCalls = output.toolCalls ?? readWorkflowToolCallsFromText(output.stdout);
    return {
        stdout: output.stdout,
        stderr: output.stderr ?? "",
        exitCode: output.exitCode ?? 0,
        ...(output.summary ? { summary: output.summary } : {}),
        ...(output.log ? { log: output.log } : {}),
        ...(output.output ? { output: output.output } : {}),
        ...(output.text ? { text: output.text } : {}),
        ...(output.transition !== undefined ? { transition: output.transition } : {}),
        ...(workflowToolCalls !== undefined ? { toolCalls: workflowToolCalls } : {}),
        ...(output.sessionResult !== undefined ? { sessionResult: output.sessionResult } : {})
    };
}
export function successTurn(assertPrompt, fileChanges) {
    return createSuccessfulTurn({
        assertPrompt,
        fileChanges,
        stdout: ""
    });
}
export function failTurn(stderr, assertPrompt, fileChanges) {
    return {
        ...(assertPrompt ? { assertPrompt } : {}),
        ...(fileChanges ? { fileChanges } : {}),
        output: {
            stdout: "",
            stderr,
            exitCode: 1
        }
    };
}
export function builderTurn(fileChanges, assertPrompt) {
    return createSuccessfulTurn({
        assertPrompt,
        fileChanges,
        stdout: ""
    });
}
export function inspectorTurn(summary, assertPrompt) {
    return createSuccessfulTurn({
        assertPrompt,
        stdout: summary
    });
}
export function superintendentTurn(transition, fileChanges, assertPrompt) {
    return createSuccessfulTurn({
        assertPrompt,
        fileChanges,
        stdout: transition ? formatWorkflowTransition(transition) : ""
    });
}
export function ownerApproveTurn(assertPrompt) {
    return createSuccessfulTurn({
        assertPrompt,
        stdout: formatWorkflowTransition({ action: "approve_completion" })
    });
}
export function ownerRejectTurn(feedback, assertPrompt) {
    return createSuccessfulTurn({
        assertPrompt,
        stdout: formatWorkflowTransition({ action: "request_changes", feedback })
    });
}
export function createSuperintendentSimulation(options) {
    return {
        async run() {
            const { fs, docPath, absoluteDocPath, 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 readDoc = async () => parseSuperintendentDoc(absoluteDocPath, await fs.readFile(absoluteDocPath, "utf8"));
            const failureContext = {
                prompts,
                runs,
                fs,
                readFile,
                readDoc
            };
            try {
                const result = await runLoop({
                    docPath,
                    cwd,
                    homeDir,
                    ...(options.signal ? { signal: options.signal } : {}),
                    fs,
                    runAgent: async (input) => {
                        const turn = turns.shift();
                        if (!turn) {
                            throw new Error("Superintendent simulation ran out of turns.");
                        }
                        prompts.push(input.prompt);
                        runs.push(input);
                        if (turn.assertPrompt) {
                            await turn.assertPrompt(input.prompt, {
                                fs,
                                readFile,
                                writeFile,
                                readDoc
                            });
                        }
                        if (turn.fileChanges) {
                            await applyFileChanges(rawFs, cwd, turn.fileChanges);
                        }
                        return normalizeAgentResult(turn.output);
                    }
                });
                return {
                    result,
                    ...failureContext
                };
            }
            catch (error) {
                throw attachSimulationContext(error, failureContext);
            }
        }
    };
}
function createSuccessfulTurn(options) {
    return {
        ...(options.assertPrompt ? { assertPrompt: options.assertPrompt } : {}),
        ...(options.fileChanges ? { fileChanges: options.fileChanges } : {}),
        output: {
            stdout: options.stdout,
            exitCode: 0
        }
    };
}
function formatWorkflowTransition(transition) {
    return "workflow_transition(" + JSON.stringify(transition) + ")";
}
function readWorkflowToolCallsFromText(stdout) {
    for (const line of splitLines(stdout)) {
        const payload = readWorkflowTransitionTextPayload(line.trim());
        if (payload === undefined) {
            continue;
        }
        return [
            {
                name: "workflow_transition",
                arguments: payload
            }
        ];
    }
    return undefined;
}
function readWorkflowTransitionTextPayload(line) {
    const prefix = "workflow_transition(";
    if (line.startsWith(prefix) && line.endsWith(")")) {
        return line.slice(prefix.length, -1).trim();
    }
    return undefined;
}
function attachSimulationContext(error, context) {
    const normalizedError = error instanceof Error ? error : new Error(String(error));
    return Object.assign(normalizedError, context);
}
function splitLines(value) {
    return value.split("\n").map((line) => (line.endsWith("\r") ? line.slice(0, -1) : line));
}
function writeMaxRounds(filePath, content, maxRounds) {
    const parts = splitDocument(filePath, content);
    const frontmatterDocument = parseDocument(parts.frontmatterText);
    if (frontmatterDocument.errors.length > 0) {
        throw new Error(filePath + ": invalid YAML frontmatter: " + frontmatterDocument.errors[0].message);
    }
    frontmatterDocument.set("max_rounds", maxRounds);
    canonicalizeFrontmatter(frontmatterDocument);
    return [
        parts.bom,
        "---",
        parts.lineBreak,
        formatFrontmatter(frontmatterDocument.toString(), parts.lineBreak),
        parts.frontmatterSuffix,
        parts.body
    ].join("");
}
function getTopLevelMap(frontmatterDocument) {
    if (!frontmatterDocument.contents || !isMap(frontmatterDocument.contents)) {
        throw new Error("Expected superintendent frontmatter to be a top-level object.");
    }
    return frontmatterDocument.contents;
}
function reorderTopLevelKeys(map, keys) {
    const remaining = [...map.items];
    const ordered = keys.flatMap((key) => {
        const index = remaining.findIndex((item) => item.key?.toString() === key);
        return index === -1 ? [] : remaining.splice(index, 1);
    });
    map.items = [...ordered, ...remaining];
}
function canonicalizeFrontmatter(frontmatterDocument) {
    const map = getTopLevelMap(frontmatterDocument);
    map.delete("maxExperiments");
    map.delete("metricTimeout");
    map.delete("planPath");
    map.set("$schema", superintendentDocumentSchemaId);
    map.set("kind", "superintendent");
    map.set("version", 1);
    reorderTopLevelKeys(map, ["$schema", "kind", "version"]);
}
function splitDocument(filePath, content) {
    const bom = content.startsWith("\uFEFF") ? "\uFEFF" : "";
    const normalizedContent = bom ? content.slice(1) : content;
    const lineBreak = readOpeningLineBreak(normalizedContent);
    if (lineBreak === undefined) {
        throw new Error(filePath + ": expected YAML frontmatter delimited by ---");
    }
    const frontmatterStart = 3 + lineBreak.length;
    const closingFenceIndex = findClosingFence(normalizedContent, frontmatterStart, filePath);
    const frontmatterEnd = readFrontmatterEnd(normalizedContent, closingFenceIndex);
    const bodyStart = readBodyStart(normalizedContent, closingFenceIndex + 4);
    return {
        bom,
        lineBreak,
        frontmatterText: normalizedContent.slice(frontmatterStart, frontmatterEnd),
        frontmatterSuffix: normalizedContent.slice(frontmatterEnd, bodyStart),
        body: normalizedContent.slice(bodyStart)
    };
}
function readOpeningLineBreak(content) {
    if (!content.startsWith("---")) {
        return undefined;
    }
    const nextCharacter = content[3];
    if (nextCharacter === "\n") {
        return "\n";
    }
    if (nextCharacter === "\r" && content[4] === "\n") {
        return "\r\n";
    }
    return nextCharacter === undefined ? "\n" : undefined;
}
function findClosingFence(content, searchFrom, filePath) {
    let currentIndex = searchFrom - 1;
    while (currentIndex < content.length) {
        const candidateIndex = content.indexOf("\n---", currentIndex);
        if (candidateIndex === -1) {
            throw new Error(filePath + ": missing YAML frontmatter end delimiter (---)");
        }
        const fenceEnd = candidateIndex + 4;
        const nextCharacter = content[fenceEnd];
        if (nextCharacter === "\n" || nextCharacter === undefined) {
            return candidateIndex;
        }
        if (nextCharacter === "\r" && content[fenceEnd + 1] === "\n") {
            return candidateIndex;
        }
        currentIndex = fenceEnd;
    }
    throw new Error(filePath + ": missing YAML frontmatter end delimiter (---)");
}
function readBodyStart(content, bodyStart) {
    const nextCharacter = content[bodyStart];
    if (nextCharacter === "\n") {
        return bodyStart + 1;
    }
    if (nextCharacter === "\r" && content[bodyStart + 1] === "\n") {
        return bodyStart + 2;
    }
    return bodyStart;
}
function readFrontmatterEnd(content, closingFenceIndex) {
    return content[closingFenceIndex - 1] === "\r" ? closingFenceIndex - 1 : closingFenceIndex;
}
function formatFrontmatter(serialized, lineBreak) {
    const normalized = serialized.endsWith("\n") ? serialized.slice(0, -1) : serialized;
    if (lineBreak === "\n") {
        return normalized;
    }
    return normalized.replaceAll("\n", lineBreak);
}
