/**
 * Unified logger — always persists to ~/.blockrun/franklin-debug.log,
 * optionally mirrors to stderr when debug mode is on.
 *
 * Why this exists: before this module, agent diagnostics were emitted with
 * `if (config.debug) console.error(...)`. That meant `franklin logs` showed
 * nothing in normal use because the events never hit the file. Now every
 * level writes to disk; stderr mirroring is the opt-in part.
 *
 * Errors during a log write are swallowed — the agent loop must never die
 * because the disk is full or the home dir is read-only.
 */
import fs from 'node:fs';
import path from 'node:path';
import { BLOCKRUN_DIR } from './config.js';
const LOG_FILE = path.join(BLOCKRUN_DIR, 'franklin-debug.log');
const ARCHIVE_FILE = path.join(BLOCKRUN_DIR, 'franklin-debug.log.1');
// Self-rotation threshold. When the live log crosses this size on a
// write, rename it to franklin-debug.log.1 (overwriting any previous
// archive) and start fresh. Non-destructive: one full archive of the
// most recent ROTATE_AT_BYTES is always retained, so users can still
// read history across the rotation. Earlier behavior (only triggered
// by `franklin logs`, sliced the file in half in-place) lost history
// outright and only ran if the user happened to invoke `franklin logs`.
const ROTATE_AT_BYTES = 10 * 1024 * 1024; // 10 MB
// Probe every N writes to amortize the stat() — average debug entry is
// ~80 bytes, so 1000 writes (~80 KB worth) between checks keeps the
// overhead negligible while still catching a runaway log within seconds.
const ROTATE_PROBE_EVERY_N_WRITES = 1000;
let writesSinceRotateProbe = 0;
// Strip ANSI escapes + carriage returns so the log stays grep-able.
const ANSI_RE = /\x1b\[[0-9;]*m|\x1b\][^\x07]*\x07|\r/g;
let debugMode = false;
let dirEnsured = false;
export function setDebugMode(enabled) {
    debugMode = enabled;
}
export function isDebugMode() {
    return debugMode;
}
export function getLogFilePath() {
    return LOG_FILE;
}
function ensureDir() {
    if (dirEnsured)
        return;
    try {
        fs.mkdirSync(BLOCKRUN_DIR, { recursive: true });
        dirEnsured = true;
    }
    catch { /* readonly mount / disk full — keep trying so a remount recovers */ }
}
function maybeRotate() {
    try {
        if (!fs.existsSync(LOG_FILE))
            return;
        const { size } = fs.statSync(LOG_FILE);
        if (size < ROTATE_AT_BYTES)
            return;
        // renameSync overwrites an existing target on POSIX, which is what
        // we want — single archive, always the most recent rotation. On
        // Windows the rename can EEXIST; in that case unlink the archive
        // first and retry. If even that fails, fall through silently rather
        // than leaving the log file in a half-rotated state.
        try {
            fs.renameSync(LOG_FILE, ARCHIVE_FILE);
        }
        catch {
            try {
                fs.unlinkSync(ARCHIVE_FILE);
            }
            catch { /* may not exist */ }
            try {
                fs.renameSync(LOG_FILE, ARCHIVE_FILE);
            }
            catch { /* give up */ }
        }
    }
    catch { /* best effort */ }
}
function writeFile(level, msg) {
    ensureDir();
    try {
        writesSinceRotateProbe++;
        if (writesSinceRotateProbe >= ROTATE_PROBE_EVERY_N_WRITES) {
            writesSinceRotateProbe = 0;
            maybeRotate();
        }
        // Two-step sanitize, in this order:
        //   1. Collapse embedded newlines (\n / \r / \r\n) to a literal
        //      " ↵ " marker so a single logger call always produces one
        //      physical log line.
        //   2. Strip ANSI escape sequences.
        //
        // Order matters: ANSI_RE strips bare \r (used by progress bars), so
        // running it first would erase \r-only line breaks and let
        // "first\rsecond" appear as "firstsecond" in the log. Verified
        // 2026-05-12 from franklin-debug.log: a `Slow tool: Bash ok ...
        // python3 -c "` preview leaked `import subprocess` onto its own
        // untimestamped line because the embedded \n in the bash command
        // survived the preview slice and broke any parser that splits on
        // ^\[timestamp\]. Cheaper to enforce one-line-per-entry here than
        // to police every callsite.
        const clean = msg.replace(/\r\n|\r|\n/g, ' ↵ ').replace(ANSI_RE, '');
        fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] [${level.toUpperCase()}] ${clean}\n`);
    }
    catch { /* best-effort — never break the agent on log failure */ }
}
function writeStderr(msg) {
    try {
        process.stderr.write(msg + '\n');
    }
    catch { /* swallow */ }
}
export const logger = {
    debug(msg) {
        writeFile('debug', msg);
        if (debugMode)
            writeStderr(msg);
    },
    info(msg) {
        writeFile('info', msg);
        if (debugMode)
            writeStderr(msg);
    },
    warn(msg) {
        writeFile('warn', msg);
        if (debugMode)
            writeStderr(msg);
    },
    error(msg) {
        writeFile('error', msg);
        if (debugMode)
            writeStderr(msg);
    },
};
