"use strict";
/**
 * AtomicWriter — crash-safe persistence helpers for OpenLife state files.
 *
 * Pattern:
 *   1. Write payload to `<filePath>.tmp.<pid>.<ts>`
 *   2. fsync the temp file (flush OS buffer to disk)
 *   3. Atomically rename temp → final (single syscall on POSIX, MoveFileEx on Windows)
 *   4. On any error: best-effort cleanup of temp file
 *
 * The rename step is atomic per the OS (POSIX rename(2), NTFS MoveFile), so a
 * `kill -9` mid-operation leaves either the old file intact, or the fully new
 * one. There is no partial-write window observable by other processes.
 *
 * Concurrency: each writer uses a unique temp suffix (`pid.ts.rand`), so
 * multiple processes writing the same final path do not collide on the temp.
 * The final rename is last-writer-wins, which is acceptable for our state
 * files (the loser's data is discarded, not corrupted).
 *
 * Feature flag: `OPENLIFE_ATOMIC_WRITES=off` disables atomicity and falls
 * back to plain writeFileSync / appendFileSync. Default is 'on'.
 *
 * Story 6.1 — OpenLife v1.2 Royal Stack.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeJsonAtomic = writeJsonAtomic;
exports.writeStringAtomic = writeStringAtomic;
exports.appendJsonlAtomic = appendJsonlAtomic;
exports.appendStringAtomic = appendStringAtomic;
exports.purgeStaleTempFiles = purgeStaleTempFiles;
exports._isAtomicEnabled = _isAtomicEnabled;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const ATOMIC_FLAG_ENV = 'OPENLIFE_ATOMIC_WRITES';
function atomicEnabled() {
    const v = (process.env[ATOMIC_FLAG_ENV] || 'on').toLowerCase();
    return v !== 'off' && v !== '0' && v !== 'false';
}
function tempPath(filePath) {
    const rand = Math.random().toString(36).slice(2, 8);
    return `${filePath}.tmp.${process.pid}.${Date.now()}.${rand}`;
}
function fsyncFile(p) {
    // writeFileSync does NOT fsync — we must open + fsync + close ourselves
    // so the rename below sees a durable file on disk.
    const fd = fs.openSync(p, 'r+');
    try {
        fs.fsyncSync(fd);
    }
    finally {
        fs.closeSync(fd);
    }
}
/**
 * Write a JSON-serializable object to `filePath` atomically.
 *
 * Writes pretty-printed JSON (2-space indent) followed by a trailing newline.
 * Parent directory is created recursively if it does not exist.
 */
function writeJsonAtomic(filePath, obj) {
    const payload = `${JSON.stringify(obj, null, 2)}\n`;
    writeStringAtomic(filePath, payload);
}
/**
 * Write a raw string to `filePath` atomically. Used by writeJsonAtomic
 * internally; exported for callers that need control over serialization
 * (e.g. markdown templates).
 */
function writeStringAtomic(filePath, content) {
    const dir = path.dirname(filePath);
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
    }
    if (!atomicEnabled()) {
        fs.writeFileSync(filePath, content, 'utf-8');
        return;
    }
    const tmp = tempPath(filePath);
    try {
        fs.writeFileSync(tmp, content, 'utf-8');
        fsyncFile(tmp);
        fs.renameSync(tmp, filePath);
    }
    catch (err) {
        // Best-effort cleanup; ignore failures here, the original error is what matters
        try {
            if (fs.existsSync(tmp))
                fs.unlinkSync(tmp);
        }
        catch {
            /* swallow */
        }
        throw err;
    }
}
/**
 * Append one JSON object as a single line to a JSONL event log,
 * atomically with respect to crash.
 *
 * Implementation reads the existing file into memory, appends the new line,
 * and rewrites the whole file via the atomic temp+rename dance. This is
 * O(n) per append; suitable for event logs that stay under a few MB
 * (the typical case for `.openlife/*.jsonl` and `.artifacts/**\/events/*.jsonl`).
 *
 * For high-volume logs this becomes expensive; consider sharding or a
 * dedicated append-with-lock implementation if hot paths emerge.
 */
function appendJsonlAtomic(filePath, obj) {
    const line = `${JSON.stringify(obj)}\n`;
    if (!atomicEnabled()) {
        const dir = path.dirname(filePath);
        if (!fs.existsSync(dir))
            fs.mkdirSync(dir, { recursive: true });
        fs.appendFileSync(filePath, line, 'utf-8');
        return;
    }
    const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : '';
    // Ensure existing content ends with a newline boundary before appending,
    // so a previously truncated final line does not merge with our new entry.
    const sep = existing.length === 0 || existing.endsWith('\n') ? '' : '\n';
    writeStringAtomic(filePath, `${existing}${sep}${line}`);
}
/**
 * Append a raw string (already-terminated line, no JSON wrapping) atomically.
 * Used by callers that already control their own serialization format.
 */
function appendStringAtomic(filePath, line) {
    if (!atomicEnabled()) {
        const dir = path.dirname(filePath);
        if (!fs.existsSync(dir))
            fs.mkdirSync(dir, { recursive: true });
        fs.appendFileSync(filePath, line, 'utf-8');
        return;
    }
    const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : '';
    writeStringAtomic(filePath, `${existing}${line}`);
}
/**
 * Remove orphaned `.tmp.<pid>.<ts>.<rand>` files older than `olderThanMs`.
 * Returns the count of files removed.
 *
 * Use during boot to clean up after a previous crash that left temp files
 * lingering. Safe to call even when atomic writes are disabled — it only
 * targets the `.tmp.<pid>.*` naming convention we control.
 */
function purgeStaleTempFiles(dir, olderThanMs) {
    if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory())
        return 0;
    const cutoff = Date.now() - olderThanMs;
    let removed = 0;
    for (const entry of fs.readdirSync(dir)) {
        if (!/\.tmp\.\d+\.\d+\.[a-z0-9]+$/i.test(entry))
            continue;
        const full = path.join(dir, entry);
        try {
            const st = fs.statSync(full);
            if (st.mtimeMs < cutoff) {
                fs.unlinkSync(full);
                removed += 1;
            }
        }
        catch {
            /* race: file deleted by another process — ignore */
        }
    }
    return removed;
}
/** Internal — exposed for tests so they can assert the flag wiring. */
function _isAtomicEnabled() {
    return atomicEnabled();
}
