// AIProvider.swift — S2 인터페이스 동결 (멀티디바이스 LLM 라우팅 패브릭)
//
// 이 파일이 앱(S3)이 코딩하는 "AI 호출 모양"을 동결한다.
// S3 는 이 프로토콜 + MockAIProvider 로 빌드하고, S2 가 실제 provider
// (OnDevice / LocalMLX / RemoteDS / Specialized)를 그 뒤에서 채운다.
//
// Foundation 외 의존 0 → 표준 출발선. 실제 구현(FoundationModels / MLX / URLSession)은
// Providers/ 의 각 스켈레톤에서 S2 가 결선.

import Foundation

// MARK: - Provider 식별 (디바이스 역할 = 인프라 패브릭과 1:1)

public enum AIProviderID: String, Codable, Sendable, CaseIterable {
    /// 맥북·아이폰 온디바이스 (Apple Foundation Models). 즉답·오프라인·프라이버시.
    case onDevice
    /// 맥미니 메인 로컬 LLM 허브 (Gemma 4 26B, MLX :8801 / llm-router :8890). 무거운 생성.
    case localMLX
    /// 원격 DS 코퍼스 RAG (`GET /search/ask`). 전체 지식베이스 질의 — 온디바이스 불가 영역.
    case remoteDS
    /// GPU 특화 통로 (rerank / embed / vision / OCR). 온디맨드.
    case specialized
}

// MARK: - 태스크 분류 (앱이 AI 에 요청하는 일의 종류 → 라우팅 기준)

public enum AITask: String, Codable, Sendable {
    /// 선택 텍스트/문서 빠른 요약.
    case quickSummarize
    /// 메모 정리·제목 생성·태그 제안.
    case memoAssist
    /// 현재 문서/선택에 대한 질문 (로컬 컨텍스트, 코퍼스 불필요).
    case askSelection
    /// 전체 지식베이스 RAG 질의 (코퍼스 필요 → 원격 DS).
    case corpusAsk
    /// 문서 분류 (도메인/카테고리 제안).
    case classify
    /// 이미지/스캔 PDF 이해 (비전).
    case vision
}

public enum AIConfidence: String, Codable, Sendable {
    case high, medium, low
}

public enum AIFinishReason: String, Codable, Sendable {
    case completed      // 정상 완료
    case refused        // 모델이 답변 거부 (근거 부족 등)
    case timeout        // 생성 지연으로 생략
    case unavailable    // provider 사용 불가 (503 계열)
    case noEvidence     // (corpusAsk) 관련 근거 없음
}

// MARK: - 요청 / 응답

public struct AICompletionRequest: Sendable {
    public var task: AITask
    public var prompt: String
    /// 선택 텍스트·문서 본문 등 로컬 컨텍스트 (corpusAsk 는 보통 nil — 코퍼스가 컨텍스트).
    public var context: String?
    public var systemPrompt: String?
    public var maxTokens: Int?
    /// 사용자가 명시적으로 고른 provider. 지정 시 **자동 fallback 금지**(명시 opt-in).
    public var explicitProvider: AIProviderID?

    public init(
        task: AITask,
        prompt: String,
        context: String? = nil,
        systemPrompt: String? = nil,
        maxTokens: Int? = nil,
        explicitProvider: AIProviderID? = nil
    ) {
        self.task = task
        self.prompt = prompt
        self.context = context
        self.systemPrompt = systemPrompt
        self.maxTokens = maxTokens
        self.explicitProvider = explicitProvider
    }
}

/// corpusAsk 응답의 근거 — DS `Citation`(CONTRACT.md §4)에서 매핑.
public struct AICitation: Codable, Sendable, Identifiable {
    public var id: Int { n }
    public var n: Int
    public var docId: Int
    public var title: String?
    public var sectionTitle: String?
    public var spanText: String

    public init(n: Int, docId: Int, title: String?, sectionTitle: String?, spanText: String) {
        self.n = n
        self.docId = docId
        self.title = title
        self.sectionTitle = sectionTitle
        self.spanText = spanText
    }
}

public struct AICompletionResponse: Sendable {
    public var text: String
    public var providerUsed: AIProviderID
    public var finishReason: AIFinishReason
    public var citations: [AICitation]
    public var confidence: AIConfidence?
    public var latencyMs: Double?
    /// 라우팅 가시성 — rule-based fallback 으로 1차 선택이 빗나갔을 때 채워짐(silent 금지).
    public var routingNote: String?

    public init(
        text: String,
        providerUsed: AIProviderID,
        finishReason: AIFinishReason = .completed,
        citations: [AICitation] = [],
        confidence: AIConfidence? = nil,
        latencyMs: Double? = nil,
        routingNote: String? = nil
    ) {
        self.text = text
        self.providerUsed = providerUsed
        self.finishReason = finishReason
        self.citations = citations
        self.confidence = confidence
        self.latencyMs = latencyMs
        self.routingNote = routingNote
    }
}

// MARK: - 프로토콜 (S2 가 구현, S3 가 소비)

public protocol AIProvider: Sendable {
    var id: AIProviderID { get }
    /// 능력 프로브 (온디바이스 Apple Intelligence 가용? 맥미니 도달 가능? 등).
    var isAvailable: Bool { get async }
    func complete(_ request: AICompletionRequest) async throws -> AICompletionResponse
}

public enum AIProviderError: Error, Sendable {
    case notImplemented(AIProviderID)
    case unavailable(AIProviderID)
    case backendError(AIProviderID, status: Int, reason: String?)
}
