/**
 * Frontier Model Orchestrator
 *
 * Manages multiple frontier model providers, handles fallback,
 * and implements consensus across models for high-confidence results.
 *
 * @module frontier/orchestrator
 */
import { DEFAULT_FRONTIER_CONFIG } from "./types.js";
import { logger } from "../logger.js";
// ============================================================================
// Orchestrator
// ============================================================================
/**
 * Orchestrates frontier model analysis across multiple providers
 */
export class FrontierOrchestrator {
    providers = new Map();
    config;
    constructor(config = {}) {
        this.config = { ...DEFAULT_FRONTIER_CONFIG, ...config };
    }
    /**
     * Register a model provider
     */
    registerProvider(provider) {
        this.providers.set(provider.name, provider);
        logger.debug("frontier.provider_registered", { provider: provider.name });
    }
    /**
     * Get a registered provider
     */
    getProvider(name) {
        return this.providers.get(name);
    }
    /**
     * List all registered providers
     */
    listProviders() {
        return Array.from(this.providers.keys());
    }
    /**
     * Check availability of all providers
     */
    async checkAvailability() {
        const results = {};
        for (const [name, provider] of this.providers) {
            try {
                const status = await provider.getStatus();
                results[name] = { available: status.available, error: status.error };
            }
            catch (error) {
                results[name] = {
                    available: false,
                    error: error instanceof Error ? error.message : String(error),
                };
            }
        }
        return results;
    }
    /**
     * Run analysis with a single provider
     */
    async runWithProvider(request, providerName) {
        const provider = this.providers.get(providerName);
        if (!provider) {
            throw new Error(`Unknown provider: ${providerName}`);
        }
        const available = await provider.isAvailable();
        if (!available) {
            throw new Error(`Provider not available: ${providerName}`);
        }
        logger.info("frontier.analysis_started", {
            provider: providerName,
            files: request.files.length,
            depth: request.depth,
        });
        const startTime = Date.now();
        const result = await provider.analyze(request);
        const duration = Date.now() - startTime;
        logger.info("frontier.analysis_completed", {
            provider: providerName,
            findings: result.findings.length,
            chains: result.exploitChains.length,
            cost: result.cost.totalCost,
            duration,
        });
        return result;
    }
    /**
     * Run analysis with fallback through multiple providers
     */
    async runWithFallback(request, providerNames) {
        for (const name of providerNames) {
            const provider = this.providers.get(name);
            if (!provider) {
                logger.warn("frontier.provider_not_found", { provider: name });
                continue;
            }
            try {
                const available = await provider.isAvailable();
                if (!available) {
                    logger.warn("frontier.provider_unavailable", { provider: name });
                    continue;
                }
                return await this.runWithProvider(request, name);
            }
            catch (error) {
                logger.warn("frontier.provider_failed", {
                    provider: name,
                    error: error instanceof Error ? error.message : String(error),
                });
                continue;
            }
        }
        throw new Error("No frontier model providers available");
    }
    /**
     * Run analysis with multiple providers for consensus
     */
    async runWithConsensus(request, providerNames) {
        const results = [];
        // Run all providers in parallel
        const promises = providerNames.map(async (name) => {
            try {
                const result = await this.runWithProvider(request, name);
                return {
                    model: name,
                    findings: result.findings,
                    chains: result.exploitChains,
                    confidence: result.confidence,
                    cost: result.cost,
                };
            }
            catch (error) {
                logger.warn("frontier.consensus_provider_failed", {
                    provider: name,
                    error: error instanceof Error ? error.message : String(error),
                });
                return null;
            }
        });
        const settled = await Promise.all(promises);
        for (const result of settled) {
            if (result) {
                results.push(result);
            }
        }
        if (results.length === 0) {
            throw new Error("No providers completed successfully for consensus");
        }
        return this.calculateConsensus(results);
    }
    /**
     * Calculate consensus across model results
     */
    calculateConsensus(modelResults) {
        const consensusFindings = [];
        const disputedFindings = [];
        const consensusChains = [];
        // Group findings by a signature (file + line + category)
        const findingGroups = new Map();
        for (const result of modelResults) {
            for (const finding of result.findings) {
                const key = `${finding.file}:${finding.startLine}:${finding.category}`;
                const existing = findingGroups.get(key) || [];
                existing.push({ finding, models: [result.model] });
                findingGroups.set(key, existing);
            }
        }
        // Determine consensus threshold
        const threshold = Math.ceil(modelResults.length * this.config.consensusThreshold);
        for (const [, entries] of findingGroups) {
            // Count unique models that found this
            const modelsAgreeing = new Set(entries.flatMap((e) => e.models));
            if (modelsAgreeing.size >= threshold) {
                // Consensus reached - use finding with highest confidence
                const bestEntry = entries.reduce((a, b) => a.finding.confidence > b.finding.confidence ? a : b);
                consensusFindings.push(bestEntry.finding);
            }
            else {
                // Disputed - track which models agree/disagree
                const allModels = modelResults.map((r) => r.model);
                disputedFindings.push({
                    finding: entries[0].finding,
                    modelAgreement: Array.from(modelsAgreeing),
                    modelDisagreement: allModels.filter((m) => !modelsAgreeing.has(m)),
                });
            }
        }
        // Similar process for exploit chains
        const chainGroups = new Map();
        for (const result of modelResults) {
            for (const chain of result.chains) {
                // Key chains by their first and last step locations
                const key = chain.steps.length > 0
                    ? `${chain.steps[0].file}:${chain.steps[0].line}->${chain.steps[chain.steps.length - 1].file}:${chain.steps[chain.steps.length - 1].line}`
                    : chain.id;
                const existing = chainGroups.get(key) || [];
                existing.push({ chain, models: [result.model] });
                chainGroups.set(key, existing);
            }
        }
        for (const [, entries] of chainGroups) {
            const modelsAgreeing = new Set(entries.flatMap((e) => e.models));
            if (modelsAgreeing.size >= threshold) {
                const bestEntry = entries.reduce((a, b) => a.chain.confidence > b.chain.confidence ? a : b);
                consensusChains.push(bestEntry.chain);
            }
        }
        // Calculate consensus confidence
        const totalConsensus = consensusFindings.length + consensusChains.length;
        const totalDisputed = disputedFindings.length;
        const consensusConfidence = totalConsensus + totalDisputed > 0
            ? Math.round((totalConsensus / (totalConsensus + totalDisputed)) * 100)
            : 0;
        // Combine costs
        const totalCost = {
            inputTokens: modelResults.reduce((sum, r) => sum + r.cost.inputTokens, 0),
            outputTokens: modelResults.reduce((sum, r) => sum + r.cost.outputTokens, 0),
            totalCost: modelResults.reduce((sum, r) => sum + r.cost.totalCost, 0),
            model: modelResults.map((r) => r.model).join("+"),
            breakdown: modelResults.map((r) => ({
                phase: r.model,
                inputTokens: r.cost.inputTokens,
                outputTokens: r.cost.outputTokens,
                cost: r.cost.totalCost,
            })),
        };
        return {
            consensusFindings,
            disputedFindings,
            consensusChains,
            consensusConfidence,
            modelResults: modelResults.map((r) => ({
                model: r.model,
                findings: r.findings,
                chains: r.chains,
                confidence: r.confidence,
            })),
            totalCost,
        };
    }
    /**
     * Estimate cost for an analysis across providers
     */
    async estimateCost(request, providerNames) {
        const names = providerNames || Array.from(this.providers.keys());
        const estimates = {};
        for (const name of names) {
            const provider = this.providers.get(name);
            if (provider) {
                try {
                    estimates[name] = await provider.estimateCost(request);
                }
                catch (error) {
                    estimates[name] = {
                        min: 0,
                        max: 0,
                        estimated: 0,
                        currency: "USD",
                        factors: [
                            `Error: ${error instanceof Error ? error.message : String(error)}`,
                        ],
                    };
                }
            }
        }
        return estimates;
    }
    /**
     * Get the default provider order based on config
     */
    getDefaultProviderOrder() {
        return this.config.providers.filter((p) => this.providers.has(p));
    }
    /**
     * Run analysis with default configuration
     */
    async run(request) {
        const providers = this.getDefaultProviderOrder();
        if (providers.length === 0) {
            throw new Error("No frontier model providers configured");
        }
        if (this.config.requireConsensus && providers.length > 1) {
            return this.runWithConsensus(request, providers);
        }
        else {
            return this.runWithFallback(request, providers);
        }
    }
}
// ============================================================================
// Singleton
// ============================================================================
let _orchestrator = null;
/**
 * Get the singleton orchestrator instance
 */
export function getFrontierOrchestrator(config) {
    if (!_orchestrator) {
        _orchestrator = new FrontierOrchestrator(config);
    }
    return _orchestrator;
}
/**
 * Reset the singleton (for testing)
 */
export function resetFrontierOrchestrator() {
    _orchestrator = null;
}
//# sourceMappingURL=orchestrator.js.map