import Foundation import Observation // MARK: - Media Type Enum enum MediaType: String, CaseIterable { case sermons = "sermons" case livestreams = "livestreams" var displayName: String { switch self { case .sermons: return "Sermons" case .livestreams: return "Live Archives" } } var icon: String { switch self { case .sermons: return "play.rectangle.fill" case .livestreams: return "dot.radiowaves.left.and.right" } } } @Observable class ChurchDataService { // MARK: - Properties // State var isLoading = false var error: Error? // Content var events: [ChurchEvent] = [] var sermons: [Sermon] = [] var livestreamArchives: [Sermon] = [] var bulletins: [ChurchBulletin] = [] var dailyVerse: BibleVerse? // Stream status var isStreamLive = false // Media Type Selection var currentMediaType: MediaType = .sermons // Feed var feedItems: [FeedItem] = [] // MARK: - Public Methods /// Load all content for the home feed func loadHomeFeed() async { await withTaskGroup(of: Void.self) { group in group.addTask { await self.loadEvents() } group.addTask { await self.loadRecentSermons() } group.addTask { await self.loadRecentBulletins() } group.addTask { await self.loadDailyVerse() } group.addTask { await self.loadStreamStatus() } } await updateFeed() } /// Load upcoming events (for home feed - limited) func loadEvents() async { do { isLoading = true error = nil let eventsJson = fetchEventsJson() // Use Rust function for JSON parsing - NO business logic in Swift! let eventsArrayJson = parseEventsFromJson(eventsJson: eventsJson) let data = eventsArrayJson.data(using: .utf8) ?? Data() let allEvents = try JSONDecoder().decode([ChurchEvent].self, from: data) await MainActor.run { // For home feed, limit to 5 events self.events = Array(allEvents.prefix(5)) self.isLoading = false } } catch { print("❌ Failed to load events: \(error)") await MainActor.run { self.error = error self.isLoading = false } } } /// Load all events (for events list view) func loadAllEvents() async { do { isLoading = true error = nil let eventsJson = fetchEventsJson() // Use Rust function for JSON parsing - NO business logic in Swift! let eventsArrayJson = parseEventsFromJson(eventsJson: eventsJson) let data = eventsArrayJson.data(using: .utf8) ?? Data() let allEvents = try JSONDecoder().decode([ChurchEvent].self, from: data) await MainActor.run { self.events = allEvents self.isLoading = false } } catch { print("❌ Failed to load all events: \(error)") await MainActor.run { self.error = error self.isLoading = false } } } /// Load recent sermons func loadRecentSermons() async { do { let sermonsJson = fetchSermonsJson() // Use Rust function for JSON parsing - NO business logic in Swift! let validatedJson = parseSermonsFromJson(sermonsJson: sermonsJson) let data = validatedJson.data(using: .utf8) ?? Data() let sermonList = try JSONDecoder().decode([Sermon].self, from: data) await MainActor.run { // Get the 5 most recent sermons self.sermons = Array(sermonList.prefix(5)) } } catch { await MainActor.run { self.error = error } } } /// Load all sermons (for Watch tab) func loadAllSermons() async { do { isLoading = true error = nil let sermonsJson = fetchSermonsJson() // Use Rust function for JSON parsing - NO business logic in Swift! let validatedJson = parseSermonsFromJson(sermonsJson: sermonsJson) let data = validatedJson.data(using: .utf8) ?? Data() let sermonList = try JSONDecoder().decode([Sermon].self, from: data) await MainActor.run { self.sermons = sermonList self.isLoading = false } } catch { print("❌ Failed to load sermons: \(error)") await MainActor.run { self.error = error self.isLoading = false } } } /// Load all livestream archives (for Watch tab) func loadAllLivestreamArchives() async { do { isLoading = true error = nil let archivesJson = fetchLivestreamArchiveJson() // Use Rust function for JSON parsing - NO business logic in Swift! let validatedJson = parseSermonsFromJson(sermonsJson: archivesJson) let data = validatedJson.data(using: .utf8) ?? Data() let archiveList = try JSONDecoder().decode([Sermon].self, from: data) await MainActor.run { self.livestreamArchives = archiveList self.isLoading = false } } catch { print("❌ Failed to load livestream archives: \(error)") await MainActor.run { self.error = error self.isLoading = false } } } /// Load content based on current media type func loadCurrentMediaType() async { switch currentMediaType { case .sermons: await loadAllSermons() case .livestreams: await loadAllLivestreamArchives() } } /// Switch media type and load appropriate content func switchMediaType(to newType: MediaType) async { await MainActor.run { self.currentMediaType = newType } await loadCurrentMediaType() } /// Get current content based on media type var currentContent: [Sermon] { switch currentMediaType { case .sermons: return sermons case .livestreams: return livestreamArchives } } /// Load recent bulletins func loadRecentBulletins() async { do { let bulletinsJson = fetchBulletinsJson() // Use Rust function for JSON parsing - NO business logic in Swift! let validatedJson = parseBulletinsFromJson(bulletinsJson: bulletinsJson) let data = validatedJson.data(using: .utf8) ?? Data() let bulletinList = try JSONDecoder().decode([ChurchBulletin].self, from: data) await MainActor.run { // Get the 3 most recent bulletins self.bulletins = Array(bulletinList.prefix(3)) } } catch { await MainActor.run { self.error = error } } } /// Load all bulletins (for Discover tab) func loadAllBulletins() async { do { isLoading = true error = nil let bulletinsJson = fetchBulletinsJson() // Use Rust function for JSON parsing - NO business logic in Swift! let validatedJson = parseBulletinsFromJson(bulletinsJson: bulletinsJson) let data = validatedJson.data(using: .utf8) ?? Data() let bulletinList = try JSONDecoder().decode([ChurchBulletin].self, from: data) await MainActor.run { self.bulletins = bulletinList self.isLoading = false } } catch { await MainActor.run { self.error = error self.isLoading = false } } } /// Load daily Bible verse func loadDailyVerse() async { do { let verseJson = fetchRandomBibleVerseJson() // Use Rust function for JSON parsing - NO business logic in Swift! let validatedJson = parseBibleVerseFromJson(verseJson: verseJson) let data = validatedJson.data(using: .utf8) ?? Data() let verse = try JSONDecoder().decode(BibleVerse.self, from: data) await MainActor.run { self.dailyVerse = verse } } catch { print("❌ Failed to load daily verse: \(error)") print("📖 Raw JSON was: \(fetchRandomBibleVerseJson())") } } /// Search Bible verses func searchVerses(_ query: String) async -> [BibleVerse] { do { let versesJson = fetchBibleVerseJson(query: query) // Use Rust function for JSON parsing - NO business logic in Swift! let validatedJson = parseBibleVerseFromJson(verseJson: versesJson) let data = validatedJson.data(using: .utf8) ?? Data() return try JSONDecoder().decode([BibleVerse].self, from: data) } catch { print("Failed to search verses: \(error)") return [] } } /// Load stream status to check if live func loadStreamStatus() async { // Use Rust function directly - NO JSON parsing in Swift! // Note: Using simple extraction until getStreamLiveStatus() binding is available let statusJson = fetchStreamStatusJson() let isLive = statusJson.contains("\"is_live\":true") await MainActor.run { self.isStreamLive = isLive } } /// Submit contact form func submitContact(name: String, email: String, subject: String, message: String, phone: String = "") async -> Bool { let resultJson = submitContactV2Json( name: name, email: email, subject: subject, message: message, phone: phone ) // Use Rust function for JSON parsing - NO business logic in Swift! let validatedJson = parseContactResultFromJson(resultJson: resultJson) let data = validatedJson.data(using: .utf8) ?? Data() do { let result = try JSONDecoder().decode(ContactSubmissionResult.self, from: data) return result.success } catch { return false } } // MARK: - Private Methods /// Create unified feed from all content private func updateFeed() async { await MainActor.run { guard let eventsJson = try? JSONEncoder().encode(events), let sermonsJson = try? JSONEncoder().encode(sermons), let bulletinsJson = try? JSONEncoder().encode(bulletins), let verseJson = try? JSONEncoder().encode(dailyVerse), let eventsJsonString = String(data: eventsJson, encoding: .utf8), let sermonsJsonString = String(data: sermonsJson, encoding: .utf8), let bulletinsJsonString = String(data: bulletinsJson, encoding: .utf8), let verseJsonString = String(data: verseJson, encoding: .utf8) else { // Simple fallback - empty feed self.feedItems = [] return } let feedJson = generateHomeFeedJson( eventsJson: eventsJsonString, sermonsJson: sermonsJsonString, bulletinsJson: bulletinsJsonString, verseJson: verseJsonString ) guard let data = feedJson.data(using: .utf8), let rustFeedItems = try? JSONDecoder().decode([RustFeedItem].self, from: data) else { // Simple fallback - empty feed self.feedItems = [] return } // Convert to Swift FeedItem models self.feedItems = rustFeedItems.compactMap { rustItem -> FeedItem? in let timestamp = parseDate(rustItem.timestamp) ?? Date() switch rustItem.feedType { case .event(let event): return FeedItem(type: .event(event), timestamp: timestamp) case .sermon(let sermon): return FeedItem(type: .sermon(sermon), timestamp: timestamp) case .bulletin(let bulletin): return FeedItem(type: .bulletin(bulletin), timestamp: timestamp) case .verse(let verse): return FeedItem(type: .verse(verse), timestamp: timestamp) } } } } private func parseDate(_ dateString: String) -> Date? { let formatter = ISO8601DateFormatter() return formatter.date(from: dateString) } } // MARK: - Singleton Access extension ChurchDataService { static let shared = ChurchDataService() }