package com.correx.core.inference

import com.correx.core.events.types.StageId

/**
 * Pure selection function — no I/O, no side effects.
 * Operates on a snapshot of available providers.
 */
fun interface RoutingStrategy {
    /**
     * @throws NoEligibleProviderException if no candidate satisfies all required capabilities
     */
    fun select(
        candidates: List<InferenceProvider>,
        requiredCapabilities: Set<ModelCapability>,
    ): InferenceProvider
}

interface InferenceRouter {
    /**
     * Resolves and selects a provider for the given stage.
     * Performs a live health check and filters [ProviderHealth.Unavailable] providers
     * before selection, so callers never receive a provider that is known-down at routing time.
     * @throws NoEligibleProviderException if no healthy provider satisfies requirements
     */
    suspend fun route(
        stageId: StageId,
        requiredCapabilities: Set<ModelCapability>,
    ): InferenceProvider

    /**
     * Resolves and selects a provider for a stage, optionally pinned to a specific model id.
     * Implementations that own model lifecycle (e.g. a managed router) use [modelId] to select
     * and load the target model before routing. The default implementation ignores [modelId] and
     * falls back to capability-based selection, so static routers remain unaffected.
     */
    suspend fun route(
        stageId: StageId,
        requiredCapabilities: Set<ModelCapability>,
        modelId: String?,
    ): InferenceProvider = route(stageId, requiredCapabilities)

    /**
     * Event-driven health gate: called the moment a connection-level failure is observed on
     * [providerId] (e.g. mid-request connection drop), so the NEXT route() call sees it as
     * unavailable immediately instead of waiting for the next periodic health poll/cache TTL to
     * catch up. Default no-op for routers that don't cache health.
     */
    suspend fun reportFailure(providerId: com.correx.core.events.types.ProviderId, reason: String) = Unit
}

class NoEligibleProviderException(
    stageId: StageId,
    required: Set<ModelCapability>,
) : Exception(
    "No provider satisfies capabilities $required for stage '${stageId.value}'",
)

class ProviderUnavailableException(
    providerId: com.correx.core.events.types.ProviderId,
    reason: String,
) : Exception(
    "Provider '${providerId.value}' is unavailable: $reason",
)
