import Foundation import Combine final class EventDetailViewModel: ObservableObject { enum State: Equatable { case idle case loading case loaded(EventItem) case unavailable case error } @Published private(set) var state: State = .idle @Published var isLoading = false @Published var errorMessage = "" @Published var isShowPopup = false private let eventId: Int private let repository = EventRepository() private var subscription: AnyCancellable? private var activeRequestID: UUID? private var isUnavailableDismissalPending = false init(eventId: Int) { self.eventId = eventId } var participationURL: URL? { guard case .loaded(let event) = state, let link = event.link, let url = URL(string: link), let scheme = url.scheme?.lowercased(), ["http", "https"].contains(scheme), let host = url.host, !host.isEmpty else { return nil } return url } func load() { guard state == .idle else { return } guard eventId > 0 else { showUnavailable() return } isLoading = true state = .loading let requestID = UUID() activeRequestID = requestID subscription = repository.getEventDetail(id: eventId) .receive(on: DispatchQueue.main) .sink { [weak self] _ in guard let self, self.activeRequestID == requestID else { return } self.activeRequestID = nil self.showError() } receiveValue: { [weak self] response in guard let self, self.activeRequestID == requestID else { return } self.activeRequestID = nil self.isLoading = false guard response.statusCode == 200 else { self.showError() return } do { let decoder = JSONDecoder() let envelope = try decoder.decode(ApiResponseWithoutData.self, from: response.data) guard envelope.success else { self.showUnavailable() return } let decoded = try decoder.decode(ApiResponse.self, from: response.data) guard let event = decoded.data, event.id > 0, event.id == self.eventId else { self.showError() return } self.state = .loaded(event) } catch { self.showError() } } } func cancel() { activeRequestID = nil isUnavailableDismissalPending = false subscription?.cancel() subscription = nil isLoading = false if state == .loading { state = .idle } } func takeUnavailableDismissal() -> Bool { guard isUnavailableDismissalPending else { return false } isUnavailableDismissalPending = false return true } private func showUnavailable() { isLoading = false isUnavailableDismissalPending = true state = .unavailable } private func showError() { isLoading = false errorMessage = I18n.Common.commonError isShowPopup = true state = .error } }