// engine/workers/worker-engine.ts — Orchestrator for background workers (SPEC-078 AC-02, AC-13)
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { WorkerRegistrySchema } from '../../tools/schemas/workers-schema.js';
import { runTestGaps } from './handlers/test-gaps.js';
import { runSecurityAudit } from './handlers/security-audit.js';
import { runOptimization } from './handlers/optimization.js';
import { runCodeMap } from './handlers/code-map.js';
import { runDocumentation } from './handlers/documentation.js';
import { runDependencyHealth } from './handlers/dependency-health.js';
// ---------------------------------------------------------------------------
// Handler registry
// ---------------------------------------------------------------------------
const HANDLER_MAP = {
    'handlers/test-gaps': runTestGaps,
    'handlers/security-audit': runSecurityAudit,
    'handlers/optimization': runOptimization,
    'handlers/code-map': runCodeMap,
    'handlers/documentation': runDocumentation,
    'handlers/dependency-health': runDependencyHealth,
};
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const MAX_BACKOFF_MS = 300_000; // 5 minutes
const MAX_CONSECUTIVE_FAILURES = 3;
// ---------------------------------------------------------------------------
// Worker Engine
// ---------------------------------------------------------------------------
export class WorkerEngine {
    workers = [];
    statuses = new Map();
    queue = [];
    processing = false;
    active = false;
    totalRunsCompleted = 0;
    overrides = new Map();
    async loadRegistry(registryPath) {
        /* v8 ignore next */
        const path = registryPath ?? join(import.meta.dirname, '../../config/workers-registry.json');
        const raw = await readFile(path, 'utf-8');
        const parsed = JSON.parse(raw);
        const validated = WorkerRegistrySchema.parse(parsed);
        this.workers = validated.workers;
        for (const w of this.workers) {
            if (!this.statuses.has(w.name)) {
                this.statuses.set(w.name, {
                    name: w.name,
                    enabled: w.enabled,
                    running: false,
                    consecutiveFailures: 0,
                    effectiveCooldownMs: w.cooldownMs,
                });
            }
        }
    }
    start() {
        this.active = true;
    }
    stop() {
        this.active = false;
        this.queue = [];
    }
    isActive() {
        return this.active;
    }
    getWorkerDefinitions() {
        return [...this.workers];
    }
    applyOverride(workerName, override) {
        this.overrides.set(workerName, override);
        const status = this.statuses.get(workerName);
        if (status && override.enabled !== undefined) {
            status.enabled = override.enabled;
        }
        const worker = this.workers.find((w) => w.name === workerName);
        if (worker && override.enabled !== undefined) {
            worker.enabled = override.enabled;
        }
        if (worker && override.cooldownMs !== undefined) {
            worker.cooldownMs = override.cooldownMs;
        }
        if (worker && override.priority !== undefined) {
            worker.priority = override.priority;
        }
        if (worker && override.triggerPatterns) {
            worker.trigger.patterns = override.triggerPatterns;
        }
        if (status && override.cooldownMs !== undefined) {
            status.effectiveCooldownMs = override.cooldownMs;
        }
    }
    enqueue(workerName, changedFiles) {
        if (!this.active) {
            return false;
        }
        const worker = this.workers.find((w) => w.name === workerName);
        if (!worker?.enabled) {
            return false;
        }
        const status = this.statuses.get(workerName);
        if (!status) {
            return false;
        }
        // Cooldown check
        if (status.lastRun) {
            const lastRunTime = new Date(status.lastRun.completedAt).getTime();
            const elapsed = Date.now() - lastRunTime;
            if (elapsed < status.effectiveCooldownMs) {
                return false;
            }
        }
        // Deduplication: update existing queue entry instead of adding duplicate
        const existing = this.queue.find((q) => q.workerName === workerName);
        if (existing) {
            if (changedFiles) {
                existing.changedFiles = [...new Set([...(existing.changedFiles ?? []), ...changedFiles])];
            }
            return false;
        }
        this.queue.push({ workerName, changedFiles });
        // Sort queue by priority (higher first)
        this.queue.sort((a, b) => {
            const pa = this.workers.find((w) => w.name === a.workerName)?.priority ?? 0;
            const pb = this.workers.find((w) => w.name === b.workerName)?.priority ?? 0;
            return pb - pa;
        });
        return true;
    }
    async processQueue(projectDir) {
        if (this.processing || this.queue.length === 0) {
            return [];
        }
        this.processing = true;
        const results = [];
        while (this.queue.length > 0) {
            const item = this.queue.shift();
            /* v8 ignore next 3 -- unreachable: while loop checks length > 0 */
            if (!item) {
                break;
            }
            const result = await this.executeWorker(item.workerName, projectDir, item.changedFiles);
            if (result) {
                results.push(result);
            }
        }
        this.processing = false;
        return results;
    }
    async executeWorker(workerName, projectDir, changedFiles) {
        const worker = this.workers.find((w) => w.name === workerName);
        if (!worker) {
            return undefined;
        }
        const status = this.statuses.get(workerName);
        if (!status) {
            return undefined;
        }
        const handler = HANDLER_MAP[worker.handler];
        if (!handler) {
            return {
                workerName,
                startedAt: new Date().toISOString(),
                completedAt: new Date().toISOString(),
                durationMs: 0,
                status: 'error',
                findings: [],
                error: `No handler found for "${worker.handler}"`,
                analyzedFiles: 0,
            };
        }
        status.running = true;
        try {
            const result = await handler(projectDir, changedFiles);
            status.running = false;
            status.lastRun = result;
            status.consecutiveFailures = 0;
            status.effectiveCooldownMs = worker.cooldownMs;
            this.totalRunsCompleted++;
            return result;
        }
        catch (err) {
            status.running = false;
            status.consecutiveFailures++;
            // Backoff on consecutive failures
            if (status.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
                status.effectiveCooldownMs = Math.min(status.effectiveCooldownMs * 2, MAX_BACKOFF_MS);
            }
            const errorResult = {
                workerName,
                startedAt: new Date().toISOString(),
                completedAt: new Date().toISOString(),
                durationMs: 0,
                status: 'error',
                findings: [],
                error: err instanceof Error ? err.message : String(err),
                analyzedFiles: 0,
            };
            status.lastRun = errorResult;
            this.totalRunsCompleted++;
            return errorResult;
        }
    }
    getStatus(workerName) {
        if (workerName) {
            return this.statuses.get(workerName);
        }
        return {
            active: this.active,
            workers: Array.from(this.statuses.values()),
            queueLength: this.queue.length,
            totalRunsCompleted: this.totalRunsCompleted,
        };
    }
    getQueueLength() {
        return this.queue.length;
    }
}
// ---------------------------------------------------------------------------
// Singleton instance
// ---------------------------------------------------------------------------
let engineInstance;
export function getWorkerEngine() {
    engineInstance ??= new WorkerEngine();
    return engineInstance;
}
export function resetWorkerEngine() {
    if (engineInstance) {
        engineInstance.stop();
    }
    engineInstance = undefined;
}
//# sourceMappingURL=worker-engine.js.map