import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

public struct AntigravityModelQuota: Sendable {
    public let label: String
    public let modelId: String
    public let remainingFraction: Double?
    public let resetTime: Date?
    public let resetDescription: String?

    public init(
        label: String,
        modelId: String,
        remainingFraction: Double?,
        resetTime: Date?,
        resetDescription: String?)
    {
        self.label = label
        self.modelId = modelId
        self.remainingFraction = remainingFraction
        self.resetTime = resetTime
        self.resetDescription = resetDescription
    }

    public var remainingPercent: Double {
        guard let remainingFraction else { return 0 }
        return max(0, min(100, remainingFraction * 100))
    }
}

private enum AntigravityModelFamily {
    case claudeModels
    case gpt
    case geminiPro
    case geminiFlash
    case unknown
}

private enum AntigravityUsagePool: Hashable {
    case geminiAI
    case claudeGPT

    var id: String {
        switch self {
        case .geminiAI: "antigravity-gemini"
        case .claudeGPT: "antigravity-claude-gpt"
        }
    }

    var title: String {
        switch self {
        case .geminiAI: "Gemini Models"
        case .claudeGPT: "Claude and GPT models"
        }
    }

    var sortRank: Int {
        switch self {
        case .geminiAI: 0
        case .claudeGPT: 1
        }
    }
}

private struct AntigravityModelVersion: Comparable {
    let major: Int
    let minor: Int

    static func < (lhs: AntigravityModelVersion, rhs: AntigravityModelVersion) -> Bool {
        if lhs.major != rhs.major { return lhs.major < rhs.major }
        return lhs.minor < rhs.minor
    }
}

private struct AntigravityNormalizedModel {
    let quota: AntigravityModelQuota
    let family: AntigravityModelFamily
    let selectionPriority: Int?
    let isImage: Bool
    let isLite: Bool
    let isAutocomplete: Bool
    let version: AntigravityModelVersion?
    let tier: Int
}

public enum AntigravityModelQuotaSource: Sendable {
    case local
    case remote
}

public struct AntigravityStatusSnapshot: Sendable {
    public let modelQuotas: [AntigravityModelQuota]
    public let accountEmail: String?
    public let accountPlan: String?
    public let source: AntigravityModelQuotaSource
    let quotaSummary: AntigravityQuotaSummary?

    public init(
        modelQuotas: [AntigravityModelQuota],
        accountEmail: String?,
        accountPlan: String?,
        source: AntigravityModelQuotaSource = .remote)
    {
        self.modelQuotas = modelQuotas
        self.accountEmail = accountEmail
        self.accountPlan = accountPlan
        self.source = source
        self.quotaSummary = nil
    }

    init(
        quotaSummary: AntigravityQuotaSummary,
        accountEmail: String?,
        accountPlan: String?,
        source: AntigravityModelQuotaSource = .local)
    {
        self.modelQuotas = []
        self.accountEmail = accountEmail
        self.accountPlan = accountPlan
        self.source = source
        self.quotaSummary = quotaSummary
    }

    public func toUsageSnapshot() throws -> UsageSnapshot {
        if let quotaSummary {
            return try Self.usageSnapshot(
                from: quotaSummary,
                accountEmail: self.accountEmail,
                accountPlan: self.accountPlan)
        }

        guard !self.modelQuotas.isEmpty else {
            throw AntigravityStatusProbeError.parseFailed("No quota models available")
        }

        let normalized = Self.normalizedModels(self.modelQuotas)
        let summaryCandidates = normalized.filter(Self.isSummaryCandidate)
        let primaryQuota = Self.representative(for: .geminiAI, in: summaryCandidates)
        let secondaryQuota = Self.representative(for: .claudeGPT, in: summaryCandidates)
        let fallbackQuota: AntigravityModelQuota? = if primaryQuota == nil, secondaryQuota == nil {
            switch self.source {
            case .local:
                Self.fallbackRepresentative(in: normalized.filter {
                    $0.family == .unknown &&
                        Self.isSelectableTextModel($0) &&
                        $0.quota.remainingFraction != nil
                })
            case .remote:
                nil
            }
        } else {
            nil
        }

        let primary = primaryQuota.map(Self.rateWindow(for:))
        let secondary = secondaryQuota.map(Self.rateWindow(for:))
        let extraWindows = Self.extraRateWindows(
            from: normalized,
            summaryCandidates: summaryCandidates,
            compactFallbackModelID: fallbackQuota?.modelId,
            representedPools: Set([
                primaryQuota.map { _ in AntigravityUsagePool.geminiAI },
                secondaryQuota.map { _ in AntigravityUsagePool.claudeGPT },
            ].compactMap(\.self)))

        let identity = ProviderIdentitySnapshot(
            providerID: .antigravity,
            accountEmail: self.accountEmail,
            accountOrganization: nil,
            loginMethod: self.accountPlan)
        return UsageSnapshot(
            primary: primary,
            secondary: secondary,
            tertiary: nil,
            extraRateWindows: extraWindows.isEmpty ? nil : extraWindows,
            updatedAt: Date(),
            identity: identity)
    }

    func withIdentity(from snapshot: AntigravityStatusSnapshot?) -> AntigravityStatusSnapshot {
        guard let snapshot else { return self }
        let accountEmail = snapshot.accountEmail ?? self.accountEmail
        let accountPlan = snapshot.accountPlan ?? self.accountPlan
        if let quotaSummary {
            return AntigravityStatusSnapshot(
                quotaSummary: quotaSummary,
                accountEmail: accountEmail,
                accountPlan: accountPlan,
                source: self.source)
        }
        return AntigravityStatusSnapshot(
            modelQuotas: self.modelQuotas,
            accountEmail: accountEmail,
            accountPlan: accountPlan,
            source: self.source)
    }

    private static func usageSnapshot(
        from quotaSummary: AntigravityQuotaSummary,
        accountEmail: String?,
        accountPlan: String?) throws -> UsageSnapshot
    {
        let namedWindows = Self.quotaSummaryWindows(from: quotaSummary)
        guard !namedWindows.isEmpty else {
            throw AntigravityStatusProbeError.parseFailed("No quota buckets available")
        }

        let primary = Self.quotaSummaryRepresentative(
            matching: { $0.lowercased().contains("gemini") },
            in: namedWindows)
        let secondary = Self.quotaSummaryRepresentative(
            matching: { $0.lowercased().contains("claude") || $0.lowercased().contains("gpt") },
            in: namedWindows)

        let identity = ProviderIdentitySnapshot(
            providerID: .antigravity,
            accountEmail: accountEmail,
            accountOrganization: nil,
            loginMethod: accountPlan)
        return UsageSnapshot(
            primary: primary,
            secondary: secondary,
            tertiary: nil,
            extraRateWindows: namedWindows,
            updatedAt: Date(),
            identity: identity)
    }

    private static func quotaSummaryRepresentative(
        matching predicate: (String) -> Bool,
        in windows: [NamedRateWindow]) -> RateWindow?
    {
        windows
            .filter { $0.usageKnown && predicate($0.title) }
            .max { lhs, rhs in
                if lhs.window.usedPercent != rhs.window.usedPercent {
                    return lhs.window.usedPercent < rhs.window.usedPercent
                }
                return lhs.title.localizedCaseInsensitiveCompare(rhs.title) == .orderedDescending
            }?
            .window
    }

    private static func quotaSummaryWindows(from quotaSummary: AntigravityQuotaSummary) -> [NamedRateWindow] {
        let sortedGroups = quotaSummary.groups.enumerated().sorted { lhs, rhs in
            let lhsRank = Self.quotaGroupSortRank(lhs.element)
            let rhsRank = Self.quotaGroupSortRank(rhs.element)
            if lhsRank != rhsRank {
                return lhsRank < rhsRank
            }
            return lhs.offset < rhs.offset
        }.map(\.element)

        return sortedGroups.flatMap { group in
            let groupTitle = Self.displayTitle(forQuotaGroup: group)
            let sortedBuckets = group.buckets.enumerated().sorted { lhs, rhs in
                let lhsRank = Self.quotaBucketSortRank(lhs.element)
                let rhsRank = Self.quotaBucketSortRank(rhs.element)
                if lhsRank != rhsRank {
                    return lhsRank < rhsRank
                }
                return lhs.offset < rhs.offset
            }.map(\.element)

            return sortedBuckets.map { bucket in
                let bucketTitle = Self.displayTitle(forQuotaBucket: bucket)
                let remainingPercent = Self.remainingPercent(from: bucket.remainingFraction)
                let usedPercent = remainingPercent.map { 100 - $0 } ?? 0
                let window = RateWindow(
                    usedPercent: usedPercent,
                    windowMinutes: Self.windowMinutes(forQuotaBucket: bucket),
                    resetsAt: bucket.resetTime,
                    resetDescription: bucket.resetDescription)
                return NamedRateWindow(
                    id: Self.quotaSummaryWindowID(for: bucket),
                    title: "\(groupTitle) \(bucketTitle)",
                    window: window,
                    usageKnown: !bucket.disabled && bucket.remainingFraction != nil)
            }
        }
    }

    static func isQuotaSummaryWindowID(_ id: String) -> Bool {
        id.hasPrefix(self.quotaSummaryWindowIDPrefix)
    }

    private static let quotaSummaryWindowIDPrefix = "antigravity-quota-summary-"

    private static func quotaSummaryWindowID(for bucket: AntigravityQuotaSummaryBucket) -> String {
        self.quotaSummaryWindowIDPrefix + bucket.bucketId
    }

    private static func remainingPercent(from remainingFraction: Double?) -> Double? {
        guard let remainingFraction else { return nil }
        return max(0, min(100, remainingFraction * 100))
    }

    private static func displayTitle(forQuotaGroup group: AntigravityQuotaSummaryGroup) -> String {
        let title = group.displayName.trimmingCharacters(in: .whitespacesAndNewlines)
        let lowercasedTitle = title.lowercased()
        if lowercasedTitle.contains("gemini") {
            return "Gemini"
        }
        if lowercasedTitle.contains("claude") || lowercasedTitle.contains("gpt") {
            return "Claude/GPT"
        }
        return title.isEmpty ? "Quota" : title
    }

    private static func displayTitle(forQuotaBucket bucket: AntigravityQuotaSummaryBucket) -> String {
        switch self.quotaBucketKind(for: bucket) {
        case .session:
            "5-hour"
        case .weekly:
            "weekly"
        case .other:
            bucket.displayName
        }
    }

    private static func windowMinutes(forQuotaBucket bucket: AntigravityQuotaSummaryBucket) -> Int? {
        switch self.quotaBucketKind(for: bucket) {
        case .session:
            300
        case .weekly:
            10080
        case .other:
            nil
        }
    }

    private enum QuotaBucketKind {
        case session
        case weekly
        case other
    }

    private static func quotaGroupSortRank(_ group: AntigravityQuotaSummaryGroup) -> Int {
        let title = group.displayName.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
        if title.contains("gemini") {
            return 0
        }
        if title.contains("claude") || title.contains("gpt") {
            return 1
        }
        return 2
    }

    private static func quotaBucketSortRank(_ bucket: AntigravityQuotaSummaryBucket) -> Int {
        switch self.quotaBucketKind(for: bucket) {
        case .session:
            0
        case .weekly:
            1
        case .other:
            2
        }
    }

    private static func quotaBucketKind(for bucket: AntigravityQuotaSummaryBucket) -> QuotaBucketKind {
        let candidates = Self.quotaCadenceCandidates(for: bucket)
        if !candidates.isDisjoint(with: Self.sessionCadenceAliases) {
            return .session
        }
        if candidates.contains("weekly") {
            return .weekly
        }
        return .other
    }

    private static let sessionCadenceAliases: Set<String> = [
        "session",
        "5h",
        "5-hour",
        "five hour",
        "five-hour",
    ]

    private static func quotaCadenceCandidates(for bucket: AntigravityQuotaSummaryBucket) -> Set<String> {
        var candidates: Set<String> = []
        for rawValue in [bucket.bucketId, bucket.displayName] {
            let normalized = rawValue
                .trimmingCharacters(in: .whitespacesAndNewlines)
                .lowercased()
                .replacingOccurrences(of: "_", with: "-")
            guard !normalized.isEmpty else { continue }

            var normalizedCandidates = [normalized]
            if normalized.hasSuffix(" limit") {
                normalizedCandidates.append(String(normalized.dropLast(" limit".count)))
            }
            for candidate in normalizedCandidates {
                candidates.insert(candidate)
                for alias in Self.sessionCadenceAliases.union(["weekly"])
                    where candidate.hasSuffix("-\(alias)")
                {
                    candidates.insert(alias)
                }
            }
        }
        return candidates
    }

    static func quotaDisplayLabel(_ quota: AntigravityModelQuota) -> String {
        let trimmed = quota.label.trimmingCharacters(in: .whitespacesAndNewlines)
        guard trimmed.isEmpty || trimmed == quota.modelId else { return quota.label }
        return Self.humanizedModelID(quota.modelId)
    }

    static func humanizedModelID(_ modelId: String) -> String {
        let parts = modelId.split(separator: "-").map(String.init)
        var words: [String] = []
        var index = 0

        while index < parts.count {
            let part = parts[index]
            if Self.isSingleDigit(part), index + 1 < parts.count, Self.isSingleDigit(parts[index + 1]) {
                var versionParts = [part]
                index += 1
                while index < parts.count, Self.isSingleDigit(parts[index]) {
                    versionParts.append(parts[index])
                    index += 1
                }
                words.append(versionParts.joined(separator: "."))
                continue
            }

            if part.allSatisfy({ $0.isNumber || $0 == "." }) {
                words.append(part)
            } else if Self.modelLabelAcronyms.contains(part.lowercased()) {
                words.append(part.uppercased())
            } else {
                words.append(part.prefix(1).uppercased() + part.dropFirst())
            }
            index += 1
        }

        return words.joined(separator: " ")
    }

    private static let modelLabelAcronyms: Set<String> = ["ai", "api", "gpt", "oss"]

    private static func isSingleDigit(_ value: String) -> Bool {
        value.count == 1 && value.allSatisfy(\.isNumber)
    }

    private static func rateWindow(for quota: AntigravityModelQuota) -> RateWindow {
        RateWindow(
            usedPercent: 100 - quota.remainingPercent,
            windowMinutes: nil,
            resetsAt: quota.resetTime,
            resetDescription: quota.resetDescription)
    }

    private static func modelOrderPrecedes(
        _ lhs: AntigravityNormalizedModel,
        _ rhs: AntigravityNormalizedModel) -> Bool
    {
        // 1. Family rank: claude=0, geminiPro=1, geminiFlash=2, unknown=3
        let lhsFamilyRank = Self.familyRank(lhs.family)
        let rhsFamilyRank = Self.familyRank(rhs.family)
        if lhsFamilyRank != rhsFamilyRank {
            return lhsFamilyRank < rhsFamilyRank
        }

        // 2. Version descending (newer first); nil version sorts after non-nil
        switch (lhs.version, rhs.version) {
        case let (.some(lv), .some(rv)):
            if lv != rv {
                return lv > rv
            }
        case (.some, .none):
            return true
        case (.none, .some):
            return false
        case (.none, .none):
            break
        }

        // 3. Tier ascending: High(0) < Medium(1) < Low(2)
        if lhs.tier != rhs.tier {
            return lhs.tier < rhs.tier
        }

        // 4. Label tiebreaker
        return lhs.quota.label.localizedCaseInsensitiveCompare(rhs.quota.label) == .orderedAscending
    }

    private static func familyRank(_ family: AntigravityModelFamily) -> Int {
        switch family {
        case .claudeModels: 0
        case .gpt: 1
        case .geminiPro: 2
        case .geminiFlash: 3
        case .unknown: 4
        }
    }

    private static func isSummaryCandidate(_ model: AntigravityNormalizedModel) -> Bool {
        self.usagePool(for: model) != nil && self.isSelectableTextModel(model)
    }

    private static func isSelectableTextModel(_ model: AntigravityNormalizedModel) -> Bool {
        !model.isLite && !model.isAutocomplete && !model.isImage
    }

    private static func normalizedModels(_ models: [AntigravityModelQuota]) -> [AntigravityNormalizedModel] {
        models.map { self.normalizeModel($0) }
    }

    private static func normalizeModel(_ quota: AntigravityModelQuota) -> AntigravityNormalizedModel {
        let modelId = quota.modelId.lowercased()
        let label = quota.label.lowercased()
        let family = Self.family(forModelID: modelId, label: label)

        let isLite = modelId.contains("lite") || label.contains("lite")
        let isAutocomplete = modelId.contains("autocomplete") || label.contains("autocomplete") || modelId
            .hasPrefix("tab_")
        let isImage = modelId.contains("image") || label.contains("image")
        let isSelectableTextModel = !isLite && !isAutocomplete && !isImage
        let isLowPriorityGeminiPro = modelId.contains("pro-low")
            || (label.contains("pro") && label.contains("low"))

        let selectionPriority: Int? = switch family {
        case .claudeModels, .gpt:
            0
        case .geminiPro:
            if isLowPriorityGeminiPro, isSelectableTextModel {
                0
            } else if isSelectableTextModel {
                1
            } else {
                nil
            }
        case .geminiFlash:
            isSelectableTextModel ? 0 : nil
        case .unknown:
            nil
        }

        let version = Self.parseVersion(from: label)
        let tier = Self.parseTier(from: label, modelId: modelId)

        return AntigravityNormalizedModel(
            quota: quota,
            family: family,
            selectionPriority: selectionPriority,
            isImage: isImage,
            isLite: isLite,
            isAutocomplete: isAutocomplete,
            version: version,
            tier: tier)
    }

    private static func parseVersion(from label: String) -> AntigravityModelVersion? {
        // Accept either "." or "-" between major and minor so a raw model id used as the
        // label when displayName is missing (e.g. "gemini-3-1-pro-low") still parses 3.1.
        guard let regex = try? NSRegularExpression(pattern: #"(\d+)(?:[.\-](\d+))?"#) else { return nil }
        let nsLabel = label as NSString
        let range = NSRange(location: 0, length: nsLabel.length)
        guard let match = regex.firstMatch(in: label, options: [], range: range) else { return nil }
        let majorRange = Range(match.range(at: 1), in: label)
        guard let majorRange, let major = Int(label[majorRange]) else { return nil }
        let minor: Int = if match.range(at: 2).location != NSNotFound,
                            let minorRange = Range(match.range(at: 2), in: label),
                            let parsed = Int(label[minorRange])
        {
            parsed
        } else {
            0
        }
        return AntigravityModelVersion(major: major, minor: minor)
    }

    private static func parseTier(from label: String, modelId: String) -> Int {
        let combined = label + " " + modelId
        if combined.contains("high") { return 0 }
        if combined.contains("medium") { return 1 }
        if combined.contains("low") { return 2 }
        return 1
    }

    private static func representative(
        for pool: AntigravityUsagePool,
        in models: [AntigravityNormalizedModel]) -> AntigravityModelQuota?
    {
        let candidates = models.filter {
            Self.usagePool(for: $0) == pool && $0.quota.remainingFraction != nil
        }
        guard !candidates.isEmpty else { return nil }
        return candidates.min { lhs, rhs in
            if lhs.quota.remainingPercent != rhs.quota.remainingPercent {
                return lhs.quota.remainingPercent < rhs.quota.remainingPercent
            }
            switch (lhs.quota.resetTime, rhs.quota.resetTime) {
            case let (.some(left), .some(right)) where left != right:
                return left < right
            case (.some, .none):
                return true
            case (.none, .some):
                return false
            default:
                return lhs.quota.label.localizedCaseInsensitiveCompare(rhs.quota.label) == .orderedAscending
            }
        }?.quota
    }

    private static func fallbackRepresentative(in models: [AntigravityNormalizedModel]) -> AntigravityModelQuota? {
        models.min { lhs, rhs in
            if lhs.quota.remainingPercent != rhs.quota.remainingPercent {
                return lhs.quota.remainingPercent < rhs.quota.remainingPercent
            }
            return lhs.quota.label.localizedCaseInsensitiveCompare(rhs.quota.label) == .orderedAscending
        }?.quota
    }

    private static func extraRateWindows(
        from models: [AntigravityNormalizedModel],
        summaryCandidates: [AntigravityNormalizedModel],
        compactFallbackModelID: String?,
        representedPools: Set<AntigravityUsagePool>) -> [NamedRateWindow]
    {
        let resetOnlyPoolWindows = [AntigravityUsagePool.geminiAI, .claudeGPT].compactMap { pool -> NamedRateWindow? in
            guard !representedPools.contains(pool) else { return nil }
            let candidates = summaryCandidates.filter { Self.usagePool(for: $0) == pool }
            guard let resetOnly = candidates.first(where: { model in
                model.quota.remainingFraction == nil &&
                    (model.quota.resetTime != nil || model.quota.resetDescription != nil)
            }) else {
                return nil
            }
            return NamedRateWindow(
                id: pool.id,
                title: pool.title,
                window: Self.rateWindow(for: resetOnly.quota),
                usageKnown: false)
        }

        let distinctWindows = models
            .filter {
                $0.quota.modelId == compactFallbackModelID || Self.shouldShowDistinctExtraWindow($0)
            }
            .sorted(by: Self.modelOrderPrecedes)
            .map { m in
                NamedRateWindow(
                    id: m.quota.modelId == compactFallbackModelID
                        ? Self.compactFallbackWindowID(modelID: m.quota.modelId)
                        : m.quota.modelId,
                    title: Self.quotaDisplayLabel(m.quota),
                    window: Self.rateWindow(for: m.quota),
                    usageKnown: m.quota.remainingFraction != nil)
            }

        return resetOnlyPoolWindows.sorted { lhs, rhs in
            guard let lhsPool = Self.pool(forExtraWindowID: lhs.id),
                  let rhsPool = Self.pool(forExtraWindowID: rhs.id)
            else {
                return lhs.title.localizedCaseInsensitiveCompare(rhs.title) == .orderedAscending
            }
            return lhsPool.sortRank < rhsPool.sortRank
        } + distinctWindows
    }

    private static func compactFallbackWindowID(modelID: String) -> String {
        "antigravity-compact-fallback-\(modelID)"
    }

    private static func shouldShowDistinctExtraWindow(_ model: AntigravityNormalizedModel) -> Bool {
        guard !self.isSummaryCandidate(model) else { return false }
        if model.quota.remainingFraction == nil {
            return model.quota.resetTime != nil || model.quota.resetDescription != nil
        }
        return model.quota.remainingPercent < 99.9
    }

    private static func pool(forExtraWindowID id: String) -> AntigravityUsagePool? {
        switch id {
        case AntigravityUsagePool.geminiAI.id: .geminiAI
        case AntigravityUsagePool.claudeGPT.id: .claudeGPT
        default: nil
        }
    }

    private static func usagePool(for model: AntigravityNormalizedModel) -> AntigravityUsagePool? {
        switch model.family {
        case .geminiPro, .geminiFlash:
            .geminiAI
        case .claudeModels, .gpt:
            .claudeGPT
        case .unknown:
            nil
        }
    }

    private static func family(forModelID modelId: String, label: String) -> AntigravityModelFamily {
        let modelIDFamily = Self.family(from: modelId)
        if modelIDFamily != .unknown {
            return modelIDFamily
        }
        return Self.family(from: label)
    }

    private static func family(from text: String) -> AntigravityModelFamily {
        if text.contains("claude") {
            return .claudeModels
        }
        if text.contains("gpt") || text.contains("openai") {
            return .gpt
        }
        if text.contains("gemini"), text.contains("pro") {
            return .geminiPro
        }
        if text.contains("gemini"), text.contains("flash") {
            return .geminiFlash
        }
        return .unknown
    }
}

public struct AntigravityPlanInfoSummary: Sendable, Codable, Equatable {
    public let planName: String?
    public let planDisplayName: String?
    public let displayName: String?
    public let productName: String?
    public let planShortName: String?
}

public enum AntigravityStatusProbeError: LocalizedError, Sendable, Equatable {
    case notRunning
    case missingCSRFToken
    case portDetectionFailed(String)
    case apiError(String)
    case parseFailed(String)
    case timedOut
    case authenticationRequired
    case accountMismatch(expected: String?, found: String?)

    public var errorDescription: String? {
        switch self {
        case .notRunning:
            "Antigravity language server not detected. Launch Antigravity and retry."
        case .missingCSRFToken:
            "Antigravity CSRF token not found. Restart Antigravity and retry."
        case let .portDetectionFailed(message):
            Self.portDetectionDescription(message)
        case let .apiError(message):
            Self.apiErrorDescription(message)
        case let .parseFailed(message):
            "Could not parse Antigravity quota: \(message)"
        case .timedOut:
            "Antigravity quota request timed out."
        case .authenticationRequired:
            "Antigravity CLI is signed out. Run agy in a terminal to sign in, then retry."
        case let .accountMismatch(expected, found):
            Self.accountMismatchDescription(expected: expected, found: found)
        }
    }

    private static func accountMismatchDescription(expected: String?, found: String?) -> String {
        let selected = expected ?? "the selected account"
        if let found {
            return "Antigravity local session is signed in as \(found), not \(selected); "
                + "using the selected account's OAuth data instead."
        }
        return "Antigravity local session did not report an account matching \(selected); "
            + "using the selected account's OAuth data instead."
    }

    private static func portDetectionDescription(_ message: String) -> String {
        switch message {
        case "lsof not available":
            "Antigravity port detection needs lsof. Install it, then retry."
        case "no listening ports found":
            "Antigravity is running but not exposing ports yet. Try again in a few seconds."
        default:
            "Antigravity port detection failed: \(message)"
        }
    }

    private static func apiErrorDescription(_ message: String) -> String {
        if message.contains("HTTP 401") || message.contains("HTTP 403") {
            return "Antigravity session expired. Restart Antigravity and retry."
        }
        return "Antigravity API error: \(message)"
    }
}

public struct AntigravityStatusProbe: Sendable {
    /// Which local Antigravity processes the probe may attach to.
    public enum ProcessScope: Sendable {
        /// Match Antigravity app, Antigravity IDE, and the `agy` CLI language server.
        case ideAndCLI
        /// Match only the Antigravity 2.0 app language server.
        case appOnly
        /// Match only the Antigravity IDE extension language server.
        case ideOnly
    }

    public var timeout: TimeInterval = 8.0
    public var processScope: ProcessScope = .ideAndCLI

    private static let getUserStatusPath = "/exa.language_server_pb.LanguageServerService/GetUserStatus"
    private static let commandModelConfigPath =
        "/exa.language_server_pb.LanguageServerService/GetCommandModelConfigs"
    private static let quotaSummaryPath =
        "/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary"
    private static let unleashPath = "/exa.language_server_pb.LanguageServerService/GetUnleashData"
    private static let log = CodexBarLog.logger(LogCategories.provider(.antigravity))

    public init(timeout: TimeInterval = 8.0, processScope: ProcessScope = .ideAndCLI) {
        self.timeout = timeout
        self.processScope = processScope
    }

    public func fetch(matchingAccountEmail expectedAccountEmail: String? = nil) async throws
        -> AntigravityStatusSnapshot
    {
        let deadline = Date().addingTimeInterval(self.timeout)
        let processInfos = try await Self.detectProcessInfos(timeout: self.timeout, scope: self.processScope)
        let result = try await Self.fetchProcessSnapshots(processInfos: processInfos) { processInfo in
            try await Self.fetch(
                processInfo: processInfo,
                timeout: self.timeout,
                deadline: deadline)
        }

        if let bestSnapshot = Self.preferredLocalSnapshot(
            result.snapshots,
            matchingAccountEmail: expectedAccountEmail)
        {
            return bestSnapshot
        }
        throw result.lastError ?? AntigravityStatusProbeError.notRunning
    }

    public func fetchPlanInfoSummary() async throws -> AntigravityPlanInfoSummary? {
        let processInfo = try await Self.detectProcessInfo(timeout: self.timeout)
        let ports = try await Self.listeningPorts(pid: processInfo.pid, timeout: self.timeout)
        let endpoint = try await Self.resolveWorkingEndpoint(
            candidateEndpoints: Self.connectionCandidates(
                listeningPorts: ports,
                languageServerCSRFToken: processInfo.csrfToken,
                extensionServerPort: processInfo.extensionPort,
                extensionServerCSRFToken: processInfo.extensionServerCSRFToken),
            timeout: self.timeout)
        return try await Self.makeParsedRequest(
            payload: RequestPayload(
                path: Self.getUserStatusPath,
                body: Self.defaultRequestBody()),
            context: RequestContext(
                endpoints: Self.requestEndpoints(
                    resolvedEndpoint: endpoint,
                    listeningPorts: ports,
                    languageServerCSRFToken: processInfo.csrfToken,
                    extensionServerPort: processInfo.extensionPort,
                    extensionServerCSRFToken: processInfo.extensionServerCSRFToken),
                timeout: self.timeout),
            parse: Self.parsePlanInfoSummary)
    }

    static func localSnapshotScore(_ snapshot: AntigravityStatusSnapshot) -> Int {
        var score = 0
        if let quotaSummary = snapshot.quotaSummary {
            let buckets = quotaSummary.groups.flatMap(\.buckets)
            let knownBuckets = buckets.count(where: { !$0.disabled && $0.remainingFraction != nil })
            score += 1000
            score += quotaSummary.groups.count * 10
            score += buckets.count
            score += knownBuckets * 20
        } else {
            let knownRows = snapshot.modelQuotas.count(where: { $0.remainingFraction != nil })
            score += snapshot.modelQuotas.count
            score += knownRows * 10
        }
        if snapshot.accountEmail != nil {
            score += 2
        }
        if snapshot.accountPlan != nil {
            score += 1
        }
        return score
    }

    public static func isRunning(timeout: TimeInterval = 4.0) async -> Bool {
        await (try? self.detectProcessInfo(timeout: timeout)) != nil
    }

    public static func detectVersion(timeout: TimeInterval = 4.0) async -> String? {
        let running = await Self.isRunning(timeout: timeout)
        return running ? "running" : nil
    }

    // MARK: - CLI Local Fetch

    /// Fetch usage data from a known set of local ports (discovered via
    /// ``AntigravityCLISession``'s ``pid``), without requiring a running
    /// ``language_server`` process or CSRF token.
    ///
    /// The ``agy`` CLI exposes the same ``GetUserStatus`` gRPC-web endpoint as
    /// the desktop ``language_server``. Unlike the desktop endpoint, it does
    /// not require a CSRF token header.
    public func fetchFromPorts(_ ports: [Int], deadline: Date? = nil) async throws -> AntigravityStatusSnapshot {
        guard !ports.isEmpty else {
            throw AntigravityStatusProbeError.portDetectionFailed("no listening ports found")
        }
        let endpoints = Self.cliEndpoints(ports: ports)
        let context = RequestContext(endpoints: endpoints, timeout: self.timeout, deadline: deadline)
        return try await Self.fetchSnapshot(context: context)
    }

    // MARK: - Parsing

    public static func parseUserStatusResponse(_ data: Data) throws -> AntigravityStatusSnapshot {
        let decoder = JSONDecoder()
        let response = try decoder.decode(UserStatusResponse.self, from: data)
        if let invalid = Self.invalidCode(response.code) {
            throw AntigravityStatusProbeError.apiError(invalid)
        }
        guard let userStatus = response.userStatus else {
            throw AntigravityStatusProbeError.parseFailed("Missing userStatus")
        }

        let modelConfigs = userStatus.cascadeModelConfigData?.clientModelConfigs ?? []
        let models = modelConfigs.compactMap(Self.quotaFromConfig(_:))
        let email = userStatus.email
        // Prefer userTier.name (actual subscription tier) over planInfo (shows "Pro" for Ultra users)
        let planName = userStatus.userTier?.preferredName ?? userStatus.planStatus?.planInfo?.preferredName

        return AntigravityStatusSnapshot(
            modelQuotas: models,
            accountEmail: email,
            accountPlan: planName,
            source: .local)
    }

    static func parsePlanInfoSummary(_ data: Data) throws -> AntigravityPlanInfoSummary? {
        let decoder = JSONDecoder()
        let response = try decoder.decode(UserStatusResponse.self, from: data)
        if let invalid = Self.invalidCode(response.code) {
            throw AntigravityStatusProbeError.apiError(invalid)
        }
        guard let userStatus = response.userStatus else {
            throw AntigravityStatusProbeError.parseFailed("Missing userStatus")
        }
        guard let planInfo = userStatus.planStatus?.planInfo else { return nil }
        return AntigravityPlanInfoSummary(
            planName: planInfo.planName,
            planDisplayName: planInfo.planDisplayName,
            displayName: planInfo.displayName,
            productName: planInfo.productName,
            planShortName: planInfo.planShortName)
    }

    static func parseCommandModelResponse(_ data: Data) throws -> AntigravityStatusSnapshot {
        let decoder = JSONDecoder()
        let response = try decoder.decode(CommandModelConfigResponse.self, from: data)
        if let invalid = Self.invalidCode(response.code) {
            throw AntigravityStatusProbeError.apiError(invalid)
        }
        let modelConfigs = response.clientModelConfigs ?? []
        let models = modelConfigs.compactMap(Self.quotaFromConfig(_:))
        return AntigravityStatusSnapshot(modelQuotas: models, accountEmail: nil, accountPlan: nil, source: .local)
    }

    private static func quotaFromConfig(_ config: ModelConfig) -> AntigravityModelQuota? {
        guard let quota = config.quotaInfo else { return nil }
        let reset = quota.resetTime.flatMap { Self.parseDate($0) }
        return AntigravityModelQuota(
            label: config.label,
            modelId: config.modelOrAlias.model,
            remainingFraction: quota.remainingFraction,
            resetTime: reset,
            resetDescription: nil)
    }

    static func invalidCode(_ code: CodeValue?) -> String? {
        guard let code else { return nil }
        if code.isOK { return nil }
        return "\(code.rawValue)"
    }

    static func parseDate(_ value: String) -> Date? {
        if let date = ISO8601DateFormatter().date(from: value) {
            return date
        }
        if let seconds = Double(value) {
            return Date(timeIntervalSince1970: seconds)
        }
        return nil
    }

    // MARK: - Port detection

    struct ProcessInfoResult {
        let pid: Int
        let extensionPort: Int?
        let extensionServerCSRFToken: String?
        let csrfToken: String
        let commandLine: String
    }

    struct AntigravityConnectionEndpoint: Equatable {
        enum Source: String {
            case languageServer = "language-server"
            case extensionServer = "extension-server"
            case cliHTTPS = "cli-https"
        }

        let scheme: String
        let port: Int
        let csrfToken: String
        let source: Source
        /// Whether this endpoint needs a CSRF token header.
        /// The CLI HTTPS endpoint (``Source/cliHTTPS``) speaks the same HTTP API
        /// but does not require a CSRF token.
        var requiresCSRFToken: Bool {
            switch self.source {
            case .languageServer, .extensionServer: true
            case .cliHTTPS: false
            }
        }

        func matchesRequestTarget(_ other: Self) -> Bool {
            self.scheme == other.scheme && self.port == other.port && self.csrfToken == other.csrfToken
        }
    }

    private static func detectProcessInfo(
        timeout: TimeInterval,
        scope: ProcessScope = .ideAndCLI) async throws -> ProcessInfoResult
    {
        let processInfos = try await self.detectProcessInfos(timeout: timeout, scope: scope)
        guard let first = processInfos.first else {
            throw AntigravityStatusProbeError.notRunning
        }
        return first
    }

    /// Internal (not private): called by AntigravityCLIHTTPSFetchStrategy
    /// .liveWarmAgyDependencies to discover an already-running `agy` for reuse.
    static func detectProcessInfos(
        timeout: TimeInterval,
        scope: ProcessScope = .ideAndCLI) async throws -> [ProcessInfoResult]
    {
        #if canImport(Darwin)
        let entries = DarwinProcessEnumerator.allPIDs().compactMap { pid -> (pid: Int, command: String)? in
            guard let executablePath = DarwinProcessEnumerator.executablePath(pid: pid),
                  DarwinProcessEnumerator.isAntigravityCandidatePath(executablePath)
            else { return nil }
            let command = DarwinProcessEnumerator.commandLine(pid: pid) ?? executablePath
            return (Int(pid), command)
        }
        return try self.processInfos(fromEntries: entries, scope: scope)
        #else
        let env = ProcessInfo.processInfo.environment
        let result = try await SubprocessRunner.run(
            binary: "/bin/ps",
            arguments: ["-ax", "-o", "pid=,command="],
            environment: env,
            timeout: timeout,
            label: "antigravity-ps")

        return try Self.processInfos(fromProcessListOutput: result.stdout, scope: scope)
        #endif
    }

    static func processInfo(
        fromProcessListOutput output: String,
        scope: ProcessScope = .ideAndCLI) throws -> ProcessInfoResult
    {
        let processInfos = try self.processInfos(fromProcessListOutput: output, scope: scope)
        guard let first = processInfos.first else {
            throw AntigravityStatusProbeError.notRunning
        }
        return first
    }

    static func processInfos(
        fromProcessListOutput output: String,
        scope: ProcessScope = .ideAndCLI) throws -> [ProcessInfoResult]
    {
        let entries = output.split(separator: "\n").compactMap { line -> (pid: Int, command: String)? in
            guard let match = Self.matchProcessLine(String(line)) else { return nil }
            return (match.pid, match.command)
        }
        return try self.processInfos(fromEntries: entries, scope: scope)
    }

    static func processInfos(
        fromEntries entries: [(pid: Int, command: String)],
        scope: ProcessScope = .ideAndCLI) throws -> [ProcessInfoResult]
    {
        var sawTokenlessIDE = false
        var results: [ProcessInfoResult] = []
        for entry in entries {
            guard let kind = Self.antigravityProcessKind(entry.command) else { continue }
            if !Self.processKind(kind, matches: scope) { continue }
            // The IDE language server authenticates local requests with a
            // `--csrf_token` and must keep requiring it: skip a tokenless IDE
            // or app match so a later valid server can still be found (and surface
            // `missingCSRFToken` if none is). The CLI's language server exposes
            // no token flag and needs none, so an empty token is allowed there.
            guard let token = Self.resolvedCSRFToken(forKind: kind, command: entry.command) else {
                sawTokenlessIDE = true
                continue
            }
            let port = Self.extractPort("--extension_server_port", from: entry.command)
            let extensionServerCSRFToken = Self.extractFlag("--extension_server_csrf_token", from: entry.command)
            results.append(ProcessInfoResult(
                pid: entry.pid,
                extensionPort: port,
                extensionServerCSRFToken: extensionServerCSRFToken,
                csrfToken: token,
                commandLine: entry.command))
        }

        if !results.isEmpty {
            return results
        }
        if sawTokenlessIDE {
            throw AntigravityStatusProbeError.missingCSRFToken
        }
        throw AntigravityStatusProbeError.notRunning
    }

    private struct ProcessLineMatch {
        let pid: Int
        let command: String
    }

    private static func matchProcessLine(_ line: String) -> ProcessLineMatch? {
        let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty else { return nil }
        let parts = trimmed.split(separator: " ", maxSplits: 1, omittingEmptySubsequences: true)
        guard parts.count == 2, let pid = Int(parts[0]) else { return nil }
        return ProcessLineMatch(pid: pid, command: String(parts[1]))
    }

    enum AntigravityProcessKind: Equatable {
        /// Antigravity 2.0 app language server. Requires a `--csrf_token`.
        case app
        /// Antigravity IDE extension language server. Requires a `--csrf_token`.
        case ide
        /// CLI language server (`agy` / `antigravity-cli`). Needs no CSRF token.
        case cli
    }

    static func isAntigravityLanguageServerCommandLine(_ command: String) -> Bool {
        self.antigravityProcessKind(command) != nil
    }

    /// Classify a process command line as the Antigravity app language server,
    /// the Antigravity IDE language server, the Antigravity CLI language server,
    /// or neither. Desktop language servers take precedence so their CSRF-token
    /// requirement is preserved.
    static func antigravityProcessKind(_ command: String) -> AntigravityProcessKind? {
        let lower = command.lowercased()
        if Self.isLanguageServerCommandLine(lower), Self.isAntigravityCommandLine(lower) {
            return Self.isAntigravityIDECommandLine(lower) ? .ide : .app
        }
        if Self.isAntigravityCLICommandLine(lower) {
            return .cli
        }
        return nil
    }

    private static func processKind(_ kind: AntigravityProcessKind, matches scope: ProcessScope) -> Bool {
        switch scope {
        case .ideAndCLI:
            true
        case .appOnly:
            kind == .app
        case .ideOnly:
            kind == .ide
        }
    }

    /// Resolve the CSRF token to use for a matched process, or `nil` when the
    /// match must be skipped. IDE matches keep requiring `--csrf_token`
    /// (tokenless IDE matches are skipped). CLI matches accept an empty token
    /// because the CLI's language server requires none.
    static func resolvedCSRFToken(forKind kind: AntigravityProcessKind, command: String) -> String? {
        if let token = extractFlag("--csrf_token", from: command) {
            return token
        }
        switch kind {
        case .app, .ide: return nil
        case .cli: return ""
        }
    }

    private static func isLanguageServerCommandLine(_ lowerCommand: String) -> Bool {
        let pattern = #"(^|[/\\])language(?:_|-)server(?:[_-][a-z0-9]+)*(?:\.exe)?(\s|$)"#
        return lowerCommand.range(of: pattern, options: .regularExpression) != nil
    }

    /// The Antigravity CLI (`agy` / `antigravity-cli`) hosts the same language
    /// server locally as the IDE, but launches it without a `--csrf_token` flag
    /// and under a different process name. Match it so usage can be probed when
    /// only the CLI is running.
    private static func isAntigravityCLICommandLine(_ lowerCommand: String) -> Bool {
        let cliPathPattern = #"(^|[/\\])(antigravity-cli|antigravity_cli)([\s/\\]|$)"#
        if lowerCommand.range(of: cliPathPattern, options: .regularExpression) != nil {
            return true
        }
        let agyPattern = #"(^|[/\\])agy(\s|$)"#
        return lowerCommand.range(of: agyPattern, options: .regularExpression) != nil
    }

    private static func isAntigravityCommandLine(_ command: String) -> Bool {
        if command.contains("--app_data_dir") && command.contains("antigravity") { return true }
        if command.contains("antigravity.app/") || command.contains("antigravity.app\\") { return true }
        if command.contains("antigravity ide.app/") || command.contains("antigravity ide.app\\") { return true }
        if command.contains("/antigravity/") || command.contains("\\antigravity\\") { return true }
        return false
    }

    private static func isAntigravityIDECommandLine(_ lowerCommand: String) -> Bool {
        [
            "antigravity ide.app/",
            "antigravity ide.app\\",
            "--app_data_dir antigravity-ide",
            "--app_data_dir=antigravity-ide",
            "/extensions/antigravity/bin/language_server",
            "\\extensions\\antigravity\\bin\\language_server",
        ].contains { lowerCommand.contains($0) }
    }

    private static func extractFlag(_ flag: String, from command: String) -> String? {
        let pattern = "\(NSRegularExpression.escapedPattern(for: flag))[=\\s]+([^\\s]+)"
        guard let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) else { return nil }
        let range = NSRange(command.startIndex..<command.endIndex, in: command)
        guard let match = regex.firstMatch(in: command, options: [], range: range),
              let tokenRange = Range(match.range(at: 1), in: command) else { return nil }
        return String(command[tokenRange])
    }

    private static func extractPort(_ flag: String, from command: String) -> Int? {
        guard let raw = extractFlag(flag, from: command) else { return nil }
        return Int(raw)
    }

    static func connectionCandidates(
        listeningPorts: [Int],
        languageServerCSRFToken: String,
        extensionServerPort: Int?,
        extensionServerCSRFToken: String?) -> [AntigravityConnectionEndpoint]
    {
        var endpoints = Self.languageServerEndpoints(
            listeningPorts: listeningPorts,
            languageServerCSRFToken: languageServerCSRFToken)

        for endpoint in Self.extensionServerEndpoints(
            extensionServerPort: extensionServerPort,
            languageServerCSRFToken: languageServerCSRFToken,
            extensionServerCSRFToken: extensionServerCSRFToken)
        {
            guard !endpoints.contains(where: { $0.matchesRequestTarget(endpoint) }) else { continue }
            endpoints.append(endpoint)
        }

        return endpoints
    }

    static func requestEndpoints(
        resolvedEndpoint: AntigravityConnectionEndpoint,
        listeningPorts: [Int],
        languageServerCSRFToken: String,
        extensionServerPort: Int?,
        extensionServerCSRFToken: String?) -> [AntigravityConnectionEndpoint]
    {
        var endpoints = [resolvedEndpoint]

        if resolvedEndpoint.source == .extensionServer {
            Self.appendUniqueRequestTargets(
                from: Self.extensionServerEndpoints(
                    extensionServerPort: extensionServerPort,
                    languageServerCSRFToken: languageServerCSRFToken,
                    extensionServerCSRFToken: extensionServerCSRFToken),
                to: &endpoints)
            Self.appendUniqueRequestTargets(
                from: Self.languageServerEndpoints(
                    listeningPorts: listeningPorts,
                    languageServerCSRFToken: languageServerCSRFToken),
                to: &endpoints)
        } else {
            Self.appendUniqueRequestTargets(
                from: Self.languageServerEndpoints(
                    listeningPorts: listeningPorts,
                    languageServerCSRFToken: languageServerCSRFToken),
                to: &endpoints)
            Self.appendUniqueRequestTargets(
                from: Self.extensionServerEndpoints(
                    extensionServerPort: extensionServerPort,
                    languageServerCSRFToken: languageServerCSRFToken,
                    extensionServerCSRFToken: extensionServerCSRFToken),
                to: &endpoints)
        }

        return endpoints
    }

    private static func languageServerEndpoints(
        listeningPorts: [Int],
        languageServerCSRFToken: String) -> [AntigravityConnectionEndpoint]
    {
        listeningPorts.flatMap { port in
            self.localProbeSchemes.map { scheme in
                AntigravityConnectionEndpoint(
                    scheme: scheme,
                    port: port,
                    csrfToken: languageServerCSRFToken,
                    source: .languageServer)
            }
        }
    }

    private static func extensionServerEndpoints(
        extensionServerPort: Int?,
        languageServerCSRFToken: String,
        extensionServerCSRFToken: String?) -> [AntigravityConnectionEndpoint]
    {
        guard let extensionServerPort else { return [] }

        var endpoints: [AntigravityConnectionEndpoint] = []
        if let extensionServerCSRFToken {
            endpoints.append(
                AntigravityConnectionEndpoint(
                    scheme: "http",
                    port: extensionServerPort,
                    csrfToken: extensionServerCSRFToken,
                    source: .extensionServer))
        }

        if extensionServerCSRFToken != languageServerCSRFToken {
            endpoints.append(
                AntigravityConnectionEndpoint(
                    scheme: "http",
                    port: extensionServerPort,
                    csrfToken: languageServerCSRFToken,
                    source: .extensionServer))
        }

        return endpoints
    }

    private static func appendUniqueRequestTargets(
        from candidates: [AntigravityConnectionEndpoint],
        to endpoints: inout [AntigravityConnectionEndpoint])
    {
        for endpoint in candidates {
            guard !endpoints.contains(where: { $0.matchesRequestTarget(endpoint) }) else { continue }
            endpoints.append(endpoint)
        }
    }

    static func resolveWorkingEndpoint(
        candidateEndpoints: [AntigravityConnectionEndpoint],
        timeout: TimeInterval,
        deadline: Date? = nil,
        testConnectivity: @escaping @Sendable (AntigravityConnectionEndpoint, TimeInterval) async -> Bool = Self
            .testEndpointConnectivity) async throws -> AntigravityConnectionEndpoint
    {
        for (index, endpoint) in candidateEndpoints.enumerated() {
            let remainingAttemptCount = candidateEndpoints.count - index
            guard let attemptTimeout = timeoutForEndpointAttempt(
                timeout: timeout,
                deadline: deadline,
                remainingAttemptCount: remainingAttemptCount)
            else {
                throw AntigravityStatusProbeError.timedOut
            }
            let ok = await testConnectivity(endpoint, attemptTimeout)
            if ok { return endpoint }
        }
        if let fallback = fallbackProbeEndpoint(candidateEndpoints) {
            self.log.debug("Port probe fell back to best-effort endpoint", metadata: [
                "source": fallback.source.rawValue,
                "scheme": fallback.scheme,
                "port": "\(fallback.port)",
            ])
            return fallback
        }
        throw AntigravityStatusProbeError.portDetectionFailed("no working API port found")
    }

    private static func timeoutForEndpointAttempt(
        timeout: TimeInterval,
        deadline: Date?,
        remainingAttemptCount: Int) -> TimeInterval?
    {
        guard let deadline else { return timeout }
        let remaining = deadline.timeIntervalSinceNow
        guard remaining > 0 else { return nil }
        return min(timeout, remaining / Double(max(1, remainingAttemptCount)))
    }

    static func fallbackProbePort(ports: [Int], extensionPort: Int?) -> Int? {
        if let nonExtension = ports.first(where: { $0 != extensionPort }) {
            return nonExtension
        }
        if let extensionPort {
            return extensionPort
        }
        return ports.first
    }

    static func isReachableProbeError(_ error: Error) -> Bool {
        guard case let AntigravityStatusProbeError.apiError(message) = error else { return false }
        return message.hasPrefix("HTTP ")
    }

    private static func fallbackProbeEndpoint(
        _ endpoints: [AntigravityConnectionEndpoint]) -> AntigravityConnectionEndpoint?
    {
        if let languageServerEndpoint = endpoints.first(where: { $0.source == .languageServer }) {
            return languageServerEndpoint
        }
        return endpoints.first
    }

    private static func testEndpointConnectivity(
        _ endpoint: AntigravityConnectionEndpoint,
        timeout: TimeInterval) async -> Bool
    {
        do {
            _ = try await self.makeRequest(
                payload: RequestPayload(
                    path: self.unleashPath,
                    body: self.unleashRequestBody()),
                context: RequestContext(endpoints: [endpoint], timeout: timeout))
            return true
        } catch {
            if self.isReachableProbeError(error) {
                self.log.debug("Port probe received HTTP response; treating endpoint as reachable", metadata: [
                    "source": endpoint.source.rawValue,
                    "scheme": endpoint.scheme,
                    "port": "\(endpoint.port)",
                    "error": error.localizedDescription,
                ])
                return true
            }
            self.log.debug("Port probe failed", metadata: [
                "source": endpoint.source.rawValue,
                "scheme": endpoint.scheme,
                "port": "\(endpoint.port)",
                "error": error.localizedDescription,
            ])
            return false
        }
    }

    // MARK: - HTTP

    struct RequestPayload {
        let path: String
        let body: [String: Any]
    }

    struct RequestContext {
        let endpoints: [AntigravityConnectionEndpoint]
        let timeout: TimeInterval
        let deadline: Date?

        init(endpoints: [AntigravityConnectionEndpoint], timeout: TimeInterval, deadline: Date? = nil) {
            self.endpoints = endpoints
            self.timeout = timeout
            self.deadline = deadline
        }

        func timeoutForNextAttempt() -> TimeInterval? {
            AntigravityStatusProbe.timeoutForNextAttempt(timeout: self.timeout, deadline: self.deadline)
        }
    }

    private static func defaultRequestBody() -> [String: Any] {
        [
            "metadata": [
                "ideName": "antigravity",
                "extensionName": "antigravity",
                "ideVersion": "unknown",
                "locale": "en",
            ],
        ]
    }

    private static func unleashRequestBody() -> [String: Any] {
        [
            "context": [
                "properties": [
                    "devMode": "false",
                    "extensionVersion": "unknown",
                    "hasAnthropicModelAccess": "true",
                    "ide": "antigravity",
                    "ideVersion": "unknown",
                    "installationId": "codexbar",
                    "language": "UNSPECIFIED",
                    "os": "macos",
                    "requestedModelId": "MODEL_UNSPECIFIED",
                ],
            ],
        ]
    }

    static func fetchSnapshot(
        context: RequestContext,
        send: @escaping @Sendable (RequestPayload, AntigravityConnectionEndpoint, TimeInterval) async throws -> Data =
            sendRequest) async throws -> AntigravityStatusSnapshot
    {
        do {
            let quotaSummary = try await self.makeParsedRequest(
                payload: RequestPayload(
                    path: self.quotaSummaryPath,
                    body: ["forceRefresh": true]),
                context: self.quotaSummaryRequestContext(from: context),
                send: send,
                parse: self.parseQuotaSummaryResponse)
            guard quotaSummary.quotaSummary?.groups.contains(where: { group in
                group.buckets.contains { !$0.disabled && $0.remainingFraction != nil }
            }) == true else {
                throw AntigravityStatusProbeError.parseFailed("Quota summary has no usable quota buckets")
            }
            let identity = try? await self.makeParsedRequest(
                payload: RequestPayload(
                    path: self.getUserStatusPath,
                    body: self.defaultRequestBody()),
                context: self.identityRequestContext(from: context),
                send: send,
                parse: self.parseUserStatusResponse)
            return quotaSummary.withIdentity(from: identity)
        } catch {
            self.log.debug("Antigravity quota summary unavailable; falling back to model quotas", metadata: [
                "error": error.localizedDescription,
            ])
        }

        do {
            return try await self.makeParsedRequest(
                payload: RequestPayload(
                    path: self.getUserStatusPath,
                    body: self.defaultRequestBody()),
                context: self.legacyUserStatusRequestContext(from: context),
                send: send,
                parse: self.parseUserStatusResponse)
        } catch {
            return try await self.makeParsedRequest(
                payload: RequestPayload(
                    path: self.commandModelConfigPath,
                    body: self.defaultRequestBody()),
                context: context,
                send: send,
                parse: self.parseCommandModelResponse)
        }
    }

    private static func legacyUserStatusRequestContext(from context: RequestContext) -> RequestContext {
        guard let deadline = context.deadline else { return context }
        let remaining = max(0, deadline.timeIntervalSinceNow)
        let userStatusBudget = remaining / 2
        return RequestContext(
            endpoints: context.endpoints,
            timeout: min(context.timeout, userStatusBudget),
            deadline: Date().addingTimeInterval(userStatusBudget))
    }

    private static func quotaSummaryRequestContext(from context: RequestContext) -> RequestContext {
        guard let deadline = context.deadline else { return context }
        let remaining = max(0, deadline.timeIntervalSinceNow)
        let quotaSummaryBudget = remaining / 2
        return RequestContext(
            endpoints: context.endpoints,
            timeout: min(context.timeout, quotaSummaryBudget),
            deadline: Date().addingTimeInterval(quotaSummaryBudget))
    }

    private static func identityRequestContext(from context: RequestContext) -> RequestContext {
        RequestContext(
            endpoints: context.endpoints,
            timeout: min(context.timeout, 1),
            deadline: context.deadline)
    }

    private static func makeRequest(
        payload: RequestPayload,
        context: RequestContext) async throws -> Data
    {
        try await self.sendRequest(payload: payload, context: context)
    }

    static func makeParsedRequest<T>(
        payload: RequestPayload,
        context: RequestContext,
        send: @escaping @Sendable (RequestPayload, AntigravityConnectionEndpoint, TimeInterval) async throws -> Data =
            sendRequest,
        parse: @escaping @Sendable (Data) throws -> T) async throws -> T
    {
        var lastError: Error?

        for endpoint in context.endpoints {
            guard let timeout = context.timeoutForNextAttempt() else {
                lastError = lastError ?? AntigravityStatusProbeError.timedOut
                break
            }
            do {
                let data = try await send(payload, endpoint, timeout)
                return try parse(data)
            } catch {
                lastError = error
                Self.log.debug("Antigravity request/parse attempt failed", metadata: [
                    "path": payload.path,
                    "source": endpoint.source.rawValue,
                    "scheme": endpoint.scheme,
                    "port": "\(endpoint.port)",
                    "error": error.localizedDescription,
                ])
            }
        }

        throw lastError ?? AntigravityStatusProbeError.apiError("Invalid response")
    }

    private static func sendRequest(
        payload: RequestPayload,
        context: RequestContext) async throws -> Data
    {
        var lastError: Error?

        for endpoint in context.endpoints {
            guard let timeout = context.timeoutForNextAttempt() else {
                lastError = lastError ?? AntigravityStatusProbeError.timedOut
                break
            }
            do {
                return try await Self.sendRequest(payload: payload, endpoint: endpoint, timeout: timeout)
            } catch {
                lastError = error
                Self.log.debug("Antigravity request attempt failed", metadata: [
                    "path": payload.path,
                    "source": endpoint.source.rawValue,
                    "scheme": endpoint.scheme,
                    "port": "\(endpoint.port)",
                    "error": error.localizedDescription,
                ])
            }
        }

        throw lastError ?? AntigravityStatusProbeError.apiError("Invalid URL")
    }

    private static func sendRequest(
        payload: RequestPayload,
        endpoint: AntigravityConnectionEndpoint,
        timeout: TimeInterval) async throws -> Data
    {
        guard let url = URL(string: "\(endpoint.scheme)://127.0.0.1:\(endpoint.port)\(payload.path)") else {
            throw AntigravityStatusProbeError.apiError("Invalid URL")
        }

        let body = try JSONSerialization.data(withJSONObject: payload.body, options: [])
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.httpBody = body
        request.timeoutInterval = timeout
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue(String(body.count), forHTTPHeaderField: "Content-Length")
        request.setValue("1", forHTTPHeaderField: "Connect-Protocol-Version")
        if endpoint.requiresCSRFToken {
            request.setValue(endpoint.csrfToken, forHTTPHeaderField: "X-Codeium-Csrf-Token")
        }

        let config = URLSessionConfiguration.ephemeral
        config.timeoutIntervalForRequest = timeout
        config.timeoutIntervalForResource = timeout
        #if !os(Linux)
        config.waitsForConnectivity = false
        #endif

        let delegate = LocalhostSessionDelegate()
        let session = URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
        defer { session.invalidateAndCancel() }

        let (data, response) = try await delegate.data(for: request, session: session)
        guard let http = response as? HTTPURLResponse else {
            throw AntigravityStatusProbeError.apiError("Invalid response")
        }
        guard http.statusCode == 200 else {
            let message = String(data: data, encoding: .utf8) ?? ""
            throw AntigravityStatusProbeError.apiError("HTTP \(http.statusCode): \(message)")
        }
        return data
    }
}
