/**
 * Provider Capability Registry
 *
 * Unified capability contracts per provider/model for routing decisions.
 * Routing choices are fully explainable from capability records.
 */
import { getCacheCapability } from './cache-capability.js';
// ---------------------------------------------------------------------------
// Route Explanation
// ---------------------------------------------------------------------------
/**
 * Typed reason codes for routing rejections.
 * Use these instead of free-form strings so callers can branch on them.
 */
export const RouteRejectionCode = {
    NO_STREAMING: 'NO_STREAMING',
    NO_TOOL_CALLING: 'NO_TOOL_CALLING',
    NO_PARALLEL_TOOLS: 'NO_PARALLEL_TOOLS',
    NO_JSON_MODE: 'NO_JSON_MODE',
    NO_REASONING_CONTROLS: 'NO_REASONING_CONTROLS',
    CONTEXT_TOO_SMALL: 'CONTEXT_TOO_SMALL',
    OUTPUT_TOO_SMALL: 'OUTPUT_TOO_SMALL',
};
// ---------------------------------------------------------------------------
// Provider Defaults
// ---------------------------------------------------------------------------
/**
 * Baseline capability defaults per built-in provider.
 * Fields not listed fall back to `GLOBAL_DEFAULTS`.
 *
 * Note: All known providers intentionally specify every field — this makes each
 * provider's contract explicit and self-contained, at the cost of requiring all
 * entries to be updated when a new `ProviderCapability` field is added.
 * Unknown/custom providers fall back to `GLOBAL_DEFAULTS` for unspecified fields.
 */
const PROVIDER_DEFAULTS = {
    anthropic: {
        streaming: true,
        toolCalling: true,
        parallelTools: true,
        jsonMode: false, // Anthropic uses structured output via tool schemas, not a json_mode flag
        reasoningControls: false, // Extended thinking enabled per-model via reasoningEffort
        maxContextTokens: 200_000,
        maxOutputTokens: 8_192,
        timeoutMs: 120_000,
    },
    openai: {
        streaming: true,
        toolCalling: true,
        parallelTools: true,
        jsonMode: true,
        reasoningControls: false, // o-series models override this
        maxContextTokens: 128_000,
        maxOutputTokens: 16_384,
        timeoutMs: 120_000,
    },
    gemini: {
        streaming: true,
        toolCalling: true,
        parallelTools: true,
        jsonMode: true,
        reasoningControls: false,
        maxContextTokens: 1_000_000,
        maxOutputTokens: 8_192,
        timeoutMs: 120_000,
    },
    inceptionlabs: {
        streaming: true,
        toolCalling: false, // mercury-2 does not support tool calling
        parallelTools: false,
        jsonMode: false,
        reasoningControls: true, // mercury reasoning budget
        maxContextTokens: 128_000,
        maxOutputTokens: 32_000,
        timeoutMs: 60_000,
    },
    openrouter: {
        streaming: true,
        toolCalling: true, // varies per routed model; default optimistic
        parallelTools: true,
        jsonMode: true,
        reasoningControls: false,
        maxContextTokens: 128_000,
        maxOutputTokens: 4_096,
        timeoutMs: 120_000,
    },
    groq: {
        streaming: true,
        toolCalling: true,
        parallelTools: false, // Groq does not support parallel tool calls
        jsonMode: true,
        reasoningControls: false,
        maxContextTokens: 131_072,
        maxOutputTokens: 8_192,
        timeoutMs: 30_000, // Groq is very fast; short timeout appropriate
    },
    cerebras: {
        streaming: true,
        toolCalling: true,
        parallelTools: false,
        jsonMode: true,
        reasoningControls: false,
        maxContextTokens: 128_000,
        maxOutputTokens: 8_192,
        timeoutMs: 30_000,
    },
    mistral: {
        streaming: true,
        toolCalling: true,
        parallelTools: false,
        jsonMode: true,
        reasoningControls: false,
        maxContextTokens: 128_000,
        maxOutputTokens: 4_096,
        timeoutMs: 60_000,
    },
    huggingface: {
        streaming: true,
        toolCalling: true,
        parallelTools: false,
        jsonMode: false,
        reasoningControls: false,
        maxContextTokens: 128_000,
        maxOutputTokens: 8_192,
        timeoutMs: 120_000,
    },
    ollama: {
        streaming: true,
        toolCalling: true,
        parallelTools: false,
        jsonMode: true,
        reasoningControls: false,
        maxContextTokens: 32_768,
        maxOutputTokens: 4_096,
        timeoutMs: 300_000, // Local models may be slow
    },
    'ollama-cloud': {
        streaming: true,
        toolCalling: true,
        parallelTools: false,
        jsonMode: true,
        reasoningControls: false,
        maxContextTokens: 131_072,
        maxOutputTokens: 8_192,
        timeoutMs: 120_000,
    },
    aihubmix: {
        streaming: true,
        toolCalling: true,
        parallelTools: true,
        jsonMode: true,
        reasoningControls: false,
        maxContextTokens: 128_000,
        maxOutputTokens: 8_192,
        timeoutMs: 120_000,
    },
    deepseek: {
        streaming: true,
        toolCalling: true,
        parallelTools: false,
        jsonMode: true,
        reasoningControls: false,
        maxContextTokens: 128_000,
        maxOutputTokens: 8_192,
        timeoutMs: 120_000,
    },
    together: {
        streaming: true,
        toolCalling: true,
        parallelTools: true,
        jsonMode: true,
        reasoningControls: false,
        maxContextTokens: 131_072,
        maxOutputTokens: 8_192,
        timeoutMs: 120_000,
    },
    fireworks: {
        streaming: true,
        toolCalling: true,
        parallelTools: true,
        jsonMode: true,
        reasoningControls: false,
        maxContextTokens: 131_072,
        maxOutputTokens: 8_192,
        timeoutMs: 120_000,
    },
    'github-copilot': {
        streaming: true,
        toolCalling: true,
        parallelTools: true,
        jsonMode: true,
        reasoningControls: false,
        maxContextTokens: 128_000,
        maxOutputTokens: 8_192,
        timeoutMs: 120_000,
    },
    'amazon-bedrock': {
        streaming: true,
        toolCalling: true,
        parallelTools: true,
        jsonMode: false,
        reasoningControls: true,
        maxContextTokens: 200_000,
        maxOutputTokens: 8_192,
        timeoutMs: 120_000,
    },
    'amazon-bedrock-mantle': {
        streaming: true,
        toolCalling: true,
        parallelTools: true,
        jsonMode: false,
        reasoningControls: true,
        maxContextTokens: 200_000,
        maxOutputTokens: 8_192,
        timeoutMs: 120_000,
    },
    'anthropic-vertex': {
        streaming: true,
        toolCalling: true,
        parallelTools: true,
        jsonMode: false,
        reasoningControls: true,
        maxContextTokens: 200_000,
        maxOutputTokens: 8_192,
        timeoutMs: 120_000,
    },
    minimax: {
        streaming: true,
        toolCalling: true,
        parallelTools: true,
        jsonMode: false,
        reasoningControls: true,
        maxContextTokens: 128_000,
        maxOutputTokens: 8_192,
        timeoutMs: 120_000,
    },
    xai: {
        streaming: true,
        toolCalling: true,
        parallelTools: true,
        jsonMode: true,
        reasoningControls: false,
        maxContextTokens: 131_072,
        maxOutputTokens: 8_192,
        timeoutMs: 120_000,
    },
};
/**
 * Global defaults applied when a provider is not in `PROVIDER_DEFAULTS`.
 * Intentionally conservative: no reasoning controls, modest token limits.
 */
const GLOBAL_DEFAULTS = {
    streaming: true,
    toolCalling: true,
    parallelTools: false,
    jsonMode: false,
    reasoningControls: false,
    maxContextTokens: 32_768,
    maxOutputTokens: 4_096,
    timeoutMs: 120_000,
    caching: 'none',
};
// ---------------------------------------------------------------------------
// Per-model overrides
// ---------------------------------------------------------------------------
/**
 * Model-level capability overrides keyed by model ID.
 * These take precedence over both provider defaults and `LLMProvider.capabilities`.
 */
const MODEL_OVERRIDES = {
    // Anthropic reasoning models
    'claude-opus-4-5': { reasoningControls: true, maxOutputTokens: 32_000 },
    'claude-sonnet-4-5': { reasoningControls: true, maxOutputTokens: 64_000 },
    'claude-3-5-sonnet-20241022': { maxOutputTokens: 8_192 },
    'claude-3-7-sonnet-20250219': { reasoningControls: true, maxOutputTokens: 64_000 },
    // OpenAI reasoning models
    'o1': { reasoningControls: true, jsonMode: false, maxOutputTokens: 32_768 },
    'o1-mini': { reasoningControls: true, jsonMode: false, maxOutputTokens: 65_536 },
    'o1-preview': { reasoningControls: true, jsonMode: false, maxOutputTokens: 32_768 },
    'o3': { reasoningControls: true, maxOutputTokens: 100_000 },
    'o3-mini': { reasoningControls: true, maxOutputTokens: 65_536 },
    'o4-mini': { reasoningControls: true, maxOutputTokens: 65_536 },
    // Mercury models (InceptionLabs)
    'mercury-2': { toolCalling: false, parallelTools: false, reasoningControls: true, maxOutputTokens: 32_000 },
    'mercury-edit': { toolCalling: false, parallelTools: false, reasoningControls: false, maxOutputTokens: 32_000 },
    // Gemini large context
    'gemini-2.5-pro': { maxContextTokens: 2_097_152, maxOutputTokens: 65_536, reasoningControls: true },
    'gemini-2.5-flash': { maxContextTokens: 1_048_576, maxOutputTokens: 65_536, reasoningControls: true },
    'gemini-2.0-flash': { maxContextTokens: 1_048_576, maxOutputTokens: 8_192 },
    // Gemini 3 series
    'gemini-3-flash-preview': { maxContextTokens: 1_048_576, maxOutputTokens: 65_536, reasoningControls: true },
};
// ---------------------------------------------------------------------------
// ProviderCapabilityRegistry
// ---------------------------------------------------------------------------
/**
 * Registry that resolves and caches capability records per provider/model,
 * and provides explainable routing decisions.
 *
 * Merge order (lowest to highest priority):
 * 1. `GLOBAL_DEFAULTS` — conservative baseline
 * 2. `PROVIDER_DEFAULTS[providerId]` — provider-level defaults
 * 3. `LLMProvider.capabilities` — self-declared by the provider instance
 * 4. `MODEL_OVERRIDES` — static per-model overrides
 *
 * Exception: the `caching` field is always sourced from `getCacheCapability(providerId)`
 * (falling back to `MODEL_OVERRIDES.caching` if present), so self-declared caching
 * from `LLMProvider.capabilities` is intentionally ignored.
 *
 * The cache key is `${providerId}::${modelId}`. Call `invalidate()` after dynamic
 * provider registration to avoid stale entries.
 */
export class ProviderCapabilityRegistry {
    cache = new Map();
    /**
     * Resolve the full capability record for a provider/model pair.
     *
     * @param providerId - The registered provider name (e.g. `'anthropic'`).
     * @param modelId    - The model ID (e.g. `'claude-opus-4-5'`).
     * @param provider   - Optional provider instance for self-declared capabilities.
     * @returns A fully-resolved, immutable `ProviderCapability`.
     */
    getCapability(providerId, modelId, provider) {
        const key = `${providerId}::${modelId}`;
        const cached = this.cache.get(key);
        if (cached)
            return cached;
        const resolved = this._resolve(providerId, modelId, provider);
        this.cache.set(key, resolved);
        return resolved;
    }
    /**
     * Invalidate all cached capability records.
     * Call after dynamic provider registration or model discovery.
     */
    invalidate() {
        this.cache.clear();
    }
    /**
     * Check whether a resolved capability record satisfies a request profile.
     *
     * @param capability - Resolved capability from `getCapability()`.
     * @param request    - The request profile describing requirements.
     * @returns `true` if every requirement in the profile is satisfied.
     */
    canHandle(capability, request) {
        return this._collectRejections(capability, request).length === 0;
    }
    /**
     * Produce a structured routing explanation for a provider/model/request triple.
     * Always returns a complete `RouteExplanation` — never throws.
     *
     * @param providerId - The registered provider name.
     * @param modelId    - The model ID.
     * @param request    - The request profile.
     * @param provider   - Optional provider instance for self-declared capabilities.
     * @returns A `RouteExplanation` with `accepted` flag, rejections, and capability.
     */
    getRouteExplanation(providerId, modelId, request, provider) {
        const capability = this.getCapability(providerId, modelId, provider);
        const rejections = this._collectRejections(capability, request);
        if (rejections.length === 0) {
            return {
                accepted: true,
                providerId,
                modelId,
                summary: `Route accepted: ${providerId}/${modelId} satisfies all request requirements.`,
                capability,
            };
        }
        const reasons = rejections.map((r) => r.reason).join('; ');
        return {
            accepted: false,
            providerId,
            modelId,
            summary: `Route rejected: ${providerId}/${modelId} — ${reasons}`,
            rejections,
            capability,
        };
    }
    // -------------------------------------------------------------------------
    // Private helpers
    // -------------------------------------------------------------------------
    _resolve(providerId, modelId, provider) {
        const providerDefaults = PROVIDER_DEFAULTS[providerId] ?? {};
        const selfDeclared = provider?.capabilities ?? {};
        const modelOverride = MODEL_OVERRIDES[modelId] ?? {};
        // Resolve caching separately via cache-capability module
        const cacheType = getCacheCapability(providerId).type;
        // Merge: GLOBAL_DEFAULTS < provider defaults < self-declared < model overrides
        return Object.freeze({
            ...GLOBAL_DEFAULTS,
            ...providerDefaults,
            ...selfDeclared,
            ...modelOverride,
            // caching always derived from getCacheCapability; not overridable via selfDeclared
            // but can be overridden by MODEL_OVERRIDES if a future model has different caching
            caching: modelOverride.caching ?? cacheType,
        });
    }
    _collectRejections(capability, request) {
        const rejections = [];
        if (request.requiresStreaming === true && !capability.streaming) {
            rejections.push({
                code: RouteRejectionCode.NO_STREAMING,
                reason: 'Provider does not support streaming responses',
                actual: capability.streaming,
                required: true,
            });
        }
        if (request.requiresToolCalling === true && !capability.toolCalling) {
            rejections.push({
                code: RouteRejectionCode.NO_TOOL_CALLING,
                reason: 'Model does not support tool/function calling',
                actual: capability.toolCalling,
                required: true,
            });
        }
        if (request.requiresParallelTools === true && !capability.parallelTools) {
            rejections.push({
                code: RouteRejectionCode.NO_PARALLEL_TOOLS,
                reason: 'Model does not support parallel tool execution',
                actual: capability.parallelTools,
                required: true,
            });
        }
        if (request.requiresJsonMode === true && !capability.jsonMode) {
            rejections.push({
                code: RouteRejectionCode.NO_JSON_MODE,
                reason: 'Provider does not support JSON mode / structured output',
                actual: capability.jsonMode,
                required: true,
            });
        }
        if (request.requiresReasoningControls === true && !capability.reasoningControls) {
            rejections.push({
                code: RouteRejectionCode.NO_REASONING_CONTROLS,
                reason: 'Model does not expose reasoning effort or budget controls',
                actual: capability.reasoningControls,
                required: true,
            });
        }
        if (request.minContextTokens !== undefined &&
            capability.maxContextTokens < request.minContextTokens) {
            rejections.push({
                code: RouteRejectionCode.CONTEXT_TOO_SMALL,
                reason: `Context window (${capability.maxContextTokens.toLocaleString()} tokens) is smaller than the required minimum (${request.minContextTokens.toLocaleString()} tokens)`,
                actual: capability.maxContextTokens,
                required: request.minContextTokens,
            });
        }
        if (request.minOutputTokens !== undefined &&
            capability.maxOutputTokens < request.minOutputTokens) {
            rejections.push({
                code: RouteRejectionCode.OUTPUT_TOO_SMALL,
                reason: `Output capacity (${capability.maxOutputTokens.toLocaleString()} tokens) is smaller than the required minimum (${request.minOutputTokens.toLocaleString()} tokens)`,
                actual: capability.maxOutputTokens,
                required: request.minOutputTokens,
            });
        }
        return rejections;
    }
}
