"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WAL = void 0;
const fs_1 = require("fs");
const path_1 = require("path");
const atomic_js_1 = require("./atomic.js");
const types_js_1 = require("./types.js");
const OUTPUT_THRESHOLD = 4096;
class WAL {
    walPath;
    runDir;
    seq;
    outputDirCreated = false;
    cachedContent = null;
    constructor(runDir) {
        this.runDir = runDir;
        this.walPath = (0, path_1.join)(runDir, 'wal.jsonl');
        (0, fs_1.mkdirSync)(runDir, { recursive: true });
        this.seq = this.loadLastSeq();
    }
    get currentSeq() {
        return this.seq;
    }
    append(partial) {
        const nextSeq = this.seq + 1;
        const event = {
            seq: nextSeq,
            ts: new Date().toISOString(),
            ...partial,
        };
        const line = JSON.stringify(event) + '\n';
        try {
            (0, fs_1.appendFileSync)(this.walPath, line, { encoding: 'utf-8', mode: 0o600 });
        }
        catch (err) {
            const e = err;
            if (e.code === 'ENOSPC') {
                throw new types_js_1.StorageError('Disk full — cannot append WAL', 'DISK_FULL', this.walPath, e);
            }
            throw e;
        }
        this.cachedContent = null; // Invalidate cache — file content changed
        this.seq = nextSeq;
        return event;
    }
    ensureOutputDir() {
        if (!this.outputDirCreated) {
            (0, fs_1.mkdirSync)((0, path_1.join)(this.runDir, 'outputs'), { recursive: true });
            this.outputDirCreated = true;
        }
    }
    readWALContent() {
        if (!(0, fs_1.existsSync)(this.walPath))
            return null;
        const content = (0, atomic_js_1.safeReadSync)(this.walPath);
        if (content instanceof types_js_1.StorageError)
            return null;
        return content;
    }
    replay(afterSeq = 0) {
        const content = this.cachedContent ?? this.readWALContent();
        this.cachedContent = null; // Free after use
        if (!content)
            return [];
        const events = [];
        for (const line of content.split('\n')) {
            if (!line.trim())
                continue;
            try {
                const event = JSON.parse(line);
                if (event.seq > afterSeq) {
                    events.push(event);
                }
            }
            catch {
                // Skip corrupt lines (spec: replay partial)
                continue;
            }
        }
        return events;
    }
    persistOutput(nodeId, output) {
        if (output.length <= OUTPUT_THRESHOLD)
            return output;
        const refPath = `outputs/${nodeId}.txt`;
        this.ensureOutputDir();
        (0, atomic_js_1.atomicWriteSync)((0, path_1.join)(this.runDir, refPath), output);
        return { __vsaf_ref: refPath };
    }
    persistStructuredOutput(nodeId, data) {
        const json = JSON.stringify(data);
        if (json.length <= OUTPUT_THRESHOLD)
            return data;
        const refPath = `outputs/${nodeId}.json`;
        this.ensureOutputDir();
        (0, atomic_js_1.atomicWriteSync)((0, path_1.join)(this.runDir, refPath), json);
        return { __vsaf_ref: refPath };
    }
    getOutputRef(nodeId, output) {
        if (output.length <= OUTPUT_THRESHOLD)
            return output;
        return { __vsaf_ref: `outputs/${nodeId}.txt` };
    }
    getStructuredOutputRef(nodeId, data) {
        const json = JSON.stringify(data);
        if (json.length <= OUTPUT_THRESHOLD)
            return data;
        return { __vsaf_ref: `outputs/${nodeId}.json` };
    }
    resolveOutput(stored) {
        if (typeof stored === 'string')
            return stored;
        const refPath = '__vsaf_ref' in stored ? stored.__vsaf_ref : stored.ref;
        const content = (0, atomic_js_1.safeReadSync)((0, path_1.join)(this.runDir, refPath));
        if (content instanceof types_js_1.StorageError) {
            console.error(`[WAL] Warning: output file missing: ${refPath}`);
            return '';
        }
        return content;
    }
    resolveStructuredOutput(stored) {
        // Check for new sentinel first
        if ('__vsaf_ref' in stored) {
            const refPath = stored.__vsaf_ref;
            const content = (0, atomic_js_1.safeReadSync)((0, path_1.join)(this.runDir, refPath));
            if (content instanceof types_js_1.StorageError) {
                console.error(`[WAL] Warning: structured output file missing: ${refPath}`);
                return {};
            }
            try {
                return JSON.parse(content);
            }
            catch {
                console.error(`[WAL] Warning: corrupted structured output: ${refPath}`);
                return {};
            }
        }
        // Legacy sentinel: { ref: string } with exactly one key
        if ('ref' in stored && typeof stored.ref === 'string' && Object.keys(stored).length === 1) {
            const refPath = stored.ref;
            if (!refPath.startsWith('outputs/')) {
                return stored;
            }
            const content = (0, atomic_js_1.safeReadSync)((0, path_1.join)(this.runDir, refPath));
            if (content instanceof types_js_1.StorageError) {
                console.error(`[WAL] Warning: structured output file missing: ${refPath}`);
                return {};
            }
            try {
                return JSON.parse(content);
            }
            catch {
                console.error(`[WAL] Warning: corrupted structured output: ${refPath}`);
                return {};
            }
        }
        return stored;
    }
    loadLastSeq() {
        const content = this.readWALContent();
        if (!content)
            return 0;
        this.cachedContent = content; // Cache for subsequent replay()
        const lines = content.trim().split('\n').filter(Boolean);
        if (lines.length === 0)
            return 0;
        // Read last valid line
        for (let i = lines.length - 1; i >= 0; i--) {
            try {
                const event = JSON.parse(lines[i]);
                return event.seq;
            }
            catch {
                continue;
            }
        }
        return 0;
    }
    static hasTerminalAfter(runDir, afterSeq) {
        const walPath = (0, path_1.join)(runDir, 'wal.jsonl');
        if (!(0, fs_1.existsSync)(walPath))
            return false;
        const content = (0, atomic_js_1.safeReadSync)(walPath);
        if (content instanceof types_js_1.StorageError)
            return false;
        for (const line of content.split('\n')) {
            if (!line.trim())
                continue;
            try {
                const event = JSON.parse(line);
                if (event.seq > afterSeq && (event.type === 'run:complete' || event.type === 'run:fail')) {
                    return true;
                }
            }
            catch {
                continue;
            }
        }
        return false;
    }
}
exports.WAL = WAL;
//# sourceMappingURL=wal.js.map