// engine/hooks/hook-engine.ts — Hook orchestration engine (SPEC-130)
import { EventBus } from './event-bus.js';
// ---------------------------------------------------------------------------
// HookEngine
// ---------------------------------------------------------------------------
/**
 * Orchestrates all hook handlers with plugin support, priority ordering,
 * async non-blocking execution, and telemetry.
 */
export class HookEngine {
    handlers = [];
    telemetry = new Map();
    eventBus;
    running = false;
    pendingCount = 0;
    constructor(eventBus) {
        this.eventBus = eventBus ?? new EventBus();
    }
    /**
     * Register a hook handler with optional priority.
     * Lower priority = executed first (default: 100).
     */
    register(handler) {
        const h = { ...handler, priority: handler.priority ?? 100 };
        this.handlers.push(h);
        // Re-sort by priority
        this.handlers.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
        // Initialize telemetry
        if (!this.telemetry.has(h.name)) {
            this.telemetry.set(h.name, {
                executionCount: 0,
                avgLatencyMs: 0,
                errorCount: 0,
                errorRate: 0,
                totalLatencyMs: 0,
            });
        }
    }
    /**
     * Unregister a handler by name.
     */
    unregister(name) {
        const idx = this.handlers.findIndex((h) => h.name === name);
        if (idx === -1) {
            return false;
        }
        this.handlers.splice(idx, 1);
        return true;
    }
    /**
     * Start the engine (marks as running).
     */
    start() {
        this.running = true;
    }
    /**
     * Stop the engine.
     */
    stop() {
        this.running = false;
    }
    /**
     * Dispatch an event to all matching handlers (async, non-blocking).
     * Each handler is wrapped in try/catch — errors don't propagate.
     */
    async dispatch(event, hookType) {
        if (!this.running) {
            return [];
        }
        const matching = this.handlers.filter((h) => h.hookType === hookType);
        if (matching.length === 0) {
            return [];
        }
        this.pendingCount++;
        const results = [];
        try {
            for (const handler of matching) {
                this.eventBus.emitEvent('hook:start', {
                    handler: handler.name,
                    hookType,
                    path: event.path,
                });
                const startMs = Date.now();
                try {
                    const result = await handler.execute(event);
                    results.push(result);
                    const latency = Date.now() - startMs;
                    this.updateTelemetry(handler.name, latency, false);
                    this.eventBus.emitEvent('hook:complete', {
                        handler: handler.name,
                        hookType,
                        path: event.path,
                        latencyMs: latency,
                    });
                }
                catch (err) {
                    const latency = Date.now() - startMs;
                    this.updateTelemetry(handler.name, latency, true);
                    const message = err instanceof Error ? err.message : String(err);
                    this.eventBus.emitEvent('hook:error', {
                        handler: handler.name,
                        hookType,
                        path: event.path,
                        error: message,
                    });
                }
            }
        }
        finally {
            this.pendingCount--;
        }
        return results;
    }
    /**
     * Get current engine status including telemetry.
     */
    status() {
        return {
            running: this.running,
            activeHooks: this.handlers.map((h) => ({
                name: h.name,
                hookType: h.hookType,
                /* v8 ignore next -- priority always set in register(), ?? 100 is defensive */
                priority: h.priority ?? 100,
            })),
            pendingEvents: this.pendingCount,
            telemetry: Object.fromEntries(this.telemetry),
        };
    }
    /**
     * Get the underlying event bus.
     */
    getEventBus() {
        return this.eventBus;
    }
    /**
     * Get registered handler count.
     */
    getHandlerCount() {
        return this.handlers.length;
    }
    // ---------------------------------------------------------------------------
    // Private
    // ---------------------------------------------------------------------------
    updateTelemetry(name, latencyMs, isError) {
        const t = this.telemetry.get(name);
        if (!t) {
            return;
        }
        t.executionCount++;
        t.totalLatencyMs += latencyMs;
        t.avgLatencyMs = t.totalLatencyMs / t.executionCount;
        if (isError) {
            t.errorCount++;
        }
        t.errorRate = t.errorCount / t.executionCount;
    }
}
//# sourceMappingURL=hook-engine.js.map