import Foundation public struct MiniMaxUsageSnapshot: Sendable { public let planName: String? public let availablePrompts: Int? public let currentPrompts: Int? public let remainingPrompts: Int? public let windowMinutes: Int? public let usedPercent: Double? public let resetsAt: Date? public let updatedAt: Date public let services: [MiniMaxServiceUsage]? public let billingSummary: MiniMaxBillingSummary? public let pointsBalance: Double? public let subscriptionExpiresAt: Date? public let subscriptionRenewsAt: Date? public var primaryService: MiniMaxServiceUsage? { self.orderedQuotaServices.first } public var secondaryService: MiniMaxServiceUsage? { let services = self.orderedQuotaServices guard services.count >= 2 else { return nil } return services[1] } public var tertiaryService: MiniMaxServiceUsage? { let services = self.orderedQuotaServices guard services.count >= 3 else { return nil } return services[2] } public var orderedQuotaServices: [MiniMaxServiceUsage] { guard let services, !services.isEmpty else { return [] } return services.enumerated().sorted { lhs, rhs in let lhsRank = self.quotaServiceRank(lhs.element, originalIndex: lhs.offset) let rhsRank = self.quotaServiceRank(rhs.element, originalIndex: rhs.offset) if lhsRank.primary != rhsRank.primary { return lhsRank.primary < rhsRank.primary } if lhsRank.window != rhsRank.window { return lhsRank.window < rhsRank.window } return lhsRank.originalIndex < rhsRank.originalIndex }.map(\.element) } private func quotaServiceRank( _ service: MiniMaxServiceUsage, originalIndex: Int) -> (primary: Int, window: Int, originalIndex: Int) { ( primary: service.isPrimaryTextQuotaLane ? 0 : 1, window: self.quotaWindowRank(service), originalIndex: originalIndex) } private func quotaWindowRank(_ service: MiniMaxServiceUsage) -> Int { let window = service.windowType.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() if window == "weekly" { return 1 } return 0 } public init( planName: String?, availablePrompts: Int?, currentPrompts: Int?, remainingPrompts: Int?, windowMinutes: Int?, usedPercent: Double?, resetsAt: Date?, updatedAt: Date, services: [MiniMaxServiceUsage]? = nil, billingSummary: MiniMaxBillingSummary? = nil, pointsBalance: Double? = nil, subscriptionExpiresAt: Date? = nil, subscriptionRenewsAt: Date? = nil) { self.planName = planName self.availablePrompts = availablePrompts self.currentPrompts = currentPrompts self.remainingPrompts = remainingPrompts self.windowMinutes = windowMinutes self.usedPercent = usedPercent self.resetsAt = resetsAt self.updatedAt = updatedAt self.services = services self.billingSummary = billingSummary self.pointsBalance = pointsBalance self.subscriptionExpiresAt = subscriptionExpiresAt self.subscriptionRenewsAt = subscriptionRenewsAt } public func withBillingSummary(_ billingSummary: MiniMaxBillingSummary?) -> MiniMaxUsageSnapshot { MiniMaxUsageSnapshot( planName: self.planName, availablePrompts: self.availablePrompts, currentPrompts: self.currentPrompts, remainingPrompts: self.remainingPrompts, windowMinutes: self.windowMinutes, usedPercent: self.usedPercent, resetsAt: self.resetsAt, updatedAt: self.updatedAt, services: self.services, billingSummary: billingSummary, pointsBalance: self.pointsBalance, subscriptionExpiresAt: self.subscriptionExpiresAt, subscriptionRenewsAt: self.subscriptionRenewsAt) } } extension MiniMaxUsageSnapshot { public func toUsageSnapshot() -> UsageSnapshot { // If we have services array, use that for multi-service support if let services = self.services, !services.isEmpty { let primaryWindow = self.rateWindow(for: self.primaryService) let secondaryWindow = self.rateWindow(for: self.secondaryService) let tertiaryWindow = self.rateWindow(for: self.tertiaryService) let planName = self.planName?.trimmingCharacters(in: .whitespacesAndNewlines) let loginMethod = (planName?.isEmpty ?? true) ? nil : planName let identity = ProviderIdentitySnapshot( providerID: .minimax, accountEmail: nil, accountOrganization: nil, loginMethod: loginMethod) return UsageSnapshot( primary: primaryWindow, secondary: secondaryWindow, tertiary: tertiaryWindow, providerCost: self.pointsBalanceSnapshot(), minimaxUsage: self, subscriptionExpiresAt: self.subscriptionExpiresAt, subscriptionRenewsAt: self.subscriptionRenewsAt, updatedAt: self.updatedAt, identity: identity) } // Fallback to single-service mode for backward compatibility let used = max(0, min(100, self.usedPercent ?? 0)) let resetDescription = self.limitDescription() let primary = RateWindow( usedPercent: used, windowMinutes: self.windowMinutes, resetsAt: self.resetsAt, resetDescription: resetDescription) let planName = self.planName?.trimmingCharacters(in: .whitespacesAndNewlines) let loginMethod = (planName?.isEmpty ?? true) ? nil : planName let identity = ProviderIdentitySnapshot( providerID: .minimax, accountEmail: nil, accountOrganization: nil, loginMethod: loginMethod) return UsageSnapshot( primary: primary, secondary: nil, tertiary: nil, providerCost: self.pointsBalanceSnapshot(), minimaxUsage: self, subscriptionExpiresAt: self.subscriptionExpiresAt, subscriptionRenewsAt: self.subscriptionRenewsAt, updatedAt: self.updatedAt, identity: identity) } private func rateWindow(for service: MiniMaxServiceUsage?) -> RateWindow? { guard let service else { return nil } let windowMinutes = self.windowMinutes(for: service) return RateWindow( usedPercent: max(0, min(100, service.percent)), windowMinutes: windowMinutes, resetsAt: service.resetsAt, resetDescription: service.resetDescription) } private func limitDescription() -> String? { guard let availablePrompts, availablePrompts > 0 else { return self.windowDescription() } if let windowDescription = self.windowDescription() { return "\(availablePrompts) prompts / \(windowDescription)" } return "\(availablePrompts) prompts" } private func windowDescription() -> String? { guard let windowMinutes, windowMinutes > 0 else { return nil } if windowMinutes % (24 * 60) == 0 { let days = windowMinutes / (24 * 60) return "\(days) \(days == 1 ? "day" : "days")" } if windowMinutes % 60 == 0 { let hours = windowMinutes / 60 return "\(hours) \(hours == 1 ? "hour" : "hours")" } return "\(windowMinutes) \(windowMinutes == 1 ? "minute" : "minutes")" } private func windowMinutes(for service: MiniMaxServiceUsage) -> Int? { let windowType = service.windowType.lowercased().trimmingCharacters(in: .whitespacesAndNewlines) // Handle "Today" case - 24 hours = 1440 minutes if windowType == "today" { return 24 * 60 } if windowType == "weekly" { return 7 * 24 * 60 } // Handle time duration formats like "5 hours", "30 minutes", etc. let components = windowType.split(separator: " ") guard components.count >= 2 else { return nil } guard let value = Int(components[0]) else { return nil } let unit = components[1].lowercased() switch unit { case "hour", "hours", "h", "hr", "hrs": return value * 60 case "minute", "minutes", "min", "mins", "m": return value case "day", "days", "d": return value * 24 * 60 default: return nil } } private func pointsBalanceSnapshot() -> ProviderCostSnapshot? { guard let pointsBalance, pointsBalance >= 0 else { return nil } return ProviderCostSnapshot( used: pointsBalance, limit: 0, currencyCode: "Points", period: "MiniMax points balance", updatedAt: self.updatedAt) } }