"use strict";
/**
 * EventBus -- File-based event system for inter-agent communication.
 *
 * Replaces polling with file-watching for instant handoff detection.
 * Uses chokidar-compatible patterns but falls back to fs.watchFile
 * for zero-dependency operation.
 *
 * @connexum/ai-governance
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.EventBus = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
class EventBus {
    handlers = new Map();
    eventDir;
    agentName;
    pollInterval;
    watcher = null;
    lastProcessedOffset = 0;
    eventFile;
    maxEventFileBytes;
    maxRotatedFiles;
    constructor(config) {
        this.eventDir = config.eventDir;
        this.agentName = config.agentName;
        this.pollInterval = config.pollIntervalMs ?? 2000;
        this.maxEventFileBytes = config.maxEventFileBytes ?? 50 * 1024 * 1024;
        this.maxRotatedFiles = config.maxRotatedFiles ?? 5;
        if (!fs.existsSync(this.eventDir)) {
            fs.mkdirSync(this.eventDir, { recursive: true });
        }
        this.eventFile = path.join(this.eventDir, 'events.jsonl');
    }
    /**
     * AIAG-017: Rotate events.jsonl when it exceeds maxEventFileBytes.
     * events.jsonl -> events.jsonl.1, events.jsonl.1 -> events.jsonl.2, ...
     * Files beyond maxRotatedFiles are deleted.
     */
    rotateIfNeeded() {
        if (this.maxEventFileBytes <= 0)
            return;
        if (!fs.existsSync(this.eventFile))
            return;
        let size = 0;
        try {
            size = fs.statSync(this.eventFile).size;
        }
        catch {
            return;
        }
        if (size < this.maxEventFileBytes)
            return;
        // Shift existing rotations: .N -> .N+1 (drop oldest)
        for (let i = this.maxRotatedFiles - 1; i >= 1; i--) {
            const src = `${this.eventFile}.${i}`;
            const dst = `${this.eventFile}.${i + 1}`;
            if (fs.existsSync(src)) {
                try {
                    if (i + 1 > this.maxRotatedFiles) {
                        fs.unlinkSync(src);
                    }
                    else {
                        fs.renameSync(src, dst);
                    }
                }
                catch { /* best-effort */ }
            }
        }
        // Move current -> .1
        try {
            fs.renameSync(this.eventFile, `${this.eventFile}.1`);
        }
        catch { /* best-effort */ }
        // Reset watcher offset so a concurrent reader doesn't seek past EOF.
        this.lastProcessedOffset = 0;
    }
    /**
     * Subscribe to an event type.
     */
    on(type, handler) {
        const handlers = this.handlers.get(type) || [];
        handlers.push(handler);
        this.handlers.set(type, handlers);
    }
    /**
     * Emit an event (writes to event file).
     */
    emit(type, data, target) {
        const event = {
            type,
            source: this.agentName,
            target,
            data,
            timestamp: new Date().toISOString(),
        };
        try {
            this.rotateIfNeeded();
            fs.appendFileSync(this.eventFile, JSON.stringify(event) + '\n');
        }
        catch {
            // Fire-and-forget
        }
        // Also trigger local handlers immediately
        this.dispatch(event);
    }
    /**
     * Start watching for events.
     */
    start() {
        if (this.watcher)
            return;
        // Start from current end of file to avoid replaying old events
        if (fs.existsSync(this.eventFile)) {
            try {
                this.lastProcessedOffset = fs.statSync(this.eventFile).size;
            }
            catch {
                this.lastProcessedOffset = 0;
            }
        }
        this.watcher = setInterval(() => {
            this.processNewEvents();
        }, this.pollInterval);
    }
    /**
     * Stop watching for events.
     */
    stop() {
        if (this.watcher) {
            clearInterval(this.watcher);
            this.watcher = null;
        }
    }
    /**
     * Get recent events (for dashboard/monitoring).
     */
    getRecentEvents(limit = 50) {
        if (!fs.existsSync(this.eventFile))
            return [];
        try {
            const lines = fs.readFileSync(this.eventFile, 'utf-8')
                .trim()
                .split('\n')
                .filter(Boolean);
            return lines
                .slice(-limit)
                .map((line) => {
                try {
                    return JSON.parse(line);
                }
                catch {
                    return null;
                }
            })
                .filter((e) => e !== null);
        }
        catch {
            return [];
        }
    }
    processNewEvents() {
        if (!fs.existsSync(this.eventFile))
            return;
        try {
            const stat = fs.statSync(this.eventFile);
            // No new data since last check
            if (stat.size <= this.lastProcessedOffset)
                return;
            // Read only the new bytes (offset-based, avoids re-reading entire file)
            const fd = fs.openSync(this.eventFile, 'r');
            const newBytes = Buffer.alloc(stat.size - this.lastProcessedOffset);
            fs.readSync(fd, newBytes, 0, newBytes.length, this.lastProcessedOffset);
            fs.closeSync(fd);
            const newContent = newBytes.toString('utf-8');
            const lines = newContent.trim().split('\n').filter(Boolean);
            for (const line of lines) {
                try {
                    const event = JSON.parse(line);
                    // Only process events targeted at us or broadcast
                    if (event.target && event.target !== this.agentName)
                        continue;
                    // Don't process our own events (already dispatched locally)
                    if (event.source === this.agentName)
                        continue;
                    this.dispatch(event);
                }
                catch {
                    // Skip malformed events
                }
            }
            this.lastProcessedOffset = stat.size;
        }
        catch {
            // File read error -- skip this cycle
        }
    }
    dispatch(event) {
        // Type-specific handlers
        const typeHandlers = this.handlers.get(event.type) || [];
        for (const handler of typeHandlers) {
            try {
                handler(event);
            }
            catch {
                // Handler errors are swallowed to prevent cascading failures
            }
        }
        // Wildcard handlers
        const wildcardHandlers = this.handlers.get('*') || [];
        for (const handler of wildcardHandlers) {
            try {
                handler(event);
            }
            catch {
                // Swallow
            }
        }
    }
}
exports.EventBus = EventBus;
//# sourceMappingURL=event-bus.js.map