import Foundation
import NucleicProtocol

/// The per-model effort capability rules, extracted from the app-layer `ModelCatalog` so the
/// Intelligence router (and its tests) can live in NucleicCore. `ModelCatalog` keeps thin
/// delegating wrappers, so every existing call site — and the wire catalog projection — is
/// unchanged. First slice of the catalog-by-BackendID refactor flagged in
/// docs/GROK_ADAPTER.md §"Known design debt".
public enum EffortLadder {
    /// Ordinary effort levels, ordered lowest → highest. A given model supports a *prefix*
    /// of these (see `efforts(for:codexPro:)`).
    public static let efforts: [String] = ["low", "medium", "high", "xhigh", "max"]
    /// Codex's wire value for the Pro-only GPT-5.6 Sol mode (displayed "Pro").
    public static let proEffort = "ultra"
    /// The single "Auto" reasoning mode used by the ACP wrapper agents: the agent picks its
    /// own thinking depth, so the only API-level choice is `auto` (cosmetic — never sent).
    public static let autoEfforts: [String] = ["auto"]
    /// Effort when nothing else is stored or supported (mirrored by
    /// `ModelCatalog.fallbackEffort`).
    public static let fallbackEffort = "high"

    /// The backends whose agents expose only the single "Auto" reasoning — the ACP wrapper
    /// agents, which don't take a `reasoning_effort`-style flag.
    public static let autoReasoningBackends: Set<BackendID> = [
        .grok, .opencode, .openclaw, .hermes, .cursorAgent, .acp,
    ]

    /// The effort levels `sku` actually supports for this account. ACP wrappers expose only
    /// "Auto"; GPT-5.6 models support "max"; a ChatGPT Pro account additionally gets Sol's
    /// `ultra` wire mode; older Codex models top out at "xhigh".
    public static func efforts(for sku: String, codexPro: Bool = false) -> [String] {
        if let backend = BackendID.forModel(sku), autoReasoningBackends.contains(backend) {
            return autoEfforts
        }
        if sku == "gpt-5.6-sol", codexPro {
            return efforts + [proEffort]
        }
        guard let cap = effortCap(for: sku), let idx = efforts.firstIndex(of: cap) else {
            return efforts
        }
        return Array(efforts[...idx])
    }

    /// Highest effort `sku` supports, or `nil` for the full range.
    private static func effortCap(for sku: String) -> String? {
        if sku.hasPrefix("gpt-5.6-") { return "max" }
        return BackendID.forModel(sku) == .codex ? "xhigh" : nil
    }

    /// Clamp `effort` to what `sku` supports, so switching to a model with a lower cap can't
    /// leave an unsupported level selected. Orchestra is an orchestration mode, not an API
    /// level — valid for every model (it resolves to `xhigh` on the host), so it's preserved.
    public static func clampedEffort(_ effort: String, for sku: String, codexPro: Bool = false) -> String {
        if OrchestrationMode.isOrchestra(effort) { return effort }
        let supported = efforts(for: sku, codexPro: codexPro)
        return supported.contains(effort) ? effort : (supported.last ?? fallbackEffort)
    }
}

/// Intelligence-routing provenance carried on a `Session` — pure transparency for the
/// "why this model" UI. It is created with a routed chat and rewritten when the in-session
/// Intelligence slider changes level; a deliberate manual model pick clears it.
public struct RoutingNote: Sendable, Equatable {
    public var purpose: PromptPurpose
    public var level: IntelligenceLevel
    public var reason: String

    public init(purpose: PromptPurpose, level: IntelligenceLevel, reason: String) {
        self.purpose = purpose
        self.level = level
        self.reason = reason
    }

    public init(_ resolution: IntelligenceRouter.Resolution) {
        self.init(
            purpose: resolution.purpose, level: resolution.level, reason: resolution.reason)
    }
}

/// Purpose × level → concrete (model, effort). The routing knowledge is a hand-encoded
/// matrix grounded in July-2026 model research (see the plan's research table and the
/// per-cell comments): Opus 5 peaks at *medium* effort on routine coding and never exceeds
/// `high`; GPT-5.6 Sol owns the backend lane at high+; Fable 5 appears only at Deep/Max on
/// the lanes that genuinely reward long-horizon reasoning; Haiku/Luna only where a light
/// model can't hurt. Every cell carries a Claude-lane and a GPT-lane candidate so a
/// single-provider user always resolves; `IntelligenceRoutingTests` enforces the
/// invariants (tier monotonicity, per-model caps, valid efforts for every connected set).
public enum IntelligenceRouter {
    /// One ranked routing option: a SKU, its effort, and whether a ChatGPT Pro account
    /// upgrades the effort to Sol's `ultra` mode at Max level.
    public struct Candidate: Sendable, Equatable {
        public var sku: String
        public var effort: String
        public var ultraEligible: Bool

        public init(_ sku: String, _ effort: String, ultraEligible: Bool = false) {
            self.sku = sku
            self.effort = effort
            self.ultraEligible = ultraEligible
        }
    }

    /// Live quota posture applied on top of the purpose ranking. Reached provider-wide
    /// windows remove a whole lane; Claude model-family windows remove only that family so
    /// another Claude model can still be the nearest usable match. Sub-limit utilization is
    /// retained too: the router uses remaining capacity to break close performance calls
    /// before a window is fully exhausted.
    public struct Limits: Sendable, Equatable {
        public var providers: Set<BackendID>
        public var models: Set<String>
        /// Percent consumed in each provider-wide quota window, normalized to 0...100.
        public var providerUtilization: [BackendID: Double]
        /// Percent consumed in a model-family quota window, normalized to 0...100.
        public var modelUtilization: [String: Double]

        public init(
            providers: Set<BackendID> = [],
            models: Set<String> = [],
            providerUtilization: [BackendID: Double] = [:],
            modelUtilization: [String: Double] = [:]
        ) {
            self.providers = providers
            self.models = models
            self.providerUtilization = providerUtilization.mapValues(Self.clampedUtilization)
            self.modelUtilization = modelUtilization.mapValues(Self.clampedUtilization)
        }

        public init(
            claudeUsage: SubscriptionUsage?, codexUsage: CodexUsage?,
            claudeRateLimit: RateLimit?, now: Date
        ) {
            var providers = Set<BackendID>()
            var models = Set<String>()
            var providerUtilization = [BackendID: Double]()
            var modelUtilization = [String: Double]()
            let claudeWindows = [
                claudeUsage?.fiveHour, claudeUsage?.sevenDay,
                claudeUsage?.sevenDayOpus, claudeUsage?.sevenDaySonnet,
            ]
            let hasPreciseClaudeUsage = claudeWindows.contains { $0 != nil }
            func utilization(_ window: UsageWindow?) -> Double? {
                window.map { Self.clampedUtilization($0.utilization(at: now)) }
            }
            func reached(_ window: UsageWindow?) -> Bool {
                utilization(window).map { $0 >= 100 } ?? false
            }

            let claudeProviderUsage = [
                utilization(claudeUsage?.fiveHour),
                utilization(claudeUsage?.sevenDay),
            ].compactMap { $0 }.max()
            if let claudeProviderUsage {
                providerUtilization[.claudeCode] = claudeProviderUsage
            }
            if let codexProviderUsage = codexUsage?.weekly.map({
                Self.clampedUtilization($0.utilization(at: now))
            }) {
                providerUtilization[.codex] = codexProviderUsage
            }

            if reached(claudeUsage?.fiveHour) || reached(claudeUsage?.sevenDay) {
                providers.insert(.claudeCode)
            } else if !hasPreciseClaudeUsage,
                      claudeRateLimit?.status == "rejected",
                      claudeRateLimit?.resetsAt.map({ now < $0 }) ?? true {
                // The stream event is a coarse Claude-only fallback when the richer OAuth
                // snapshot is unavailable.
                providers.insert(.claudeCode)
            }
            if reached(claudeUsage?.sevenDayOpus) {
                models.formUnion(IntelligenceRouter.routedModels.filter { $0.hasPrefix("claude-opus") })
            }
            if reached(claudeUsage?.sevenDaySonnet) {
                models.formUnion(IntelligenceRouter.routedModels.filter { $0.hasPrefix("claude-sonnet") })
            }
            if let opusUsage = utilization(claudeUsage?.sevenDayOpus) {
                for sku in IntelligenceRouter.routedModels where sku.hasPrefix("claude-opus") {
                    modelUtilization[sku] = opusUsage
                }
            }
            if let sonnetUsage = utilization(claudeUsage?.sevenDaySonnet) {
                for sku in IntelligenceRouter.routedModels where sku.hasPrefix("claude-sonnet") {
                    modelUtilization[sku] = sonnetUsage
                }
            }
            if providerUtilization[.codex] ?? 0 >= 100 {
                providers.insert(.codex)
            }
            self.init(
                providers: providers,
                models: models,
                providerUtilization: providerUtilization,
                modelUtilization: modelUtilization)
        }

        public func contains(model sku: String) -> Bool {
            if models.contains(sku) || modelUtilization[sku] ?? 0 >= 100 { return true }
            guard let backend = BackendID.forModel(sku) else { return false }
            let normalized = backend == .codexExec ? BackendID.codex : backend
            return providers.contains(normalized)
                || providerUtilization[normalized] ?? 0 >= 100
        }

        /// Remaining quota capacity for a model, from 0 (exhausted) to 1 (unused). The most
        /// constrained applicable window wins. Nil means that provider reports no quantitative
        /// quota, which the score treats neutrally rather than guessing that it is full or empty.
        public func availableCapacity(forModel sku: String) -> Double? {
            guard let backend = BackendID.forModel(sku) else { return nil }
            let normalized = backend == .codexExec ? BackendID.codex : backend
            let utilization = [
                providerUtilization[normalized],
                modelUtilization[sku],
            ].compactMap { $0 }.max()
            return utilization.map { (100 - Self.clampedUtilization($0)) / 100 }
        }

        private static func clampedUtilization(_ utilization: Double) -> Double {
            min(100, max(0, utilization))
        }
    }

    /// What the router decided and why — the composer passes `model`/`effort` straight into
    /// the existing start path (concrete strings; downstream plumbing unchanged), and the
    /// resolved chip + session routing note surface `reason`.
    public struct Resolution: Sendable, Equatable {
        public var model: String
        public var effort: String
        public var purpose: PromptPurpose
        public var level: IntelligenceLevel
        public var reason: String
        public var isAvailable: Bool

        public init(
            model: String, effort: String, purpose: PromptPurpose,
            level: IntelligenceLevel, reason: String, isAvailable: Bool = true
        ) {
            self.model = model
            self.effort = effort
            self.purpose = purpose
            self.level = level
            self.reason = reason
            self.isAvailable = isAvailable
        }
    }

    /// Cost tiers for the "a misroute can never cross more than one tier" test invariant:
    /// T0 haiku/luna · T1 terra/sonnet · T2 opus-5/sol · T3 fable. Unknown SKUs rank T2 so
    /// a future addition fails toward caution in the tests, not silently into the cheap bin.
    public static func costTier(of sku: String) -> Int {
        if sku.hasPrefix("claude-haiku") || sku == "gpt-5.6-luna" { return 0 }
        if sku.hasPrefix("claude-sonnet") || sku == "gpt-5.6-terra" { return 1 }
        if sku.hasPrefix("claude-fable") { return 3 }
        return 2
    }

    /// The routing matrix. Per cell: ranked candidates, preferred lane first. Claude lane
    /// uses sonnet-5/opus-5/fable-5/haiku-4-5; GPT lane uses luna/terra/sol. Research notes
    /// inline where a cell is deliberately *not* the obvious escalation.
    static let matrix: [PromptPurpose: [IntelligenceLevel: [Candidate]]] = [
        .planning: [
            .quick: [.init("claude-sonnet-5", "medium"), .init("gpt-5.6-terra", "medium")],
            .light: [.init("claude-opus-5", "medium"), .init("gpt-5.6-terra", "high")],
            .balanced: [.init("claude-opus-5", "high"), .init("gpt-5.6-sol", "high")],
            .deep: [.init("claude-fable-5", "high"), .init("gpt-5.6-sol", "xhigh")],
            // Fable is the long-horizon planning specialist; max effort is reserved for it.
            .max: [.init("claude-fable-5", "max"), .init("gpt-5.6-sol", "xhigh", ultraEligible: true)],
        ],
        .backendImpl: [
            .quick: [.init("claude-sonnet-5", "low"), .init("gpt-5.6-luna", "medium")],
            .light: [.init("claude-sonnet-5", "medium"), .init("gpt-5.6-terra", "medium")],
            // Sol leads the backend lane from Balanced up — it rewards high+ effort on
            // implementation where Opus 5 would already be past its medium-effort peak.
            .balanced: [.init("gpt-5.6-sol", "high"), .init("claude-opus-5", "medium")],
            .deep: [.init("gpt-5.6-sol", "xhigh"), .init("claude-fable-5", "high")],
            .max: [.init("gpt-5.6-sol", "xhigh", ultraEligible: true), .init("claude-fable-5", "high")],
        ],
        .frontendImpl: [
            .quick: [.init("claude-sonnet-5", "low"), .init("gpt-5.6-luna", "medium")],
            .light: [.init("claude-sonnet-5", "medium"), .init("gpt-5.6-terra", "medium")],
            // Opus 5 is the consensus UI pick — at medium, its measured peak for routine work.
            .balanced: [.init("claude-opus-5", "medium"), .init("gpt-5.6-terra", "high")],
            .deep: [.init("claude-opus-5", "high"), .init("gpt-5.6-sol", "high")],
            .max: [.init("claude-fable-5", "high"), .init("gpt-5.6-sol", "xhigh")],
        ],
        .quickFix: [
            .quick: [.init("gpt-5.6-luna", "low"), .init("claude-sonnet-5", "low")],
            .light: [.init("claude-sonnet-5", "low"), .init("gpt-5.6-luna", "medium")],
            .balanced: [.init("claude-sonnet-5", "medium"), .init("gpt-5.6-terra", "medium")],
            // Deep tops out at Opus·medium (its measured peak) — a "deep" quick fix wants
            // care, not scope; and it keeps this row within one cost tier of the heavy rows
            // a borderline classification could have landed on (the drift invariant).
            .deep: [.init("claude-opus-5", "medium"), .init("gpt-5.6-terra", "high")],
            // Even "Max" on a quick fix stops at Opus·medium — more model would only invite
            // out-of-scope refactors (the measured Opus-past-medium failure mode).
            .max: [.init("claude-opus-5", "medium"), .init("gpt-5.6-sol", "high")],
        ],
        .refactor: [
            .quick: [.init("claude-sonnet-5", "low"), .init("gpt-5.6-luna", "medium")],
            .light: [.init("claude-sonnet-5", "medium"), .init("gpt-5.6-terra", "medium")],
            .balanced: [.init("claude-sonnet-5", "high"), .init("gpt-5.6-terra", "high")],
            .deep: [.init("claude-opus-5", "medium"), .init("gpt-5.6-sol", "high")],
            .max: [.init("claude-fable-5", "high"), .init("gpt-5.6-sol", "xhigh")],
        ],
        .debugging: [
            .quick: [.init("claude-sonnet-5", "medium"), .init("gpt-5.6-terra", "medium")],
            .light: [.init("claude-sonnet-5", "high"), .init("gpt-5.6-terra", "high")],
            .balanced: [.init("claude-opus-5", "high"), .init("gpt-5.6-sol", "high")],
            .deep: [.init("claude-fable-5", "high"), .init("gpt-5.6-sol", "xhigh")],
            // Root-causing gnarly failures is the other lane that genuinely rewards Fable max.
            .max: [.init("claude-fable-5", "max"), .init("gpt-5.6-sol", "xhigh", ultraEligible: true)],
        ],
        .review: [
            .quick: [.init("claude-haiku-4-5", "low"), .init("gpt-5.6-luna", "low")],
            .light: [.init("claude-sonnet-5", "low"), .init("gpt-5.6-terra", "medium")],
            .balanced: [.init("claude-sonnet-5", "medium"), .init("gpt-5.6-terra", "high")],
            .deep: [.init("claude-opus-5", "high"), .init("gpt-5.6-sol", "high")],
            .max: [.init("claude-opus-5", "high"), .init("gpt-5.6-sol", "xhigh")],
        ],
        .writing: [
            .quick: [.init("claude-haiku-4-5", "low"), .init("gpt-5.6-luna", "low")],
            .light: [.init("claude-sonnet-5", "low"), .init("gpt-5.6-luna", "medium")],
            // Sonnet/Terra (not the bargain tier) from Balanced up: prose prompts routinely
            // border on backend/docs mixes, so the row stays within one cost tier of the
            // implementation rows a borderline classification could have meant (the drift
            // invariant) — and serious writing genuinely reads better off the mid tier.
            .balanced: [.init("claude-sonnet-5", "medium"), .init("gpt-5.6-terra", "medium")],
            .deep: [.init("claude-opus-5", "medium"), .init("gpt-5.6-terra", "high")],
            .max: [.init("claude-opus-5", "medium"), .init("gpt-5.6-terra", "high")],
        ],
        // The conservative row every failed classification lands on: mid-tier at every stop,
        // so "we couldn't tell" can never mean Fable-at-max or Haiku-on-a-hard-task.
        .general: [
            .quick: [.init("claude-sonnet-5", "low"), .init("gpt-5.6-luna", "medium")],
            .light: [.init("claude-sonnet-5", "medium"), .init("gpt-5.6-terra", "medium")],
            .balanced: [.init("claude-opus-5", "medium"), .init("gpt-5.6-terra", "high")],
            .deep: [.init("claude-opus-5", "high"), .init("gpt-5.6-sol", "high")],
            .max: [.init("claude-fable-5", "high"), .init("gpt-5.6-sol", "xhigh")],
        ],
    ]

    /// Every SKU the matrix can produce. Kept derived from the matrix so quota-family
    /// exclusions automatically cover new cells without a second catalog to maintain.
    static var routedModels: Set<String> {
        Set(matrix.values.flatMap { $0.values }.flatMap { $0 }.map(\.sku))
    }

    /// The requested cell is the primary ranking. If quota removes all of its usable
    /// candidates, walk lower levels of the same purpose from nearest to cheapest before
    /// considering higher levels. This preserves the user budget whenever possible while
    /// still providing a same-purpose match for a single-provider account.
    private static func rankedCandidates(
        purpose: PromptPurpose, level: IntelligenceLevel
    ) -> [Candidate] {
        let row = matrix[purpose] ?? matrix[.general] ?? [:]
        let lower = IntelligenceLevel.allCases
            .filter { $0 <= level }
            .sorted { $0.rawValue > $1.rawValue }
        let higher = IntelligenceLevel.allCases
            .filter { $0 > level }
            .sorted { $0.rawValue < $1.rawValue }
        var seen = Set<String>()
        return (lower + higher).flatMap { row[$0] ?? [] }.filter { seen.insert($0.sku).inserted }
    }

    /// One-line research rationale per model family, composed into `Resolution.reason` so
    /// the chip tooltip explains *why* this model, not just which.
    static func rationale(for sku: String, effort: String) -> String {
        if sku.hasPrefix("claude-fable") {
            return "long-horizon specialist — worth its cost on genuinely complex work"
        }
        if sku.hasPrefix("claude-opus-5") {
            return effort == "medium"
                ? "consensus quality pick at its measured peak effort"
                : "near-flagship quality at half the flagship price"
        }
        if sku == "gpt-5.6-sol" { return "strongest implementation lane at high effort" }
        if sku == "gpt-5.6-terra" { return "balanced mid-tier for scoped work" }
        if sku == "gpt-5.6-luna" { return "fast lane for clear, contained tasks" }
        if sku.hasPrefix("claude-sonnet") { return "balanced daily driver" }
        if sku.hasPrefix("claude-haiku") { return "light model — plenty for prose and lookups" }
        return "closest available match"
    }

    /// Combined task-fit/capacity score. Matrix rank supplies the performance signal, and
    /// remaining quota can overturn only a close call: performance owns 85% of the score,
    /// capacity 15%, with each rank step costing 10% of task fit. A first-ranked Fable gets
    /// a specialist premium large enough that even an empty competing provider cannot displace
    /// it — those are the matrix cells where long-horizon quality is the point of the level.
    private static func worthwhileScore(
        for candidate: Candidate, rank: Int, limits: Limits
    ) -> Double {
        let specialistPremium =
            rank == 0 && candidate.sku.hasPrefix("claude-fable") ? 0.10 : 0
        let taskFit = max(0, 1 - (Double(rank) * 0.10)) + specialistPremium
        let availableCapacity = limits.availableCapacity(forModel: candidate.sku) ?? 0.5
        return (taskFit * 0.85) + (availableCapacity * 0.15)
    }

    /// Pick the (model, effort) for `purpose` at `level`.
    ///
    /// - `connected`: backends the user can actually run (`ProviderAvailability`).
    /// - `pinned`: the preferred Settings provider. It remains strict while usable, but
    ///   is temporarily bypassed when quota blocks every candidate on that provider.
    /// - `backendLock`: in-session routing. The fixed backend cannot be bypassed, but a
    ///   model-family limit may choose the nearest model on that same backend.
    /// - `degraded`: active provider incidents are demoted, never eliminated.
    /// - `limits`: reached provider or model-family quota windows are hard exclusions.
    /// - `fallback`: the resolved app default used when routing has no catalog candidate.
    public static func route(
        purpose: PromptPurpose,
        level: IntelligenceLevel,
        connected: Set<BackendID>,
        pinned: BackendID? = nil,
        backendLock: BackendID? = nil,
        degraded: Set<BackendID> = [],
        limits: Limits = Limits(),
        codexPro: Bool = false,
        fallback: (model: String, effort: String)
    ) -> Resolution {
        let ranked = rankedCandidates(purpose: purpose, level: level)
        func lane(_ backend: BackendID) -> BackendID {
            backend == .codexExec ? .codex : backend
        }
        func candidateLane(_ candidate: Candidate) -> BackendID? {
            BackendID.forModel(candidate.sku).map(lane)
        }

        var reachable = Set(connected.map(lane))
        if let lock = backendLock { reachable.insert(lane(lock)) }
        let lockedLane = backendLock.map(lane)
        func structurallyEligible(_ candidate: Candidate) -> Bool {
            guard let backend = candidateLane(candidate), reachable.contains(backend) else { return false }
            return lockedLane == nil || backend == lockedLane
        }

        let quotaEligible = ranked.filter {
            structurallyEligible($0) && !limits.contains(model: $0.sku)
        }
        var pinBypassedForLimit = false
        var eligible: [Candidate]
        if let pinnedLane = pinned.map(lane) {
            let onPin = quotaEligible.filter { candidateLane($0) == pinnedLane }
            let limitedOnPin = ranked.contains {
                structurallyEligible($0) && candidateLane($0) == pinnedLane
                    && limits.contains(model: $0.sku)
            }
            if !onPin.isEmpty {
                eligible = onPin
            } else if limitedOnPin, lockedLane == nil {
                // A pin expresses preference, not a request to launch against a provider
                // known to be rejecting work. Restore it automatically after the reset.
                eligible = quotaEligible
                pinBypassedForLimit = true
            } else {
                eligible = []
            }
        } else {
            eligible = quotaEligible
        }

        // Demote (never drop) degraded lanes after quota filtering. A healthy alternative
        // wins, but if all remaining lanes have incidents the purpose ranking stands.
        var demoted = false
        let healthy = eligible.filter { candidate in
            candidateLane(candidate).map { !degraded.map(lane).contains($0) } ?? false
        }
        if !healthy.isEmpty, healthy.count < eligible.count {
            demoted = true
            eligible = healthy
        }

        guard let unbalancedPick = eligible.first else {
            let clamped = EffortLadder.clampedEffort(
                fallback.effort, for: fallback.model, codexPro: codexPro)
            let quotaBlockedRoute = limits.contains(model: fallback.model) || ranked.contains {
                structurallyEligible($0) && limits.contains(model: $0.sku)
            }
            let reason = quotaBlockedRoute
                ? "\(purpose.displayName) · \(level.displayName) — every compatible model is at its usage limit"
                : "\(purpose.displayName) · \(level.displayName) — no routed candidate on a connected provider, using your default"
            return Resolution(
                model: fallback.model, effort: clamped, purpose: purpose, level: level,
                reason: reason, isAvailable: !quotaBlockedRoute)
        }

        // Preserve matrix order on exact ties. Capacity is deliberately only a bounded
        // adjustment to the research-derived performance ranking, not a replacement for it.
        let pick = eligible.sorted { lhs, rhs in
            let lhsRank = ranked.firstIndex(of: lhs) ?? ranked.count
            let rhsRank = ranked.firstIndex(of: rhs) ?? ranked.count
            let lhsScore = worthwhileScore(for: lhs, rank: lhsRank, limits: limits)
            let rhsScore = worthwhileScore(for: rhs, rank: rhsRank, limits: limits)
            if abs(lhsScore - rhsScore) > 0.000_001 { return lhsScore > rhsScore }
            return lhsRank < rhsRank
        }.first ?? unbalancedPick
        let capacityRebalanced = pick != unbalancedPick

        var effort = pick.effort
        if pick.ultraEligible, codexPro, level == .max {
            effort = EffortLadder.proEffort
        }
        effort = EffortLadder.clampedEffort(effort, for: pick.sku, codexPro: codexPro)

        let selectedIndex = ranked.firstIndex(of: pick) ?? 0
        let preferredWasLimited = ranked[..<selectedIndex].contains {
            structurallyEligible($0) && limits.contains(model: $0.sku)
        }
        var reason = "\(purpose.displayName) · \(level.displayName) — \(rationale(for: pick.sku, effort: effort))"
        if pinBypassedForLimit {
            reason += " (pinned provider is at its usage limit; using the nearest available provider)"
        } else if preferredWasLimited {
            reason += " (preferred route is at its usage limit; using the nearest available match)"
        }
        if capacityRebalanced {
            reason += " (close performance call favored the provider with more available quota)"
        }
        if demoted { reason += " (preferred lane has a provider incident)" }
        return Resolution(
            model: pick.sku, effort: effort, purpose: purpose, level: level, reason: reason)
    }

    /// Route a prompt the deep classifier identifies as genuinely mixed-intent. The
    /// primary purpose chooses the route, raised when necessary to the secondary's cost
    /// tier at the same user-selected level.
    public static func routeMixed(
        primary: PromptPurpose,
        secondary: PromptPurpose,
        level: IntelligenceLevel,
        connected: Set<BackendID>,
        pinned: BackendID? = nil,
        backendLock: BackendID? = nil,
        degraded: Set<BackendID> = [],
        limits: Limits = Limits(),
        codexPro: Bool = false,
        fallback: (model: String, effort: String)
    ) -> Resolution {
        let primaryRoute = route(
            purpose: primary, level: level, connected: connected, pinned: pinned,
            backendLock: backendLock, degraded: degraded, limits: limits,
            codexPro: codexPro, fallback: fallback)
        guard primary != secondary else { return primaryRoute }
        let secondaryRoute = route(
            purpose: secondary, level: level, connected: connected, pinned: pinned,
            backendLock: backendLock, degraded: degraded, limits: limits,
            codexPro: codexPro, fallback: fallback)
        guard primaryRoute.isAvailable, secondaryRoute.isAvailable else { return primaryRoute }

        let secondaryFloor = costTier(of: secondaryRoute.model)
        guard costTier(of: primaryRoute.model) < secondaryFloor else {
            var result = primaryRoute
            result.reason += " (mixed with \(secondary.displayName); primary route already meets its tier)"
            return result
        }

        for candidateLevel in IntelligenceLevel.allCases where candidateLevel >= level {
            var candidate = route(
                purpose: primary, level: candidateLevel, connected: connected, pinned: pinned,
                backendLock: backendLock, degraded: degraded, limits: limits,
                codexPro: codexPro, fallback: fallback)
            guard candidate.isAvailable, costTier(of: candidate.model) >= secondaryFloor else {
                continue
            }
            // The slider remains the user's budget dial. Difficulty never changes it.
            candidate.level = level
            candidate.reason =
                "\(primary.displayName) + \(secondary.displayName) · \(level.displayName) — "
                + "primary route raised to the secondary intent's cost tier; "
                + rationale(for: candidate.model, effort: candidate.effort)
            return candidate
        }

        // Fail safe if a future matrix leaves the primary row without the required tier.
        var result = secondaryRoute
        result.purpose = primary
        result.reason =
            "\(primary.displayName) + \(secondary.displayName) · \(level.displayName) — "
            + "secondary route supplies the mixed-intent cost floor"
        return result
    }
}
