import Foundation // MARK: - BlindBox Async Polling Sequences enum BlindBoxPolling { /// Poll a single blind box until it becomes "Unopened". /// Yields once when ready, then finishes. static func singleBox(boxId: String, intervalSeconds: Double = 2.0) -> AsyncThrowingStream { AsyncThrowingStream { continuation in let task = Task { while !Task.isCancelled { do { let result = try await BlindBoxApi.shared.getBlindBox(boxId: boxId) if let data = result { if data.status.lowercased() == "unopened" { continuation.yield(data) continuation.finish() break } } try await Task.sleep(nanoseconds: UInt64(intervalSeconds * 1_000_000_000)) } catch is CancellationError { continuation.finish() break } catch { continuation.finish(throwing: error) break } } } continuation.onTermination = { @Sendable _ in task.cancel() } } } /// Poll blind box list and yield first unopened box when available. /// Yields once when found, then finishes. static func firstUnopened(intervalSeconds: Double = 2.0) -> AsyncThrowingStream { AsyncThrowingStream { continuation in let task = Task { while !Task.isCancelled { do { let list = try await BlindBoxApi.shared.getBlindBoxList() if let item = list?.first(where: { $0.status.lowercased() == "unopened" }) { continuation.yield(item) continuation.finish() break } try await Task.sleep(nanoseconds: UInt64(intervalSeconds * 1_000_000_000)) } catch is CancellationError { continuation.finish() break } catch { continuation.finish(throwing: error) break } } } continuation.onTermination = { @Sendable _ in task.cancel() } } } }