import { getQualityTier, getQualityTierFromScore, compositeScore } from '../../../providers/model-benchmarks.js';
/** Status sort priority (lower = shown first). */
const STATUS_ORDER = {
    healthy: 0,
    unknown: 1,
    degraded: 2,
    rate_limited: 3,
    auth_error: 4,
    unavailable: 5,
};
const FAMILY_PATTERNS = [
    { pattern: /claude/i, family: 'Claude' },
    { pattern: /gpt|\bo1\b|\bo3\b|\bo4\b/i, family: 'GPT' },
    { pattern: /gemini/i, family: 'Gemini' },
    { pattern: /llama/i, family: 'Llama' },
    { pattern: /qwen/i, family: 'Qwen' },
    { pattern: /glm|chatglm/i, family: 'GLM' },
    { pattern: /minimax|abab/i, family: 'MiniMax' },
    { pattern: /deepseek/i, family: 'DeepSeek' },
    { pattern: /mistral|mixtral/i, family: 'Mistral' },
    { pattern: /command|cohere/i, family: 'Command' },
    { pattern: /grok/i, family: 'Grok' },
    { pattern: /kimi|moonshot/i, family: 'Kimi' },
];
function detectFamily(model) {
    const haystack = `${model.id} ${model.displayName}`;
    for (const { pattern, family } of FAMILY_PATTERNS) {
        if (pattern.test(haystack))
            return family;
    }
    return 'Other';
}
function tierToCategoryFilter(tier) {
    if (tier === 'free')
        return 'free';
    if (tier === 'subscription')
        return 'subscription';
    return 'paid';
}
/**
 * Derive ProviderHealthContext from a ProviderHealthRecord.
 * Returns a safe default when the record is absent (provider not yet observed).
 */
function buildHealthContext(record) {
    if (!record) {
        return {
            status: 'unknown',
            isConfigured: false,
        };
    }
    let latency;
    if (record.stats.totalCalls > 0) {
        latency = {
            avgMs: record.stats.avgLatencyMs,
            p95Ms: record.stats.maxLatencyMs,
            minMs: record.stats.minLatencyMs,
        };
    }
    return {
        status: record.status,
        latency,
        cacheHitRate: record.cacheMetrics?.hitRate,
        isConfigured: record.isConfigured,
        rateLimitResetAt: record.rateLimitResetAt,
    };
}
/**
 * Derive CapabilityFlags from a ModelDefinition.
 */
function buildCapabilityFlags(model) {
    return {
        reasoning: model.capabilities?.reasoning ?? false,
        caching: false, // caching capability is provider-level; enriched separately if needed
        toolCalling: model.capabilities?.toolCalling ?? false,
        multimodal: model.capabilities?.multimodal ?? false,
        codeEditing: model.capabilities?.codeEditing ?? false,
    };
}
/**
 * Derive quality tier and benchmark score for a model.
 * Handles synthetic models (catalog-backed composite scores) and standard models.
 */
function buildQualityInfo(model, benchmarkStore, providerRegistry) {
    if (model.provider === 'synthetic') {
        const info = providerRegistry.getSyntheticModelInfoFromCatalog(model.id);
        if (info?.bestCompositeScore != null) {
            return {
                qualityTier: getQualityTierFromScore(info.bestCompositeScore),
                benchmarkScore: info.bestCompositeScore,
            };
        }
        return {};
    }
    const benchmarks = benchmarkStore.getBenchmarks(model.id) ?? benchmarkStore.getBenchmarks(model.displayName);
    if (!benchmarks)
        return {};
    return {
        qualityTier: getQualityTier(benchmarks.benchmarks),
        benchmarkScore: compositeScore(benchmarks.benchmarks) ?? undefined,
    };
}
/**
 * Build the set of registry keys in the current fallback chain.
 * Returns a Map from registryKey to its position (0 = primary).
 */
function buildFallbackPositionMap(modelState) {
    const map = new Map();
    // Position 0 is always the primary active model
    map.set(modelState.registryKey, 0);
    for (let i = 0; i < modelState.fallbackChain.length; i++) {
        const entry = modelState.fallbackChain[i];
        const key = `${entry.providerId}:${entry.modelId}`;
        if (!map.has(key)) {
            map.set(key, i + 1);
        }
    }
    return map;
}
/**
 * Enrich a flat list of ModelDefinitions with health and state data.
 *
 * @param models - All selectable models from the registry.
 * @param healthState - Current provider health domain state.
 * @param modelState - Current model domain state.
 * @param pinnedIds - Set of pinned/favorited model registry keys.
 * @returns Sorted, enriched ModelPickerEntry array.
 */
export function enrichModelEntries(models, healthState, modelState, pinnedIds, benchmarkStore, providerRegistry) {
    const fallbackPositions = buildFallbackPositionMap(modelState);
    const entries = models.map((model) => {
        const record = healthState.providers.get(model.provider);
        const health = buildHealthContext(record);
        const { qualityTier, benchmarkScore } = buildQualityInfo(model, benchmarkStore, providerRegistry);
        const isProviderDegraded = health.status === 'degraded' || health.status === 'rate_limited';
        const isProviderUnavailable = health.status === 'unavailable' || health.status === 'auth_error';
        const fallbackPosition = fallbackPositions.get(model.registryKey);
        // Resolve effective context window and determine display source label.
        const effectiveContextWindow = providerRegistry.getContextWindowForModel(model);
        // Determine source: custom/local providers carry provenance on ModelDefinition;
        // for catalog models, if getContextWindowForModel returned more than the
        // static contextWindow it came from OpenRouter, else it's the registry value.
        let contextWindowSource;
        if (effectiveContextWindow !== model.contextWindow) {
            contextWindowSource = 'openrouter';
        }
        else if (model.contextWindowProvenance) {
            contextWindowSource = model.contextWindowProvenance;
        }
        else {
            contextWindowSource = 'registry';
        }
        return {
            modelId: model.id,
            providerId: model.provider,
            displayName: model.displayName,
            family: detectFamily(model),
            pricingTier: tierToCategoryFilter(model.tier),
            qualityTier,
            benchmarkScore,
            capabilities: buildCapabilityFlags(model),
            health,
            contextWindow: effectiveContextWindow,
            contextWindowSource,
            isPinned: pinnedIds.has(model.registryKey),
            isActive: model.registryKey === modelState.registryKey,
            isProviderDegraded,
            isProviderUnavailable,
            isInFallbackChain: fallbackPosition !== undefined,
            fallbackPosition,
        };
    });
    // Sort: healthy providers first, then degraded, then unavailable.
    // Within each status bucket: pinned first, then active, then by display name.
    entries.sort((a, b) => {
        const statusDiff = (STATUS_ORDER[a.health.status] ?? 1) - (STATUS_ORDER[b.health.status] ?? 1);
        if (statusDiff !== 0)
            return statusDiff;
        // Pinned before unpinned
        if (a.isPinned !== b.isPinned)
            return a.isPinned ? -1 : 1;
        // Active before inactive
        if (a.isActive !== b.isActive)
            return a.isActive ? -1 : 1;
        return a.displayName.localeCompare(b.displayName);
    });
    return entries;
}
/**
 * Group a sorted list of ModelPickerEntry records by their provider ID.
 * Preserves the sort order of entries within each group.
 *
 * @param entries - Pre-sorted enriched entries.
 * @returns Groups ordered by first appearance of each provider in entries.
 */
export function groupEntriesByProvider(entries) {
    const groupMap = new Map();
    const groupOrder = [];
    for (const entry of entries) {
        if (!groupMap.has(entry.providerId)) {
            groupMap.set(entry.providerId, []);
            groupOrder.push(entry.providerId);
        }
        groupMap.get(entry.providerId)?.push(entry);
    }
    return groupOrder.map((providerId) => ({
        label: providerId,
        entries: groupMap.get(providerId) ?? [],
    }));
}
