"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports._testing = void 0;
exports.installCrashHook = installCrashHook;
/**
 * Crash hook — last-resort uncaught error capture for the CLI.
 *
 * Goals:
 *   1. When the bin entry crashes (uncaught exception or unhandled
 *      rejection), write a single self-contained log file to
 *      ~/.chorus/crashes/<ISO>.log so the user has something concrete
 *      to attach to a bug report.
 *   2. Print a one-line nudge to stderr pointing at the file + the
 *      issues URL. Do NOT dump the full stack — most users panic at
 *      raw stack traces; the file is for the maintainer.
 *   3. NEVER throw from inside the hook — a hook that itself crashes
 *      is the worst failure mode (silently lost diagnostic).
 *
 * Why this lives in its own tiny module:
 *   - Must be installable BEFORE any other import in bin/chorus.mjs so
 *     it catches early-startup crashes (e.g. the Node 25 + Windows
 *     ESM URL scheme issue that motivated this work — bin's
 *     `await import(distEntry)` fails with `Received protocol 'c:'`
 *     before any CLI code runs).
 *   - Therefore can't depend on commander, ui.ts, libsql, or any
 *     compiled module that itself might fail to load.
 *
 * Plain `node:` builtins only — same reason. Module is loaded by the
 * .mjs bin in raw-ESM mode, no tsx, no transpile.
 */
const node_fs_1 = require("node:fs");
const node_os_1 = require("node:os");
const node_path_1 = require("node:path");
const ISSUE_URL = 'https://github.com/chorus-codes/chorus/issues/new';
function timestamp() {
    return new Date().toISOString().replace(/[:.]/g, '-');
}
function buildCrashLog(err, source, version) {
    const stack = err instanceof Error
        ? `${err.name}: ${err.message}\n${err.stack ?? '(no stack)'}`
        : String(err);
    return [
        '# Chorus crash report',
        '',
        `timestamp:    ${new Date().toISOString()}`,
        `source:       ${source}`,
        `chorus:       ${version}`,
        `node:         ${process.versions.node}`,
        `platform:     ${process.platform} ${process.arch}`,
        `argv:         ${process.argv.slice(1).join(' ')}`,
        `cwd:          ${process.cwd()}`,
        `uptime_ms:    ${Math.round(process.uptime() * 1000)}`,
        '',
        '## Error',
        '',
        stack,
        '',
    ].join('\n');
}
function writeCrashFile(dir, body) {
    try {
        (0, node_fs_1.mkdirSync)(dir, { recursive: true });
        const file = (0, node_path_1.join)(dir, `${timestamp()}.log`);
        (0, node_fs_1.writeFileSync)(file, body, { encoding: 'utf-8' });
        return file;
    }
    catch {
        // mkdir or write failed (read-only home, ENOSPC, ...). The hook
        // must still print SOMETHING useful to stderr — the user's
        // diagnostic value here is "chorus crashed at <stack>", not "we
        // couldn't write a file."
        return null;
    }
}
/**
 * Install crash handlers on process. Idempotent — calling twice is a
 * no-op. We track the registered listeners so `_testing.reset()` (and
 * a hypothetical re-install in production) can detach them, otherwise
 * each test that runs install spawns a new listener and the next
 * uncaughtException fires the cumulative chain N times.
 */
let installed = false;
let activeUncaught = null;
let activeUnhandled = null;
function installCrashHook(opts = {}) {
    if (installed)
        return;
    installed = true;
    const crashDir = opts.crashDir ?? (0, node_path_1.join)((0, node_os_1.homedir)(), '.chorus', 'crashes');
    const stderr = opts.stderr ?? ((msg) => process.stderr.write(msg));
    const exit = opts.exit ?? ((code) => process.exit(code));
    const version = opts.version ?? '(unknown)';
    const handle = (err, source) => {
        const body = buildCrashLog(err, source, version);
        const file = writeCrashFile(crashDir, body);
        const headline = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
        stderr('\n');
        stderr(`✗ Chorus crashed (${source}): ${headline}\n`);
        if (file) {
            stderr(`  Crash log saved to: ${file}\n`);
            stderr(`  Please attach it to a new issue: ${ISSUE_URL}\n`);
            stderr('  Or run: chorus diagnose\n');
        }
        else {
            // File write failed — give the user the stack inline as a
            // last-resort fallback so they have something to paste.
            stderr(`  (could not write crash log to ${crashDir})\n`);
            stderr(`  Please file an issue at ${ISSUE_URL} with this stack:\n`);
            stderr(body + '\n');
        }
        stderr('\n');
        exit(1);
    };
    activeUncaught = (err) => handle(err, 'uncaughtException');
    activeUnhandled = (err) => handle(err, 'unhandledRejection');
    process.on('uncaughtException', activeUncaught);
    process.on('unhandledRejection', activeUnhandled);
}
// Exported for tests.
exports._testing = {
    buildCrashLog,
    writeCrashFile,
    reset: () => {
        if (activeUncaught) {
            process.off('uncaughtException', activeUncaught);
            activeUncaught = null;
        }
        if (activeUnhandled) {
            process.off('unhandledRejection', activeUnhandled);
            activeUnhandled = null;
        }
        installed = false;
    },
};
//# sourceMappingURL=crash-hook.js.map