/**
 * SQLite corruption detector + auto-repair.
 *
 * Called from the daemon server before nexus.start() so any corrupt db is
 * backed up and removed before the engines try to open it (they recreate the
 * schema on open, giving a clean fresh db instead of a crash).
 *
 * Sync I/O is intentional here — this runs once at daemon startup on the
 * non-hot path, and better-sqlite3 is itself synchronous.
 */
import * as fs from 'fs';
import * as path from 'path';
import Database from 'better-sqlite3';
import { nexusEventBus } from '../event-bus.js';
/**
 * Check a single SQLite database for corruption.
 * Returns `true` if the db is healthy, `false` if corrupt (backed up + deleted).
 * Returns `null` if the db could not be opened (treated as potentially corrupt,
 * backed up + deleted as well).
 */
function checkOne(dbPath) {
    let db = null;
    const stamp = new Date().toISOString().replace(/[:.]/g, '-');
    const backupPath = `${dbPath}.corrupt-${stamp}.bak`;
    try {
        db = new Database(dbPath, { readonly: true });
        const rows = db.pragma('integrity_check');
        db.close();
        db = null;
        const allOk = rows.length === 1 && rows[0].integrity_check === 'ok';
        if (allOk)
            return 'healthy';
        // Corrupt — back up and remove so the engine can recreate it.
        const reasons = rows.map(r => r.integrity_check).join('; ');
        console.error(`[nexus-integrity] ${path.basename(dbPath)}: corrupt — ${reasons}`);
        _backup(dbPath, backupPath);
        return 'repaired';
    }
    catch (err) {
        if (db) {
            try {
                db.close();
            }
            catch { /* ignore */ }
        }
        const msg = err instanceof Error ? err.message : String(err);
        console.error(`[nexus-integrity] ${path.basename(dbPath)}: could not open — ${msg}`);
        _backup(dbPath, backupPath);
        return 'failed';
    }
}
function _backup(dbPath, backupPath) {
    const shmPath = `${dbPath}-shm`;
    const walPath = `${dbPath}-wal`;
    try {
        fs.copyFileSync(dbPath, backupPath);
    }
    catch { /* backup failed — still remove */ }
    try {
        fs.unlinkSync(dbPath);
    }
    catch { /* ignore */ }
    try {
        fs.unlinkSync(shmPath);
    }
    catch { /* ignore — may not exist */ }
    try {
        fs.unlinkSync(walPath);
    }
    catch { /* ignore — may not exist */ }
    console.error(`[nexus-integrity] backup saved → ${backupPath}`);
}
/**
 * Run integrity checks on every *.db file found directly inside stateRoot.
 * Emits `daemon.self-heal.db-corrupt` for each file that was repaired or failed.
 */
export function runIntegrityChecks(stateRoot) {
    const result = { repaired: [], healthy: [], failed: [] };
    let files;
    try {
        files = fs.readdirSync(stateRoot).filter(f => f.endsWith('.db'));
    }
    catch {
        // stateRoot doesn't exist yet — nothing to check
        return result;
    }
    for (const file of files) {
        const dbPath = path.join(stateRoot, file);
        const outcome = checkOne(dbPath);
        if (outcome === 'healthy') {
            result.healthy.push(dbPath);
        }
        else {
            if (outcome === 'repaired')
                result.repaired.push(dbPath);
            else
                result.failed.push(dbPath);
            nexusEventBus.emit('daemon.self-heal.db-corrupt', {
                db: file,
                dbPath,
                outcome,
                at: Date.now(),
            });
        }
    }
    if (result.repaired.length + result.failed.length > 0) {
        console.error(`[nexus-integrity] self-heal complete: ${result.repaired.length} repaired, ` +
            `${result.failed.length} failed-to-open, ${result.healthy.length} healthy`);
    }
    return result;
}
