// AIRouter.swift — 태스크 → provider 라우팅 정책 (3단 fallback: explicit > rule > error)
//
// 정책 = feedback_task_routing_hybrid + feedback_no_silent_fallback_explicit_opt_in:
//   1) 명시 provider 지정 → 그것만, 불가 시 **에러**(자동 fallback 금지).
//   2) 미지정 → 태스크별 선호 체인(rule), 정의된 fallback 은 routingNote 로 가시화.
//   3) 전부 불가 → noProviderAvailable.

import Foundation

public struct AIRoutingPolicy: Sendable {
    /// 태스크별 provider 선호 순서. 앞쪽 우선, 불가/실패 시 다음으로(가시 fallback).
    public var preference: [AITask: [AIProviderID]]

    public init(preference: [AITask: [AIProviderID]]) {
        self.preference = preference
    }

    public func chain(for task: AITask) -> [AIProviderID] {
        preference[task] ?? [.onDevice, .localMLX, .remoteDS]
    }

    /// 기본 정책 — 디바이스 역할 표와 일치.
    /// 빠른/사적 = 온디바이스 우선, 무거운 생성 = 맥미니, 코퍼스 = 원격 DS, 비전 = GPU 특화.
    public static let `default` = AIRoutingPolicy(preference: [
        .quickSummarize: [.onDevice, .localMLX],
        .memoAssist:     [.onDevice, .localMLX],
        .askSelection:   [.onDevice, .localMLX, .remoteDS],
        .corpusAsk:      [.remoteDS],                 // 코퍼스는 원격만 — 온디바이스로 폴백 불가
        .classify:       [.localMLX, .remoteDS, .onDevice],
        .vision:         [.specialized, .onDevice],
    ])
}

public enum AIRoutingError: Error, Sendable {
    case providerNotConfigured(AIProviderID)
    case explicitProviderUnavailable(AIProviderID)   // 명시 지정인데 불가 → 자동 fallback 안 함
    case noProviderAvailable(AITask)
}

public struct AIRouter: Sendable {
    public let providers: [AIProviderID: any AIProvider]
    public let policy: AIRoutingPolicy
    /// 라우팅 로그 훅 (silent skip 방지 — 가시 fallback). 기본은 무시.
    public let log: @Sendable (String) -> Void

    public init(
        providers: [AIProviderID: any AIProvider],
        policy: AIRoutingPolicy = .default,
        log: @escaping @Sendable (String) -> Void = { _ in }
    ) {
        self.providers = providers
        self.policy = policy
        self.log = log
    }

    public func route(_ request: AICompletionRequest) async throws -> AICompletionResponse {
        // 1) 명시 opt-in — 자동 fallback 금지.
        if let explicit = request.explicitProvider {
            guard let provider = providers[explicit] else {
                throw AIRoutingError.providerNotConfigured(explicit)
            }
            guard await provider.isAvailable else {
                log("explicit provider \(explicit.rawValue) unavailable — no fallback (opt-in)")
                throw AIRoutingError.explicitProviderUnavailable(explicit)
            }
            return try await provider.complete(request)
        }

        // 2) rule-based 선호 체인 — 정의된 fallback 은 routingNote 로 가시화.
        let chain = policy.chain(for: request.task)
        var attempted: [AIProviderID] = []
        var lastError: Error?

        for id in chain {
            guard let provider = providers[id] else { continue }
            guard await provider.isAvailable else {
                log("provider \(id.rawValue) unavailable for \(request.task.rawValue) — trying next")
                attempted.append(id)
                continue
            }
            do {
                var response = try await provider.complete(request)
                if !attempted.isEmpty {
                    response.routingNote = "fallback from \(attempted.map(\.rawValue).joined(separator: ",")) → \(id.rawValue)"
                }
                return response
            } catch {
                log("provider \(id.rawValue) failed for \(request.task.rawValue): \(error) — trying next")
                attempted.append(id)
                lastError = error
                continue
            }
        }

        // 3) 전부 불가.
        if let lastError { throw lastError }
        throw AIRoutingError.noProviderAvailable(request.task)
    }
}
