/**
 * atomic-write.mjs — hardened atomic JSON + JSONL writers (R3).
 *
 * Split out from util/io.mjs so the primitive can be reasoned about in
 * isolation. `util/io.mjs` re-exports `atomicWriteJson` byte-for-byte so every
 * existing `import { atomicWriteJson } from '../util/io.mjs'` call site keeps
 * working.
 *
 * atomicWriteJson hardens the write with an explicit fsync on the tmp fd
 * before rename. Without fsync, a SIGKILL between writeFileSync's return and
 * renameSync can leave the tmp file zero-length or partially flushed; with
 * fsync, the tmp file is durable before the rename publishes it as the new
 * target.
 *
 * appendJsonl provides an O_APPEND-based append for the R10 per-day event log.
 * On POSIX, small O_APPEND writes (well under PIPE_BUF) are atomic across
 * processes — two agents appending concurrently cannot interleave bytes. On
 * Windows, we guard the append with withFileLock as a conservative fallback.
 */
import { closeSync, fsyncSync, mkdirSync, openSync, renameSync, unlinkSync, writeSync, } from 'node:fs';
import { dirname } from 'node:path';
import { withFileLock } from './fs-lock.mjs';
/**
 * Atomic JSON write: open tmp with `wx` (exclusive), write, fsync, close,
 * rename. The tmp name includes pid + high-resolution timestamp so two writes
 * from the same PID to the same target cannot collide (important for R10's
 * event log where the caller may issue successive appends in the same tick).
 *
 * Public contract: same signature as the legacy util/io.mjs version. Throws
 * on I/O errors (callers already handle via try/catch or let the CLI fail).
 */
export function atomicWriteJson(targetPath, data) {
    const tmpPath = `${targetPath}.tmp.${process.pid}.${Date.now()}.${Math.random()
        .toString(36)
        .slice(2, 8)}`;
    mkdirSync(dirname(targetPath), { recursive: true });
    const payload = JSON.stringify(data, null, 2) + '\n';
    let fd = null;
    try {
        // `wx` refuses to overwrite an existing tmp file — the uuid-ish suffix
        // makes collisions statistically impossible but we still fail loud if
        // one does occur.
        fd = openSync(tmpPath, 'wx');
        writeSync(fd, payload, 0, 'utf-8');
        fsyncSync(fd);
    }
    catch (err) {
        if (fd !== null) {
            try {
                closeSync(fd);
            }
            catch {
                /* already closed or invalid */
            }
        }
        try {
            unlinkSync(tmpPath);
        }
        catch {
            /* tmp may not exist */
        }
        throw err;
    }
    closeSync(fd);
    renameSync(tmpPath, targetPath);
}
/**
 * Atomically write a UTF-8 string to `targetPath`. Creates parent dirs as
 * needed. Same crash-safety contract as atomicWriteJson: tmp file with `wx`
 * (exclusive create), write, fsync, rename to target.
 *
 * @param {string} targetPath
 * @param {string} content
 */
export function atomicWriteText(targetPath, content) {
    mkdirSync(dirname(targetPath), { recursive: true });
    const tmpPath = `${targetPath}.tmp.${process.pid}.${Date.now()}.${Math.random()
        .toString(36)
        .slice(2, 8)}`;
    let fd = null;
    try {
        fd = openSync(tmpPath, 'wx');
        writeSync(fd, content, 0, 'utf-8');
        fsyncSync(fd);
    }
    catch (err) {
        if (fd !== null) {
            try {
                closeSync(fd);
            }
            catch {
                /* ignore */
            }
        }
        try {
            unlinkSync(tmpPath);
        }
        catch {
            /* ignore */
        }
        throw err;
    }
    closeSync(fd);
    renameSync(tmpPath, targetPath);
}
/**
 * Append a single JSON-encoded line (+ trailing `\n`) to `targetPath`.
 *
 * POSIX: open with `a` (O_APPEND) so the OS serializes the seek+write; small
 * writes under PIPE_BUF are atomic across concurrent writers, which is exactly
 * the guarantee the R10 event log needs (multiple agents writing to the same
 * day's JSONL).
 *
 * Windows: `a` is not guaranteed atomic across processes, so wrap the append
 * in withFileLock — slower but correct.
 */
export function appendJsonl(targetPath, obj) {
    mkdirSync(dirname(targetPath), { recursive: true });
    const line = `${JSON.stringify(obj)}\n`;
    if (process.platform === 'win32') {
        withFileLock(targetPath, () => {
            const fd = openSync(targetPath, 'a');
            try {
                writeSync(fd, line, null, 'utf-8');
                fsyncSync(fd);
            }
            finally {
                closeSync(fd);
            }
        });
        return;
    }
    const fd = openSync(targetPath, 'a');
    try {
        writeSync(fd, line, null, 'utf-8');
        fsyncSync(fd);
    }
    finally {
        closeSync(fd);
    }
}
//# sourceMappingURL=atomic-write.mjs.map