import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
import path from "node:path";
import { patchtrailAppDataDir } from "../config/appDataPath.js";
import { isRecord } from "../utils/json.js";
export class RuntimeSmokeResultParseError extends Error {
    constructor(message) {
        super(message);
        this.name = "RuntimeSmokeResultParseError";
    }
}
const MAX_SMOKE_RESULT_BYTES = 64 * 1024;
export class RuntimeSmokeResultStore {
    resultPath;
    constructor(resultPath) {
        this.resultPath = resultPath;
    }
    static fromEnv(env = process.env) {
        return new RuntimeSmokeResultStore(defaultSmokeResultPath(env));
    }
    async readLast() {
        let fileStat;
        try {
            fileStat = await stat(this.resultPath);
        }
        catch (error) {
            if (isNodeError(error) && error.code === "ENOENT")
                return undefined;
            throw error;
        }
        if (fileStat.size > MAX_SMOKE_RESULT_BYTES) {
            throw new RuntimeSmokeResultParseError("Runtime smoke result exceeds the 64KB limit.");
        }
        let raw;
        try {
            raw = await readFile(this.resultPath, "utf8");
        }
        catch (error) {
            if (isNodeError(error) && error.code === "ENOENT")
                return undefined;
            throw error;
        }
        let parsed;
        try {
            parsed = JSON.parse(raw);
        }
        catch (error) {
            throw new RuntimeSmokeResultParseError("Runtime smoke result is not valid JSON.");
        }
        if (!isRuntimeSmokeResult(parsed)) {
            throw new RuntimeSmokeResultParseError("Runtime smoke result has an invalid shape.");
        }
        return parsed;
    }
    async writeLast(result) {
        if (!isRuntimeSmokeResult(result)) {
            throw new RuntimeSmokeResultParseError("Runtime smoke result has an invalid shape.");
        }
        await mkdir(path.dirname(this.resultPath), {
            recursive: true,
            mode: 0o700,
        });
        const tempPath = `${this.resultPath}.${process.pid}.${Date.now()}.tmp`;
        await writeFile(tempPath, `${JSON.stringify(result, null, 2)}\n`, {
            mode: 0o600,
        });
        await rename(tempPath, this.resultPath);
    }
}
export function defaultSmokeResultPath(env) {
    if (env.PATCHTRAIL_OPENCODE_SMOKE_RESULT_PATH) {
        return path.resolve(env.PATCHTRAIL_OPENCODE_SMOKE_RESULT_PATH);
    }
    return path.join(patchtrailAppDataDir(env), "runtime", "opencode-smoke-last.json");
}
function isRuntimeSmokeResult(value) {
    return (isRecord(value) &&
        (value.status === "passed" || value.status === "failed") &&
        typeof value.ranAt === "string" &&
        typeof value.summary === "string" &&
        typeof value.allowExternal === "boolean" &&
        optionalNumber(value.durationMs) &&
        optionalString(value.opencodeVersion) &&
        optionalString(value.platform) &&
        optionalString(value.error));
}
function optionalString(value) {
    return value === undefined || typeof value === "string";
}
function optionalNumber(value) {
    return value === undefined || typeof value === "number";
}
function isNodeError(error) {
    return error instanceof Error && "code" in error;
}
