/**
 * registry.ts — Invoker registry with availability cache.
 *
 * Maps specialist.preferredRuntime (or auto-detected defaults) to the right
 * AgentInvoker, and caches availability checks so we don't re-exec binaries
 * on every dispatch.
 */
import { ClaudeCodeInvoker } from './claude-code.js';
import { CodexInvoker } from './codex.js';
import { AiderInvoker } from './aider.js';
import { CursorInvoker } from './cursor.js';
const CACHE_TTL_MS = 5 * 60 * 1_000; // 5 minutes
class InvokerRegistry {
    invokers = new Map();
    cache = new Map();
    constructor() {
        // Registration order = fallback preference order
        this.register(new ClaudeCodeInvoker());
        this.register(new CodexInvoker());
        this.register(new AiderInvoker());
        // Cursor: falls through to pull mode if cursor-agent not in PATH
        this.register(new CursorInvoker());
    }
    register(invoker) {
        this.invokers.set(invoker.id, invoker);
    }
    /** Returns all registered invokers in preference order. */
    all() {
        return [...this.invokers.values()];
    }
    /** Returns the invoker for the given ID, or undefined. */
    get(id) {
        return this.invokers.get(id);
    }
    /**
     * Returns the first available invoker in preference order.
     * Respects an optional preferred ID (e.g. from specialist.preferredRuntime).
     */
    async resolve(preferredId) {
        // Try preferred first
        if (preferredId) {
            const preferred = this.invokers.get(preferredId);
            if (preferred && await this.checkAvailability(preferred))
                return preferred;
        }
        // Fallback: first available in registration order
        for (const invoker of this.invokers.values()) {
            if (await this.checkAvailability(invoker))
                return invoker;
        }
        return null;
    }
    /** Returns cached (or fresh) availability status for all invokers. */
    async listAvailability() {
        const results = [];
        for (const invoker of this.invokers.values()) {
            const available = await this.checkAvailability(invoker);
            results.push(this.cache.get(invoker.id) ?? {
                id: invoker.id, label: invoker.label, available, version: null, checkedAt: Date.now(),
            });
        }
        return results;
    }
    /** Runs availability checks on all invokers in parallel (non-blocking startup sweep). */
    warmCache() {
        Promise.allSettled([...this.invokers.values()].map(inv => this.checkAvailability(inv))).catch(() => { });
    }
    async checkAvailability(invoker) {
        const cached = this.cache.get(invoker.id);
        if (cached && Date.now() - cached.checkedAt < CACHE_TTL_MS) {
            return cached.available;
        }
        const available = await invoker.isAvailable().catch(() => false);
        const version = available ? await invoker.getVersion().catch(() => null) : null;
        this.cache.set(invoker.id, { id: invoker.id, label: invoker.label, available, version, checkedAt: Date.now() });
        return available;
    }
}
export const invokerRegistry = new InvokerRegistry();
