/**
 * Model Routing Intelligence for UAP
 *
 * Routes tasks to the optimal model based on:
 * - Task classification and difficulty
 * - Model capability fingerprints (benchmarked data)
 * - Latency/accuracy/cost tradeoffs
 * - Fallback chains for resilience
 * - OPT 8: Persistent fingerprint data from SQLite
 *
 * Based on BENCHMARK_ANALYSIS.md and MODEL_BENCHMARK_RESULTS.md data.
 */
import Database from 'better-sqlite3';
import { existsSync, mkdirSync } from 'fs';
import { createLogger } from '../utils/logger.js';
const log = createLogger('model-router');
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import { classifyTask } from './task-classifier.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const DEFAULT_CONFIG = {
    preferLatency: false,
    preferAccuracy: true,
    maxCostPerTask: 0.05,
    maxLatencyMs: 120000,
    availableModels: ['opus-4.6', 'sonnet-4.6', 'haiku', 'qwen35-a3b', 'gpt-5.4', 'gpt-5.3-codex'],
};
// OPTIMIZATION 5: Pre-seeded with benchmark data for per-category routing
const MODEL_FINGERPRINTS = {
    'opus-4.6': {
        id: 'opus-4.6',
        strengths: [
            'accuracy',
            'complex-reasoning',
            'edge-cases',
            'error-handling',
            'refactoring',
            'advanced-planning',
        ],
        weaknesses: ['latency', 'cost'],
        avgLatencyMs: 24000,
        successRate: 0.9,
        costPerTask: 0.03,
        maxComplexity: 'hard',
        bestCategories: ['security', 'coding', 'sysadmin', 'debugging', 'constraint-satisfaction'],
        categoryStats: {
            security: { attempts: 8, successes: 8 },
            coding: { attempts: 8, successes: 7 },
            sysadmin: { attempts: 5, successes: 5 },
            debugging: { attempts: 5, successes: 5 },
            'file-ops': { attempts: 5, successes: 4 },
            'ml-training': { attempts: 3, successes: 3 },
            'constraint-satisfaction': { attempts: 3, successes: 3 },
        },
    },
    'sonnet-4.6': {
        id: 'sonnet-4.6',
        strengths: ['balance', 'code-generation', 'review', 'agentic', 'tool-use'],
        weaknesses: ['complex-algorithms'],
        avgLatencyMs: 18000,
        successRate: 0.85,
        costPerTask: 0.01,
        maxComplexity: 'hard',
        bestCategories: ['coding', 'debugging', 'testing', 'file-ops'],
        categoryStats: {
            coding: { attempts: 8, successes: 7 },
            debugging: { attempts: 5, successes: 4 },
            testing: { attempts: 5, successes: 5 },
            'file-ops': { attempts: 5, successes: 4 },
            security: { attempts: 4, successes: 3 },
            sysadmin: { attempts: 3, successes: 3 },
        },
    },
    haiku: {
        id: 'haiku',
        strengths: ['speed', 'simple-tasks', 'low-cost', 'high-throughput'],
        weaknesses: ['complex-reasoning', 'long-context', 'multi-step-code'],
        avgLatencyMs: 5000,
        successRate: 0.7,
        costPerTask: 0.002,
        maxComplexity: 'medium',
        bestCategories: ['coding', 'testing', 'debugging'],
        categoryStats: {
            coding: { attempts: 8, successes: 6 },
            testing: { attempts: 5, successes: 4 },
            debugging: { attempts: 4, successes: 3 },
            security: { attempts: 4, successes: 2 },
            'file-ops': { attempts: 3, successes: 2 },
            sysadmin: { attempts: 3, successes: 1 },
        },
    },
    'qwen35-a3b': {
        id: 'qwen35-a3b',
        strengths: ['speed', 'code-generation', 'simple-tasks', 'low-cost'],
        weaknesses: ['complex-reasoning', 'long-context', 'multi-step-code'],
        avgLatencyMs: 8000,
        successRate: 0.6,
        costPerTask: 0.0,
        maxComplexity: 'medium',
        bestCategories: ['coding', 'testing', 'file-ops'],
        categoryStats: {
            coding: { attempts: 8, successes: 5 },
            testing: { attempts: 5, successes: 3 },
            'file-ops': { attempts: 4, successes: 3 },
            debugging: { attempts: 4, successes: 2 },
            security: { attempts: 3, successes: 1 },
            sysadmin: { attempts: 3, successes: 1 },
        },
    },
    'gpt-5.4': {
        id: 'gpt-5.4',
        strengths: ['balance', 'consistency', 'general-purpose', 'algorithm', 'multi-step'],
        weaknesses: ['refactoring', 'latency-sensitive'],
        avgLatencyMs: 20000,
        successRate: 0.9,
        costPerTask: 0.005,
        maxComplexity: 'hard',
        bestCategories: ['coding', 'security', 'file-ops', 'debugging'],
        categoryStats: {
            coding: { attempts: 8, successes: 7 },
            security: { attempts: 6, successes: 5 },
            'file-ops': { attempts: 5, successes: 5 },
            debugging: { attempts: 5, successes: 5 },
            sysadmin: { attempts: 4, successes: 3 },
            'ml-training': { attempts: 3, successes: 3 },
            'constraint-satisfaction': { attempts: 3, successes: 3 },
        },
    },
    'gpt-5.3-codex': {
        id: 'gpt-5.3-codex',
        strengths: ['code-specific', 'syntax-accuracy', 'context-awareness', 'all-difficulties'],
        weaknesses: ['latency', 'cost', 'non-code-tasks'],
        avgLatencyMs: 30000,
        successRate: 0.95,
        costPerTask: 0.008,
        maxComplexity: 'hard',
        bestCategories: ['coding', 'testing'],
        categoryStats: {
            coding: { attempts: 8, successes: 8 },
            testing: { attempts: 5, successes: 5 },
            security: { attempts: 3, successes: 3 },
            'file-ops': { attempts: 3, successes: 2 },
        },
    },
};
const COMPLEXITY_RANK = { easy: 1, medium: 2, hard: 3 };
// OPT 8: SQLite-backed fingerprint persistence (simplified from pool-of-1)
let _fingerprintDb = null;
function initFingerprintDb() {
    if (_fingerprintDb)
        return _fingerprintDb;
    const dbDir = join(__dirname, '../../agents/data/memory');
    if (!existsSync(dbDir)) {
        mkdirSync(dbDir, { recursive: true });
    }
    const dbPath = join(dbDir, 'model_fingerprints.db');
    const db = new Database(dbPath);
    db.pragma('journal_mode = WAL');
    db.pragma('busy_timeout = 10000');
    db.pragma('synchronous = NORMAL');
    db.exec(`
    CREATE TABLE IF NOT EXISTS fingerprint_updates (
      model_id TEXT NOT NULL,
      avg_latency_ms REAL,
      success_rate REAL,
      updated_at INTEGER,
      PRIMARY KEY (model_id)
    );
    
    CREATE TABLE IF NOT EXISTS category_stats (
      model_id TEXT NOT NULL,
      category TEXT NOT NULL,
      attempts INTEGER DEFAULT 0,
      successes INTEGER DEFAULT 0,
      updated_at INTEGER,
      PRIMARY KEY (model_id, category)
    );
    
    CREATE INDEX IF NOT EXISTS idx_category_stats_model ON category_stats(model_id);
  `);
    _fingerprintDb = db;
    return db;
}
export function closeAllPools() {
    if (_fingerprintDb) {
        try {
            _fingerprintDb.close();
        }
        catch {
            // Close failure is non-fatal
        }
        _fingerprintDb = null;
    }
}
export function getFingerprintDb() {
    return initFingerprintDb();
}
/**
 * OPT 8: Load persisted fingerprint data on module init
 */
function loadPersistedFingerprints() {
    try {
        const db = getFingerprintDb();
        // Load global updates
        const updates = db.prepare('SELECT * FROM fingerprint_updates').all();
        for (const update of updates) {
            const fp = MODEL_FINGERPRINTS[update.model_id];
            if (fp) {
                // Blend persisted data with defaults (70% persisted, 30% default)
                fp.avgLatencyMs = fp.avgLatencyMs * 0.3 + update.avg_latency_ms * 0.7;
                fp.successRate = fp.successRate * 0.3 + update.success_rate * 0.7;
            }
        }
        // Load category stats
        const categoryData = db.prepare('SELECT * FROM category_stats').all();
        for (const cat of categoryData) {
            const fp = MODEL_FINGERPRINTS[cat.model_id];
            if (fp) {
                if (!fp.categoryStats)
                    fp.categoryStats = {};
                // Merge with existing stats
                const existing = fp.categoryStats[cat.category] || { attempts: 0, successes: 0 };
                fp.categoryStats[cat.category] = {
                    attempts: existing.attempts + cat.attempts,
                    successes: existing.successes + cat.successes,
                };
            }
        }
    }
    catch (err) {
        // Silently fail - fingerprints will use defaults
        log.debug('Failed to load persisted fingerprints:', err);
    }
}
/**
 * OPT 8: Persist fingerprint updates to SQLite
 */
function persistFingerprintUpdate(modelId) {
    try {
        const db = getFingerprintDb();
        const fp = MODEL_FINGERPRINTS[modelId];
        if (!fp)
            return;
        db.prepare(`
      INSERT OR REPLACE INTO fingerprint_updates (model_id, avg_latency_ms, success_rate, updated_at)
      VALUES (?, ?, ?, ?)
    `).run(modelId, fp.avgLatencyMs, fp.successRate, Date.now());
    }
    catch (err) {
        log.debug('Failed to persist fingerprint update:', err);
    }
}
/**
 * OPT 8: Persist category stats to SQLite
 */
function persistCategoryStats(modelId, category) {
    try {
        const db = getFingerprintDb();
        const fp = MODEL_FINGERPRINTS[modelId];
        const stats = fp?.categoryStats?.[category];
        if (!stats)
            return;
        db.prepare(`
      INSERT OR REPLACE INTO category_stats (model_id, category, attempts, successes, updated_at)
      VALUES (?, ?, ?, ?, ?)
    `).run(modelId, category, stats.attempts, stats.successes, Date.now());
    }
    catch (err) {
        log.debug('Failed to persist category stats:', err);
    }
}
// Deferred initialization — fingerprints load on first use, not module import
let _fingerprintsLoaded = false;
function ensureFingerprintsLoaded() {
    if (_fingerprintsLoaded)
        return;
    _fingerprintsLoaded = true;
    loadPersistedFingerprints();
}
/**
 * Failure handlers for model-specific known issues
 */
const FAILURE_HANDLERS = {
    'gpt-5.3-codex:permission_denied': {
        action: 'add_context',
        context: 'Do not attempt file operations. Return code only.',
        fallbackModel: 'gpt-5.4',
    },
    'haiku:timeout': {
        action: 'reduce_context',
        fallbackModel: 'sonnet-4.6',
    },
    'haiku:context_overflow': {
        action: 'reduce_context',
        fallbackModel: 'sonnet-4.6',
    },
    'qwen35-a3b:timeout': {
        action: 'reduce_context',
        fallbackModel: 'haiku',
    },
    'qwen35-a3b:context_overflow': {
        action: 'reduce_context',
        fallbackModel: 'haiku',
    },
};
/**
 * OPTIMIZATION 5: Enhanced per-category scoring with confidence weighting
 * Score a model for a given task using category-specific success rates
 */
function scoreModel(model, classification, difficulty, config) {
    let score = 0;
    // Category match bonus - INCREASED weight for proven categories
    if (model.bestCategories.includes(classification.category)) {
        score += 40; // Increased from 30
    }
    // Complexity match
    if (COMPLEXITY_RANK[model.maxComplexity] >= COMPLEXITY_RANK[difficulty]) {
        score += 20;
    }
    else {
        score -= 50; // Penalty for difficulty exceeding capability
    }
    // OPTIMIZATION 5: Use per-category success rate with confidence weighting
    // If we have enough data (>=3 attempts), weight category rate 2x higher than global
    const categoryRate = getCategorySuccessRate(model, classification.category);
    const categoryAttempts = model.categoryStats?.[classification.category]?.attempts || 0;
    let effectiveSuccessRate;
    if (categoryRate !== null && categoryAttempts >= 3) {
        // Blend category and global rates, weighted by sample size confidence
        const confidence = Math.min(categoryAttempts / 10, 1); // Max confidence at 10 samples
        effectiveSuccessRate = categoryRate * confidence + model.successRate * (1 - confidence);
        // Bonus for having category-specific data
        score += 10;
    }
    else {
        effectiveSuccessRate = model.successRate;
    }
    // Success rate (0-30 points) - INCREASED from 25
    score += effectiveSuccessRate * 30;
    // Latency preference (0-15 points)
    if (config.preferLatency) {
        const latencyScore = Math.max(0, 1 - model.avgLatencyMs / 120000);
        score += latencyScore * 15;
    }
    // Accuracy preference bonus
    if (config.preferAccuracy) {
        score += effectiveSuccessRate * 10;
    }
    // Cost constraint
    if (model.costPerTask > config.maxCostPerTask) {
        score -= 20;
    }
    // Latency constraint
    if (model.avgLatencyMs > config.maxLatencyMs) {
        score -= 30;
    }
    // Keyword match with model strengths - INCREASED weight
    const taskKeywords = classification.keywords.map((k) => k.toLowerCase());
    for (const strength of model.strengths) {
        if (taskKeywords.some((kw) => strength.includes(kw) || kw.includes(strength))) {
            score += 8; // Increased from 5
        }
    }
    return score;
}
/**
 * Get per-category success rate if enough data exists (>=3 attempts)
 * Returns null if not enough data, allowing fallback to global rate
 */
function getCategorySuccessRate(model, category) {
    const stats = model.categoryStats?.[category];
    if (!stats || stats.attempts < 3)
        return null;
    return stats.successes / stats.attempts;
}
/**
 * Route a task to the best model
 */
export function routeTask(instruction, difficulty = 'medium', config = {}) {
    ensureFingerprintsLoaded();
    const cfg = { ...DEFAULT_CONFIG, ...config };
    const classification = classifyTask(instruction);
    // Score all available models
    const scored = cfg.availableModels
        .filter((id) => MODEL_FINGERPRINTS[id])
        .map((id) => ({
        id,
        fingerprint: MODEL_FINGERPRINTS[id],
        score: scoreModel(MODEL_FINGERPRINTS[id], classification, difficulty, cfg),
    }))
        .sort((a, b) => b.score - a.score);
    if (scored.length === 0) {
        return {
            primary: 'gpt-5.4',
            fallback: ['opus-4.6'],
            reason: 'No available models matched, defaulting to GPT 5.4',
            estimatedLatencyMs: 20000,
            estimatedSuccessRate: 0.9,
            estimatedCost: 0.005,
        };
    }
    const primary = scored[0];
    const fallbacks = scored.slice(1).map((s) => s.id);
    return {
        primary: primary.id,
        fallback: fallbacks,
        reason: `${primary.id} selected for ${classification.category}/${difficulty} task (score: ${primary.score.toFixed(0)})`,
        estimatedLatencyMs: primary.fingerprint.avgLatencyMs,
        estimatedSuccessRate: primary.fingerprint.successRate,
        estimatedCost: primary.fingerprint.costPerTask,
    };
}
/**
 * Get failure handler for a model-specific error
 */
export function getFailureHandler(modelId, errorType) {
    return FAILURE_HANDLERS[`${modelId}:${errorType}`] || null;
}
/**
 * Get model fingerprint
 */
export function getModelFingerprint(modelId) {
    return MODEL_FINGERPRINTS[modelId] || null;
}
/**
 * Get all model fingerprints
 */
export function getAllModelFingerprints() {
    return { ...MODEL_FINGERPRINTS };
}
/**
 * Update model fingerprint with new benchmark data
 */
export function updateModelFingerprint(modelId, updates) {
    const fp = MODEL_FINGERPRINTS[modelId];
    if (!fp)
        return;
    if (updates.avgLatencyMs !== undefined) {
        // Exponential moving average
        fp.avgLatencyMs = fp.avgLatencyMs * 0.7 + updates.avgLatencyMs * 0.3;
    }
    if (updates.successRate !== undefined) {
        fp.successRate = fp.successRate * 0.7 + updates.successRate * 0.3;
    }
    if (updates.costPerTask !== undefined) {
        fp.costPerTask = updates.costPerTask;
    }
}
/**
 * Record task outcome to update model fingerprints (feedback loop)
 * Call this after each task completes to improve future routing decisions
 * OPT 8: Now persists to SQLite for cross-session learning
 */
export function recordTaskOutcome(modelId, success, latencyMs, taskCategory) {
    const fp = MODEL_FINGERPRINTS[modelId];
    if (!fp)
        return;
    // Update global success rate using exponential moving average
    const newSuccessRate = success ? 1.0 : 0.0;
    fp.successRate = fp.successRate * 0.9 + newSuccessRate * 0.1;
    // Update latency using exponential moving average
    fp.avgLatencyMs = fp.avgLatencyMs * 0.8 + latencyMs * 0.2;
    // OPT 8: Persist global fingerprint update
    persistFingerprintUpdate(modelId);
    // Update per-category stats if category provided
    if (taskCategory) {
        if (!fp.categoryStats)
            fp.categoryStats = {};
        if (!fp.categoryStats[taskCategory]) {
            fp.categoryStats[taskCategory] = { attempts: 0, successes: 0 };
        }
        fp.categoryStats[taskCategory].attempts++;
        if (success) {
            fp.categoryStats[taskCategory].successes++;
        }
        // OPT 8: Persist category stats
        persistCategoryStats(modelId, taskCategory);
    }
}
/**
 * Get routing recommendation with explanation
 */
export function explainRouting(instruction, difficulty = 'medium') {
    const decision = routeTask(instruction, difficulty);
    const fingerprint = MODEL_FINGERPRINTS[decision.primary];
    const lines = [
        `Primary Model: ${decision.primary}`,
        `Reason: ${decision.reason}`,
        `Expected Latency: ${(decision.estimatedLatencyMs / 1000).toFixed(1)}s`,
        `Expected Success: ${(decision.estimatedSuccessRate * 100).toFixed(0)}%`,
        `Strengths: ${fingerprint?.strengths.slice(0, 3).join(', ') || 'N/A'}`,
        `Fallbacks: ${decision.fallback.join(' -> ') || 'None'}`,
    ];
    return lines.join('\n');
}
/**
 * OPT 8: Close fingerprint database connection pool
 */
export function closeFingerprintDb() {
    closeAllPools();
}
/**
 * OPT 8: Force reload fingerprints from database
 */
export function reloadFingerprints() {
    loadPersistedFingerprints();
}
export const ModelRouter = {
    routeTask,
    getFailureHandler,
    getModelFingerprint,
    getAllModelFingerprints,
    updateModelFingerprint,
    recordTaskOutcome,
    explainRouting,
    closeFingerprintDb,
    reloadFingerprints,
};
export default ModelRouter;
//# sourceMappingURL=model-router.js.map