// engine/model-router/rules-engine.ts — Deterministic rules evaluation for model routing.
// SPEC-076 AC-02
import defaultRulesJson from '../../config/model-routing-rules.json' with { type: 'json' };
// ---------------------------------------------------------------------------
// Model tier ordering (for tie-breaking: higher index = more capable)
// ---------------------------------------------------------------------------
const TIER_ORDER = ['haiku', 'sonnet', 'opus'];
function tierIndex(tier) {
    return TIER_ORDER.indexOf(tier);
}
// ---------------------------------------------------------------------------
// Condition evaluation
// ---------------------------------------------------------------------------
function getSignalValue(signals, signal) {
    // ComplexitySignals is a named interface without an index signature; dynamic string-key access
    // requires a double cast. Signal names come from JSON rules config validated at rule eval time.
    return signals[signal];
}
/** Evaluate a single condition against a signal value. */
export function evaluateCondition(signals, condition) {
    const raw = getSignalValue(signals, condition.signal);
    if (raw === undefined) {
        return false;
    }
    const { operator, value } = condition;
    switch (operator) {
        case 'eq':
            return raw === value;
        case 'gt':
            return typeof raw === 'number' && typeof value === 'number' && raw > value;
        case 'gte':
            return typeof raw === 'number' && typeof value === 'number' && raw >= value;
        case 'lt':
            return typeof raw === 'number' && typeof value === 'number' && raw < value;
        case 'lte':
            return typeof raw === 'number' && typeof value === 'number' && raw <= value;
        case 'between': {
            if (typeof raw !== 'number' || !Array.isArray(value)) {
                return false;
            }
            const [lo, hi] = value;
            return raw >= lo && raw <= hi;
        }
        default:
            return false;
    }
}
// ---------------------------------------------------------------------------
// Rule evaluation
// ---------------------------------------------------------------------------
/** Evaluate a single rule against the given signals. */
export function evaluateRule(signals, rule) {
    // An empty conditions list means "always match" (catch-all)
    if (rule.conditions.length === 0) {
        return { rule, matchRatio: 1, confidence: rule.weight };
    }
    const matches = rule.conditions.filter((c) => evaluateCondition(signals, c));
    const matchRatio = matches.length / rule.conditions.length;
    const confidence = matchRatio * rule.weight;
    return { rule, matchRatio, confidence };
}
/** Load rules from the built-in config. Custom rules support is left as a hook. */
export function loadRules(customRules) {
    const config = defaultRulesJson;
    const base = config.rules;
    if (!customRules || customRules.length === 0) {
        return base;
    }
    // Custom rules have higher priority: prepend them
    return [...customRules, ...base];
}
// ---------------------------------------------------------------------------
// Main evaluation
// ---------------------------------------------------------------------------
/**
 * Evaluate all rules against the given signals and return the best model
 * recommendation with alternatives.
 */
export function evaluateRules(signals, customRules) {
    const rules = loadRules(customRules);
    const matches = rules.map((r) => evaluateRule(signals, r));
    // Only keep matches where at least all conditions matched (matchRatio === 1)
    // unless no rule fully matches, then relax to best partial match
    let fullMatches = matches.filter((m) => m.matchRatio === 1 && m.confidence > 0);
    if (fullMatches.length === 0) {
        fullMatches = matches.filter((m) => m.confidence > 0);
    }
    // Sort by confidence desc, then by tier capability desc (tie-break: more capable wins)
    fullMatches.sort((a, b) => {
        const confDiff = b.confidence - a.confidence;
        if (Math.abs(confDiff) > 0.001) {
            return confDiff;
        }
        return tierIndex(b.rule.result) - tierIndex(a.rule.result);
    });
    const best = fullMatches[0];
    /* c8 ignore next 5 */
    if (!best) {
        // Absolute fallback — should never happen because of the catch-all sonnet rule
        return {
            model: 'sonnet',
            confidence: 0.5,
            reasoning: ['Default: sonnet (no rule matched)'],
            alternatives: [],
        };
    }
    const reasoning = buildReasoning(best, signals);
    // Collect alternatives (different model tiers from other matches)
    const seen = new Set([best.rule.result]);
    const alternatives = [];
    for (const m of fullMatches) {
        if (!seen.has(m.rule.result)) {
            seen.add(m.rule.result);
            alternatives.push({ model: m.rule.result, confidence: m.confidence });
        }
    }
    const result = {
        model: best.rule.result,
        confidence: best.confidence,
        reasoning,
        alternatives,
    };
    // Inject ultrathink guidance for opus/sonnet when signals indicate deep reasoning (SPEC-259)
    if (signals.effortSuggestion === 'ultrathink') {
        const recommendedModel = best.rule.result;
        if (recommendedModel === 'opus' || recommendedModel === 'sonnet') {
            result.ultrathinkSuggested = true;
            result.ultrathinkReason = 'Task requires deep architectural reasoning';
            result.effortLevelCommand =
                "Run /effort high or include 'ultrathink' anywhere in your prompt";
            result.thinkingToggle =
                'Press Option+T (macOS) / Alt+T (Windows/Linux) to toggle extended thinking';
        }
    }
    return result;
}
// ---------------------------------------------------------------------------
// Reasoning builder
// ---------------------------------------------------------------------------
function buildReasoning(match, signals) {
    const reasons = [];
    reasons.push(`Rule "${match.rule.name}" matched with confidence ${match.confidence.toFixed(2)}.`);
    if (signals.criteriaCount > 15) {
        reasons.push(`High criteria count (${String(signals.criteriaCount)}) suggests a complex task.`);
    }
    if (signals.crossLayerCount > 2) {
        reasons.push(`Cross-layer changes (${String(signals.crossLayerCount)} layers) increase coordination complexity.`);
    }
    if (signals.isReadOnly) {
        reasons.push('Read-only task — a lighter model can handle this efficiently.');
    }
    if (signals.hasSecurityImplications) {
        reasons.push('Security implications detected — a more capable model is recommended.');
    }
    return reasons;
}
//# sourceMappingURL=rules-engine.js.map