import fs from "node:fs";
import { randomBytes } from "node:crypto";
// Cross-session log retention: the log is appended across ezcoder launches so
// you can grep back through prior sessions. Rotated at MAX_BYTES to keep it
// bounded; we keep one generation (debug.log.1) — that's enough to survive
// one rotation's worth of scrollback while bounding disk usage.
const MAX_BYTES = 10 * 1024 * 1024; // 10 MB
let fd = null;
let sessionId = "";
let unsubscribers = [];
function rotateIfNeeded(filePath) {
    try {
        const st = fs.statSync(filePath);
        if (st.size < MAX_BYTES)
            return;
        const rotated = `${filePath}.1`;
        // Replace prior rotation (fs.renameSync overwrites on POSIX; on Windows
        // it fails if dest exists, so unlink first defensively).
        try {
            fs.unlinkSync(rotated);
        }
        catch {
            // No prior rotation
        }
        fs.renameSync(filePath, rotated);
    }
    catch {
        // Log file doesn't exist yet or stat failed — nothing to rotate
    }
}
/**
 * Initialize the debug logger. Opens the log file in append mode so the
 * previous session's lines are preserved (rotated at MAX_BYTES). Generates a
 * session ID tagged onto every log line so concurrent sessions or back-scroll
 * across sessions can be filtered by `grep "sid=<id>"`. No-op if already
 * initialized.
 */
export function initLogger(filePath, meta) {
    if (fd !== null)
        return;
    rotateIfNeeded(filePath);
    try {
        fd = fs.openSync(filePath, "a");
    }
    catch {
        // Can't open log file — silently disable logging
        return;
    }
    sessionId = randomBytes(4).toString("hex");
    // Visible separator between sessions when back-reading the log.
    try {
        fs.writeSync(fd, "\n");
    }
    catch {
        // Write failed — proceed without the separator
    }
    const parts = ["ezcoder"];
    if (meta?.version)
        parts[0] += ` v${meta.version}`;
    parts.push("started");
    if (meta?.provider)
        parts.push(`provider=${meta.provider}`);
    if (meta?.model)
        parts.push(`model=${meta.model}`);
    if (meta?.thinking)
        parts.push(`thinking=${meta.thinking}`);
    parts.push(`pid=${process.pid}`);
    log("INFO", "startup", parts.join(" "));
}
/** Session identifier included on every log line as `sid=<id>`. */
export function getSessionId() {
    return sessionId;
}
/**
 * Write a timestamped log line. No-op if logger is not initialized.
 */
export function log(level, category, message, data) {
    if (fd === null)
        return;
    const ts = new Date().toISOString();
    let line = `[${ts}] [sid=${sessionId}] [${level}] [${category}] ${message}`;
    if (data) {
        const pairs = Object.entries(data)
            .map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`)
            .join(" ");
        line += ` ${pairs}`;
    }
    line += "\n";
    try {
        fs.writeSync(fd, line);
    }
    catch {
        // Write failed — don't crash
    }
}
/**
 * Subscribe to EventBus events and log them. Used by print/json modes.
 */
export function attachToEventBus(bus) {
    const unsubs = [];
    unsubs.push(bus.on("tool_call_start", ({ toolCallId, name }) => {
        log("INFO", "tool", `Tool call started: ${name}`, { id: toolCallId });
    }));
    unsubs.push(bus.on("tool_call_end", ({ toolCallId, result: _result, isError, durationMs }) => {
        const level = isError ? "ERROR" : "INFO";
        log(level, "tool", `Tool call ended`, {
            id: toolCallId,
            duration: `${durationMs}ms`,
            isError: String(isError),
        });
    }));
    unsubs.push(bus.on("turn_end", ({ turn, stopReason, usage }) => {
        log("INFO", "turn", `Turn ${turn} ended`, {
            stopReason,
            inputTokens: String(usage.inputTokens),
            outputTokens: String(usage.outputTokens),
            ...(usage.cacheRead != null && { cacheRead: String(usage.cacheRead) }),
            ...(usage.cacheWrite != null && { cacheWrite: String(usage.cacheWrite) }),
        });
    }));
    unsubs.push(bus.on("agent_done", ({ totalTurns, totalUsage }) => {
        log("INFO", "agent", `Agent done`, {
            totalTurns: String(totalTurns),
            inputTokens: String(totalUsage.inputTokens),
            outputTokens: String(totalUsage.outputTokens),
            ...(totalUsage.cacheRead != null && { cacheRead: String(totalUsage.cacheRead) }),
            ...(totalUsage.cacheWrite != null && { cacheWrite: String(totalUsage.cacheWrite) }),
        });
    }));
    unsubs.push(bus.on("error", ({ error }) => {
        log("ERROR", "error", error.message);
    }));
    unsubs.push(bus.on("session_start", ({ sessionId }) => {
        log("INFO", "session", `Session started`, { sessionId });
    }));
    unsubs.push(bus.on("model_change", ({ provider, model }) => {
        log("INFO", "model", `Model changed`, { provider, model });
    }));
    unsubs.push(bus.on("compaction_start", ({ messageCount }) => {
        log("INFO", "compaction", `Compaction started`, { messageCount: String(messageCount) });
    }));
    unsubs.push(bus.on("compaction_end", ({ originalCount, newCount }) => {
        log("INFO", "compaction", `Compaction ended`, {
            originalCount: String(originalCount),
            newCount: String(newCount),
        });
    }));
    unsubs.push(bus.on("user_input", ({ content }) => {
        const truncated = content.length > 100 ? content.slice(0, 100) + "..." : content;
        log("INFO", "input", `User input: ${truncated}`);
    }));
    unsubs.push(bus.on("slash_command", ({ name, args }) => {
        log("INFO", "command", `Slash command: /${name}${args ? ` ${args}` : ""}`);
    }));
    unsubscribers.push(...unsubs);
}
/**
 * Write a shutdown line, close the file descriptor, and clean up subscriptions.
 */
export function closeLogger() {
    if (fd === null)
        return;
    log("INFO", "shutdown", "ezcoder shutting down");
    try {
        fs.closeSync(fd);
    }
    catch {
        // Ignore close errors
    }
    fd = null;
    for (const unsub of unsubscribers) {
        unsub();
    }
    unsubscribers = [];
}
//# sourceMappingURL=logger.js.map