/**
 * Smart Model Router
 *
 * OpenClaw-inspired unified routing layer that integrates:
 * - Model tier selection (complexity-based)
 * - Provider fallback (health-based circuit breaker)
 * - Cost optimization (budget tracking)
 * - Auto-promotion and recovery
 *
 * Usage:
 * ```typescript
 * const router = getSmartRouter();
 *
 * // Configure providers and models
 * router.configureChain({
 *   providers: ['grok', 'openai', 'anthropic'],
 *   models: {
 *     grok: ['grok-3-mini', 'grok-3', 'grok-3-reasoning'],
 *     openai: ['gpt-4o-mini', 'gpt-4o'],
 *     anthropic: ['claude-3-haiku', 'claude-3-sonnet']
 *   }
 * });
 *
 * // Route a request
 * const route = await router.route({
 *   task: 'complex reasoning task',
 *   requiresVision: false,
 *   preferredProvider: 'grok'
 * });
 * ```
 */
import { EventEmitter } from 'events';
import { getFallbackChain, } from './fallback-chain.js';
import { ModelRouter, classifyTaskComplexity, } from '../optimization/model-routing.js';
// ============================================================================
// Default Configuration
// ============================================================================
const DEFAULT_CONFIG = {
    providers: ['grok'],
    models: {
        grok: ['grok-3-mini', 'grok-3', 'grok-3-reasoning'],
    },
    sessionBudget: 10,
    autoDowngrade: true,
    minTierConfidence: 0.7,
};
// Model tiers mapping
const TIER_ORDER = ['mini', 'standard', 'reasoning', 'vision'];
// ============================================================================
// SmartModelRouter Class
// ============================================================================
export class SmartModelRouter extends EventEmitter {
    config;
    fallbackChain;
    modelRouter;
    currentCost = 0;
    routeHistory = [];
    constructor(config = {}) {
        super();
        this.config = { ...DEFAULT_CONFIG, ...config };
        this.fallbackChain = getFallbackChain();
        this.modelRouter = new ModelRouter();
        // Initialize fallback chain with providers
        if (this.config.providers.length > 0) {
            this.fallbackChain.setFallbackChain(this.config.providers);
        }
        // Configure fallback chain settings
        if (this.config.fallbackConfig) {
            this.fallbackChain.updateConfig(this.config.fallbackConfig);
        }
    }
    // ==========================================================================
    // Configuration
    // ==========================================================================
    /**
     * Configure the routing chain
     */
    configureChain(config) {
        this.config = { ...this.config, ...config };
        if (config.providers && config.providers.length > 0) {
            this.fallbackChain.setFallbackChain(config.providers);
        }
        if (config.fallbackConfig) {
            this.fallbackChain.updateConfig(config.fallbackConfig);
        }
    }
    /**
     * Get current configuration
     */
    getConfig() {
        return { ...this.config };
    }
    // ==========================================================================
    // Routing
    // ==========================================================================
    /**
     * Route a request to the best provider/model combination
     */
    async route(request) {
        // Handle forced model
        if (request.forceModel) {
            return this.routeToForced(request);
        }
        // Classify the task
        const classification = classifyTaskComplexity(request.task);
        // Determine target tier
        let targetTier = request.forceTier || this.getTierForClassification(classification);
        // Auto-downgrade if budget pressure
        if (this.config.autoDowngrade && this.shouldDowngrade()) {
            const lowerTier = this.getLowerTier(targetTier);
            if (lowerTier) {
                this.emit('tier:downgraded', targetTier, lowerTier, 'budget_pressure');
                targetTier = lowerTier;
            }
        }
        // Get healthy provider
        const preferredProvider = request.preferredProvider;
        let provider = preferredProvider && this.isProviderHealthy(preferredProvider)
            ? preferredProvider
            : this.fallbackChain.getNextProvider();
        if (!provider) {
            // All providers exhausted, try to recover
            provider = this.config.providers[0];
        }
        // Select model for provider and tier
        const model = this.selectModelForTier(provider, targetTier, request);
        const providerHealth = this.fallbackChain.getHealthStatus(provider);
        // Build alternatives list
        const alternatives = this.buildAlternatives(provider, model);
        const result = {
            provider,
            model,
            tier: targetTier,
            reason: this.buildReason(classification, targetTier, provider),
            providerHealth,
            classification,
            estimatedCost: this.estimateCost(model, request.estimatedTokens),
            isFallback: provider !== (preferredProvider ?? this.config.providers[0]),
            alternatives,
        };
        this.routeHistory.push(result);
        this.emit('route:selected', result);
        return result;
    }
    /**
     * Route to a forced model
     */
    routeToForced(request) {
        const model = request.forceModel;
        const provider = this.findProviderForModel(model);
        const tier = this.getTierForModel(model);
        const providerHealth = this.fallbackChain.getHealthStatus(provider);
        return {
            provider,
            model,
            tier,
            reason: 'forced_model',
            providerHealth,
            isFallback: false,
            alternatives: [],
        };
    }
    /**
     * Get fallback route if current route fails
     */
    async getFallbackRoute(failedResult, error) {
        // Record failure
        this.fallbackChain.recordFailure(failedResult.provider, error);
        // Try alternatives
        for (const alt of failedResult.alternatives) {
            if (this.isProviderHealthy(alt.provider)) {
                const tier = this.getTierForModel(alt.model);
                const providerHealth = this.fallbackChain.getHealthStatus(alt.provider);
                const result = {
                    provider: alt.provider,
                    model: alt.model,
                    tier,
                    reason: `fallback_from_${failedResult.provider}_${error}`,
                    providerHealth,
                    isFallback: true,
                    alternatives: this.buildAlternatives(alt.provider, alt.model),
                };
                this.emit('route:fallback', failedResult, result, error);
                return result;
            }
        }
        // Try next provider in chain
        const nextProvider = this.fallbackChain.getNextProvider(true);
        if (nextProvider) {
            const model = this.selectModelForTier(nextProvider, failedResult.tier);
            const providerHealth = this.fallbackChain.getHealthStatus(nextProvider);
            const result = {
                provider: nextProvider,
                model,
                tier: failedResult.tier,
                reason: `chain_fallback_${error}`,
                providerHealth,
                isFallback: true,
                alternatives: [],
            };
            this.emit('route:fallback', failedResult, result, error);
            return result;
        }
        return null;
    }
    // ==========================================================================
    // Health Tracking
    // ==========================================================================
    /**
     * Record successful route execution
     */
    recordSuccess(result, responseTimeMs, actualCost) {
        this.fallbackChain.recordSuccess(result.provider, responseTimeMs);
        if (actualCost !== undefined) {
            this.addCost(actualCost);
        }
    }
    /**
     * Record failed route execution
     */
    recordFailure(result, error) {
        this.fallbackChain.recordFailure(result.provider, error);
    }
    /**
     * Check if provider is healthy
     */
    isProviderHealthy(provider) {
        return this.fallbackChain.isProviderHealthy(provider);
    }
    /**
     * Get health status for all providers
     */
    getAllHealth() {
        return this.fallbackChain.getAllHealthStatus();
    }
    // ==========================================================================
    // Cost Management
    // ==========================================================================
    /**
     * Add to current session cost
     */
    addCost(cost) {
        this.currentCost += cost;
        const budget = this.config.sessionBudget ?? DEFAULT_CONFIG.sessionBudget;
        const warningThreshold = budget * 0.8;
        if (this.currentCost >= budget) {
            this.emit('budget:exceeded', this.currentCost, budget);
        }
        else if (this.currentCost >= warningThreshold) {
            this.emit('budget:warning', this.currentCost, budget);
        }
    }
    /**
     * Get current session cost
     */
    getCurrentCost() {
        return this.currentCost;
    }
    /**
     * Reset session cost
     */
    resetCost() {
        this.currentCost = 0;
    }
    /**
     * Check if we should downgrade tier due to budget
     */
    shouldDowngrade() {
        const budget = this.config.sessionBudget ?? DEFAULT_CONFIG.sessionBudget;
        return this.currentCost >= budget * 0.8;
    }
    // ==========================================================================
    // Helper Methods
    // ==========================================================================
    /**
     * Get tier for task classification
     */
    getTierForClassification(classification) {
        if (classification.requiresVision) {
            return 'vision';
        }
        if (classification.requiresReasoning || classification.complexity === 'reasoning_heavy') {
            return 'reasoning';
        }
        if (classification.complexity === 'complex') {
            return 'standard';
        }
        if (classification.complexity === 'moderate') {
            return 'standard';
        }
        return 'mini';
    }
    /**
     * Get lower tier for downgrading
     */
    getLowerTier(tier) {
        const index = TIER_ORDER.indexOf(tier);
        if (index > 0) {
            return TIER_ORDER[index - 1];
        }
        return null;
    }
    /**
     * Select model for provider and tier
     */
    selectModelForTier(provider, tier, request) {
        const models = this.config.models[provider] || [];
        // Find model matching tier
        // Models are expected to be ordered: mini, standard, reasoning
        const tierIndex = TIER_ORDER.indexOf(tier);
        if (models.length > 0) {
            // Try to get model at tier index, or closest lower
            const modelIndex = Math.min(tierIndex, models.length - 1);
            return models[modelIndex];
        }
        // Fallback to first model
        return models[0] || `${provider}-default`;
    }
    /**
     * Find provider that has the given model
     */
    findProviderForModel(model) {
        for (const [provider, models] of Object.entries(this.config.models)) {
            if (models.includes(model)) {
                return provider;
            }
        }
        return this.config.providers[0];
    }
    /**
     * Get tier for a specific model
     */
    getTierForModel(model) {
        const modelLower = model.toLowerCase();
        if (modelLower.includes('vision') || modelLower.includes('4o')) {
            return 'vision';
        }
        if (modelLower.includes('reasoning') || modelLower.includes('o1') || modelLower.includes('opus')) {
            return 'reasoning';
        }
        if (modelLower.includes('mini') || modelLower.includes('haiku') || modelLower.includes('flash')) {
            return 'mini';
        }
        return 'standard';
    }
    /**
     * Build alternatives list
     */
    buildAlternatives(currentProvider, currentModel) {
        const alternatives = [];
        // Add other models from same provider
        const providerModels = this.config.models[currentProvider] || [];
        for (const model of providerModels) {
            if (model !== currentModel) {
                alternatives.push({ provider: currentProvider, model });
            }
        }
        // Add models from other providers
        for (const provider of this.config.providers) {
            if (provider !== currentProvider) {
                const models = this.config.models[provider] || [];
                if (models.length > 0) {
                    alternatives.push({ provider, model: models[0] });
                }
            }
        }
        return alternatives;
    }
    /**
     * Build reason string
     */
    buildReason(classification, tier, provider) {
        const parts = [
            `complexity=${classification.complexity}`,
            `tier=${tier}`,
            `confidence=${(classification.confidence * 100).toFixed(0)}%`,
        ];
        if (classification.requiresVision) {
            parts.push('requires_vision');
        }
        if (classification.requiresReasoning) {
            parts.push('requires_reasoning');
        }
        return parts.join(', ');
    }
    /**
     * Estimate cost for model and tokens
     */
    estimateCost(model, estimatedTokens) {
        if (!estimatedTokens) {
            return undefined;
        }
        // Rough cost estimates per 1M tokens (combined input/output)
        const costMap = {
            'grok-3-mini': 0.3,
            'grok-3': 3.0,
            'grok-3-reasoning': 5.0,
            'gpt-4o-mini': 0.15,
            'gpt-4o': 5.0,
            'claude-3-haiku': 0.25,
            'claude-3-sonnet': 3.0,
            'claude-3-opus': 15.0,
        };
        const costPerMillion = costMap[model] ?? 1.0;
        return (estimatedTokens / 1_000_000) * costPerMillion;
    }
    // ==========================================================================
    // Statistics
    // ==========================================================================
    /**
     * Get routing statistics
     */
    getStats() {
        const routesByProvider = {};
        const routesByTier = {};
        let fallbackRoutes = 0;
        for (const route of this.routeHistory) {
            routesByProvider[route.provider] = (routesByProvider[route.provider] || 0) + 1;
            routesByTier[route.tier] = (routesByTier[route.tier] || 0) + 1;
            if (route.isFallback) {
                fallbackRoutes++;
            }
        }
        return {
            totalRoutes: this.routeHistory.length,
            fallbackRoutes,
            costToDate: this.currentCost,
            routesByProvider,
            routesByTier,
            healthByProvider: this.getAllHealth(),
        };
    }
    /**
     * Format statistics as string
     */
    formatStats() {
        const stats = this.getStats();
        const lines = [
            'Smart Router Statistics',
            '='.repeat(40),
            `Total Routes: ${stats.totalRoutes}`,
            `Fallback Routes: ${stats.fallbackRoutes} (${((stats.fallbackRoutes / (stats.totalRoutes || 1)) * 100).toFixed(1)}%)`,
            `Session Cost: $${stats.costToDate.toFixed(4)}`,
            '',
            'Routes by Provider:',
            ...Object.entries(stats.routesByProvider).map(([p, c]) => `  ${p}: ${c}`),
            '',
            'Routes by Tier:',
            ...Object.entries(stats.routesByTier).map(([t, c]) => `  ${t}: ${c}`),
            '',
            'Provider Health:',
            ...stats.healthByProvider.map(h => `  ${h.provider}: ${h.healthy ? '✓' : '✗'} (failures: ${h.failureCount}, avg: ${h.avgResponseTimeMs}ms)`),
        ];
        return lines.join('\n');
    }
    // ==========================================================================
    // Lifecycle
    // ==========================================================================
    /**
     * Reset router state
     */
    reset() {
        this.currentCost = 0;
        this.routeHistory = [];
        this.fallbackChain.reset();
    }
    /**
     * Dispose router
     */
    dispose() {
        this.reset();
        this.removeAllListeners();
    }
}
// ============================================================================
// Singleton Instance
// ============================================================================
let smartRouterInstance = null;
/**
 * Get singleton SmartModelRouter instance
 */
export function getSmartRouter(config) {
    if (!smartRouterInstance) {
        smartRouterInstance = new SmartModelRouter(config);
    }
    else if (config) {
        smartRouterInstance.configureChain(config);
    }
    return smartRouterInstance;
}
/**
 * Reset singleton instance
 */
export function resetSmartRouter() {
    if (smartRouterInstance) {
        smartRouterInstance.dispose();
    }
    smartRouterInstance = null;
}
export default SmartModelRouter;
//# sourceMappingURL=smart-router.js.map