import Foundation @Observable final class BugStore { static let shared = BugStore() private(set) var bugs: [Bug] = [] private(set) var unseenBugs: Set = [] private(set) var lastUpdated: Date? private(set) var isLoading = false private(set) var error: Error? var activeBugs: [Bug] { bugs.filter { $0.status != .closed && $0.status != .resolved } } var unseenActiveCount: Int { activeBugs.filter { unseenBugs.contains($0.id) }.count } var bugsSortedByPriority: [Bug] { activeBugs.sorted { lhs, rhs in if lhs.priority != rhs.priority { return lhs.priority < rhs.priority } return lhs.createdAt < rhs.createdAt } } private init() { unseenBugs = AppStateService.shared.persistedSeenBugs } func update(with newBugs: [Bug]) { let oldBugs = bugs let oldIDs = Set(oldBugs.map(\.id)) let newIDs = Set(newBugs.map(\.id)) let added = newIDs.subtracting(oldIDs) unseenBugs.formUnion(added) let changes = detectChanges(old: oldBugs, new: newBugs) if !changes.isEmpty { Task { await NotificationService.shared.handleChanges(changes) } } bugs = newBugs lastUpdated = Date() error = nil AppStateService.shared.updateSeenBugs(unseenBugs) } func markSeen(_ bugID: String) { unseenBugs.remove(bugID) AppStateService.shared.updateSeenBugs(unseenBugs) } func markAllSeen() { unseenBugs.removeAll() AppStateService.shared.updateSeenBugs(unseenBugs) } func setLoading(_ loading: Bool) { isLoading = loading } func setError(_ error: Error?) { self.error = error } private func detectChanges(old: [Bug], new: [Bug]) -> [BugChange] { let oldMap = Dictionary(uniqueKeysWithValues: old.map { ($0.id, $0) }) var changes: [BugChange] = [] for newBug in new { guard let oldBug = oldMap[newBug.id] else { changes.append( BugChange(type: .newBug, bug: newBug, oldStatus: nil, oldAssignee: nil) ) continue } if oldBug.status != newBug.status { changes.append( BugChange(type: .statusChanged, bug: newBug, oldStatus: oldBug.status, oldAssignee: nil) ) } if oldBug.priority != newBug.priority { changes.append( BugChange(type: .priorityChanged, bug: newBug, oldStatus: nil, oldAssignee: nil) ) } if oldBug.assignee != newBug.assignee { changes.append( BugChange( type: .assigneeChanged, bug: newBug, oldStatus: nil, oldAssignee: oldBug.assignee ) ) } } return changes } }