1→// 2→// DataManager.swift 3→// KortexOS 4→// 5→// Central data management with UserDefaults persistence and 3-layer backup 6→// 7→ 8→import Foundation 9→import SwiftUI 10→import CloudKit 11→import os 12→ 13→@MainActor 14→class DataManager: ObservableObject { 15→ static let shared = DataManager() 16→ 17→ // MARK: - Published Properties 18→ 19→ @Published var rituals: [DailyRitual] = [] 20→ @Published var goals: [LifeGoal] = [] 21→ @Published var journalEntries: [JournalEntry] = [] 22→ @Published var userProgress: UserProgress = UserProgress() 23→ @Published var todaySnapshot: DailySnapshot = DailySnapshot.createForToday() 24→ 25→ // Undo support 26→ @Published private(set) var lastUndoableAction: UndoableAction? 27→ 28→ /// Represents an action that can be undone 29→ struct UndoableAction { 30→ let id = UUID() 31→ let description: String 32→ let undoAction: () -> Void 33→ let timestamp: Date = Date() 34→ 35→ /// Check if action is still valid (within 5 seconds) 36→ var isValid: Bool { 37→ Date().timeIntervalSince(timestamp) < 5.0 38→ } 39→ } 40→ 41→ // MARK: - Storage Keys 42→ 43→ private struct Keys { 44→ static let rituals = "KortexOS_Rituals" 45→ static let ritualsBackup1 = "KortexOS_Rituals_backup1" 46→ static let ritualsBackup2 = "KortexOS_Rituals_backup2" 47→ 48→ static let goals = "KortexOS_Goals" 49→ static let goalsBackup1 = "KortexOS_Goals_backup1" 50→ static let goalsBackup2 = "KortexOS_Goals_backup2" 51→ 52→ static let journal = "KortexOS_Journal" 53→ static let journalBackup1 = "KortexOS_Journal_backup1" 54→ static let journalBackup2 = "KortexOS_Journal_backup2" 55→ 56→ static let userProgress = "KortexOS_UserProgress" 57→ static let hasLaunchedBefore = "KortexOS_HasLaunchedBefore" 58→ } 59→ 60→ // MARK: - Private Properties 61→ 62→ private let defaults = UserDefaults.standard 63→ private let logger = Logger(subsystem: "com.kortexos", category: "DataManager") 64→ private var saveDebounceTask: Task? 65→ private let saveDebounceDelay: UInt64 = 500_000_000 // 500ms 66→ private let cloudKitManager = CloudKitManager.shared 67→ private var syncDebounceTask: Task? 68→ private let syncDebounceDelay: UInt64 = 1_000_000_000 // 1 second 69→ 70→ // MARK: - Initialization 71→ 72→ private init() { 73→ loadAllData() 74→ setupDailyReset() 75→ setupCloudKitCallbacks() 76→ } 77→ 78→ // MARK: - CloudKit Integration 79→ 80→ private func setupCloudKitCallbacks() { 81→ cloudKitManager.onRecordsReceived = { [weak self] records in 82→ Task { @MainActor [weak self] in 83→ self?.handleReceivedRecords(records) 84→ } 85→ } 86→ 87→ cloudKitManager.onRecordsDeleted = { [weak self] recordIDs in 88→ Task { @MainActor [weak self] in 89→ self?.handleDeletedRecords(recordIDs) 90→ } 91→ } 92→ } 93→ 94→ private func handleReceivedRecords(_ records: [CKRecord]) { 95→ for record in records { 96→ switch record.recordType { 97→ case DailyRitual.recordType: 98→ if let serverRitual = DailyRitual.from(record: record) { 99→ mergeRitual(serverRitual) 100→ } 101→ case LifeGoal.recordType: 102→ if let serverGoal = LifeGoal.from(record: record) { 103→ mergeGoal(serverGoal) 104→ } 105→ case JournalEntry.recordType: 106→ if let serverEntry = JournalEntry.from(record: record) { 107→ mergeJournalEntry(serverEntry) 108→ } 109→ case UserProgress.recordType: 110→ if let serverProgress = UserProgress.from(record: record) { 111→ mergeUserProgress(serverProgress) 112→ } 113→ default: 114→ logger.warning("Unknown record type: \(record.recordType)") 115→ } 116→ } 117→ 118→ // Save locally after merging 119→ saveRituals() 120→ saveGoals() 121→ saveJournalEntries() 122→ saveUserProgress() 123→ updateTodaySnapshot() 124→ 125→ logger.info("Merged \(records.count) records from CloudKit") 126→ } 127→ 128→ private func handleDeletedRecords(_ recordIDs: [CKRecord.ID]) { 129→ for recordID in recordIDs { 130→ let recordName = recordID.recordName 131→ 132→ // Extract UUID from record name (format: "RecordType_UUID") 133→ if let uuidString = recordName.split(separator: "_").last, 134→ let uuid = UUID(uuidString: String(uuidString)) { 135→ 136→ if recordName.hasPrefix(DailyRitual.recordType) { 137→ rituals.removeAll { $0.id == uuid } 138→ } else if recordName.hasPrefix(LifeGoal.recordType) { 139→ goals.removeAll { $0.id == uuid } 140→ } else if recordName.hasPrefix(JournalEntry.recordType) { 141→ journalEntries.removeAll { $0.id == uuid } 142→ } 143→ } 144→ } 145→ 146→ saveRituals() 147→ saveGoals() 148→ saveJournalEntries() 149→ updateTodaySnapshot() 150→ 151→ logger.info("Deleted \(recordIDs.count) records from CloudKit") 152→ } 153→ 154→ // MARK: - Merge Functions 155→ 156→ private func mergeRitual(_ server: DailyRitual) { 157→ if let index = rituals.firstIndex(where: { $0.id == server.id }) { 158→ rituals[index] = rituals[index].merge(with: server) 159→ } else { 160→ rituals.append(server) 161→ } 162→ } 163→ 164→ private func mergeGoal(_ server: LifeGoal) { 165→ if let index = goals.firstIndex(where: { $0.id == server.id }) { 166→ goals[index] = goals[index].merge(with: server) 167→ } else { 168→ goals.append(server) 169→ } 170→ } 171→ 172→ private func mergeJournalEntry(_ server: JournalEntry) { 173→ if let index = journalEntries.firstIndex(where: { $0.id == server.id }) { 174→ journalEntries[index] = journalEntries[index].merge(with: server) 175→ } else { 176→ journalEntries.insert(server, at: 0) 177→ } 178→ } 179→ 180→ private func mergeUserProgress(_ server: UserProgress) { 181→ userProgress = userProgress.merge(with: server) 182→ } 183→ 184→ // MARK: - Sync to CloudKit 185→ 186→ private func syncToCloudKit(_ items: [T]) { 187→ guard cloudKitManager.isSyncEnabled else { return } 188→ 189→ syncDebounceTask?.cancel() 190→ syncDebounceTask = Task { 191→ do { 192→ try await Task.sleep(nanoseconds: syncDebounceDelay) 193→ 194→ let zoneID = CloudKitConfiguration.zoneID 195→ let records = items.map { $0.toRecord(in: zoneID) } 196→ 197→ try await cloudKitManager.pushRecords(records) 198→ logger.debug("Synced \(records.count) \(T.recordType) records to CloudKit") 199→ } catch { 200→ logger.error("Failed to sync to CloudKit: \(error.localizedDescription)") 201→ } 202→ } 203→ } 204→ 205→ private func syncSingleToCloudKit(_ item: T) { 206→ guard cloudKitManager.isSyncEnabled else { return } 207→ 208→ Task { 209→ let record = item.toRecord(in: CloudKitConfiguration.zoneID) 210→ do { 211→ try await cloudKitManager.pushRecords([record]) 212→ logger.debug("Synced single \(T.recordType) to CloudKit") 213→ } catch { 214→ logger.error("Failed to sync single record: \(error.localizedDescription)") 215→ } 216→ } 217→ } 218→ 219→ private func deleteFromCloudKit(_ item: T) { 220→ guard cloudKitManager.isSyncEnabled else { return } 221→ 222→ Task { 223→ let recordID = item.recordID(in: CloudKitConfiguration.zoneID) 224→ do { 225→ try await cloudKitManager.deleteRecords([recordID]) 226→ logger.debug("Deleted \(T.recordType) from CloudKit") 227→ } catch { 228→ logger.error("Failed to delete from CloudKit: \(error.localizedDescription)") 229→ } 230→ } 231→ } 232→ 233→ /// Perform initial sync of all local data to CloudKit 234→ func performInitialSync() async { 235→ guard cloudKitManager.isSyncEnabled else { return } 236→ 237→ logger.info("Starting initial CloudKit sync...") 238→ 239→ // Push all local data 240→ let zoneID = CloudKitConfiguration.zoneID 241→ var allRecords: [CKRecord] = [] 242→ 243→ allRecords.append(contentsOf: rituals.map { $0.toRecord(in: zoneID) }) 244→ allRecords.append(contentsOf: goals.map { $0.toRecord(in: zoneID) }) 245→ allRecords.append(contentsOf: journalEntries.map { $0.toRecord(in: zoneID) }) 246→ allRecords.append(userProgress.toRecord(in: zoneID)) 247→ 248→ do { 249→ try await cloudKitManager.pushRecords(allRecords) 250→ logger.info("Initial sync completed: \(allRecords.count) records") 251→ } catch { 252→ logger.error("Initial sync failed: \(error.localizedDescription)") 253→ } 254→ 255→ // Then pull any changes from other devices 256→ await cloudKitManager.pullChanges() 257→ } 258→ 259→ // MARK: - Load Data 260→ 261→ func loadAllData() { 262→ loadRituals() 263→ loadGoals() 264→ loadJournalEntries() 265→ loadUserProgress() 266→ updateTodaySnapshot() 267→ checkFirstLaunch() 268→ 269→ logger.info("DataManager loaded: \(self.rituals.count) rituals, \(self.goals.count) goals, \(self.journalEntries.count) journal entries") 270→ } 271→ 272→ private func checkFirstLaunch() { 273→ let isFirstLaunch = !defaults.bool(forKey: Keys.hasLaunchedBefore) 274→ let goalsAreEmpty = goals.isEmpty 275→ 276→ // Populate if first launch OR if goals are empty (ensures ZAIROS data is always present) 277→ if isFirstLaunch || goalsAreEmpty { 278→ populateInitialData() 279→ defaults.set(true, forKey: Keys.hasLaunchedBefore) 280→ } 281→ } 282→ 283→ private func populateInitialData() { 284→ // Add sample goals from ZAIROS methodology 285→ goals = LifeGoal.allSampleGoals 286→ saveGoals() 287→ 288→ logger.info("Populated initial ZAIROS data") 289→ } 290→ 291→ // MARK: - Rituals 292→ 293→ private func loadRituals() { 294→ rituals = load(key: Keys.rituals, backup1: Keys.ritualsBackup1, backup2: Keys.ritualsBackup2) ?? [] 295→ ensureTodayRitual() 296→ } 297→ 298→ private func ensureTodayRitual() { 299→ let today = Calendar.current.startOfDay(for: Date()) 300→ if !rituals.contains(where: { DailyRitual.isSameDay($0.date, today) }) { 301→ let newRitual = DailyRitual(date: today) 302→ rituals.insert(newRitual, at: 0) 303→ saveRituals() 304→ } 305→ } 306→ 307→ var todayRitual: DailyRitual? { 308→ get { 309→ rituals.first { DailyRitual.isToday($0.date) } 310→ } 311→ set { 312→ if let newValue = newValue, 313→ let index = rituals.firstIndex(where: { DailyRitual.isToday($0.date) }) { 314→ rituals[index] = newValue 315→ saveRituals() 316→ updateTodaySnapshot() 317→ } 318→ } 319→ } 320→ 321→ func toggleRitualItem(_ itemId: UUID) { 322→ guard var ritual = todayRitual else { return } 323→ ritual.toggleItem(itemId) 324→ todayRitual = ritual 325→ 326→ // Check if ritual is complete for streak 327→ if ritual.isComplete { 328→ userProgress.updateStreak(completedToday: true) 329→ saveUserProgress() 330→ } 331→ } 332→ 333→ func addPushups(_ count: Int) { 334→ guard var ritual = todayRitual else { return } 335→ let previousRitualCount = ritual.pushupCount 336→ let previousProgressCount = userProgress.totalPushups 337→ 338→ ritual.addPushups(count) 339→ todayRitual = ritual 340→ 341→ let added = ritual.pushupCount - previousRitualCount 342→ userProgress.addPushups(added) 343→ saveUserProgress() 344→ 345→ // Create undoable action 346→ lastUndoableAction = UndoableAction( 347→ description: "+\(added) flexões", 348→ undoAction: { [weak self] in 349→ self?.setPushups(previousRitualCount, progressTotal: previousProgressCount) 350→ } 351→ ) 352→ 353→ // Show toast with undo option 354→ ToastManager.shared.undoable("+\(added) flexões adicionadas") { [weak self] in 355→ self?.executeUndo() 356→ } 357→ } 358→ 359→ func addSquats(_ count: Int) { 360→ guard var ritual = todayRitual else { return } 361→ let previousRitualCount = ritual.squatCount 362→ let previousProgressCount = userProgress.totalSquats 363→ 364→ ritual.addSquats(count) 365→ todayRitual = ritual 366→ 367→ let added = ritual.squatCount - previousRitualCount 368→ userProgress.addSquats(added) 369→ saveUserProgress() 370→ 371→ // Create undoable action 372→ lastUndoableAction = UndoableAction( 373→ description: "+\(added) agachamentos", 374→ undoAction: { [weak self] in 375→ self?.setSquats(previousRitualCount, progressTotal: previousProgressCount) 376→ } 377→ ) 378→ 379→ // Show toast with undo option 380→ ToastManager.shared.undoable("+\(added) agachamentos adicionados") { [weak self] in 381→ self?.executeUndo() 382→ } 383→ } 384→ 385→ // MARK: - Undo Support 386→ 387→ /// Execute the last undoable action if still valid 388→ func executeUndo() { 389→ guard let action = lastUndoableAction, action.isValid else { 390→ logger.info("No valid undo action available") 391→ return 392→ } 393→ 394→ action.undoAction() 395→ lastUndoableAction = nil 396→ logger.info("Undo executed: \(action.description)") 397→ ToastManager.shared.success("Ação desfeita") 398→ } 399→ 400→ /// Set pushups to a specific value (used for undo) 401→ private func setPushups(_ ritualCount: Int, progressTotal: Int) { 402→ guard var ritual = todayRitual else { return } 403→ 404→ // Calculate the difference for progress 405→ let currentRitualCount = ritual.pushupCount 406→ let difference = currentRitualCount - ritualCount 407→ 408→ // Reset ritual pushup count 409→ ritual.pushupCount = ritualCount 410→ // Update completion status manually 411→ if let pushupIndex = ritual.items.firstIndex(where: { $0.title == "200 Flexões" }) { 412→ ritual.items[pushupIndex].isCompleted = ritualCount >= 200 413→ ritual.items[pushupIndex].completedAt = ritualCount >= 200 ? Date() : nil 414→ } 415→ todayRitual = ritual 416→ 417→ // Adjust progress total 418→ userProgress.totalPushups = progressTotal 419→ saveUserProgress() 420→ } 421→ 422→ /// Set squats to a specific value (used for undo) 423→ private func setSquats(_ ritualCount: Int, progressTotal: Int) { 424→ guard var ritual = todayRitual else { return } 425→ 426→ // Calculate the difference for progress 427→ let currentRitualCount = ritual.squatCount 428→ let difference = currentRitualCount - ritualCount 429→ 430→ // Reset ritual squat count 431→ ritual.squatCount = ritualCount 432→ // Update completion status manually 433→ if let squatIndex = ritual.items.firstIndex(where: { $0.title == "200 Agachamentos" }) { 434→ ritual.items[squatIndex].isCompleted = ritualCount >= 200 435→ ritual.items[squatIndex].completedAt = ritualCount >= 200 ? Date() : nil 436→ } 437→ todayRitual = ritual 438→ 439→ // Adjust progress total 440→ userProgress.totalSquats = progressTotal 441→ saveUserProgress() 442→ } 443→ 444→ func addMeditationMinutes(_ minutes: Int) { 445→ guard var ritual = todayRitual else { return } 446→ let previousMinutes = ritual.meditationMinutes 447→ ritual.addMeditationMinutes(minutes) 448→ todayRitual = ritual 449→ 450→ let added = ritual.meditationMinutes - previousMinutes 451→ userProgress.addMeditationMinutes(added) 452→ saveUserProgress() 453→ } 454→ 455→ func resetPushups() { 456→ guard var ritual = todayRitual else { return } 457→ ritual.resetPushups() 458→ todayRitual = ritual 459→ } 460→ 461→ func resetSquats() { 462→ guard var ritual = todayRitual else { return } 463→ ritual.resetSquats() 464→ todayRitual = ritual 465→ } 466→ 467→ private func saveRituals() { 468→ save(rituals, key: Keys.rituals, backup1: Keys.ritualsBackup1, backup2: Keys.ritualsBackup2) 469→ 470→ // Sync today's ritual to CloudKit 471→ if let todayRitual = todayRitual { 472→ syncSingleToCloudKit(todayRitual) 473→ } 474→ } 475→ 476→ // MARK: - Goals 477→ 478→ private func loadGoals() { 479→ goals = load(key: Keys.goals, backup1: Keys.goalsBackup1, backup2: Keys.goalsBackup2) ?? [] 480→ } 481→ 482→ func addGoal(_ goal: LifeGoal) { 483→ var newGoal = goal 484→ newGoal.modifiedAt = Date() 485→ goals.append(newGoal) 486→ saveGoals() 487→ syncSingleToCloudKit(newGoal) 488→ } 489→ 490→ func updateGoal(_ goal: LifeGoal) { 491→ if let index = goals.firstIndex(where: { $0.id == goal.id }) { 492→ var updatedGoal = goal 493→ updatedGoal.modifiedAt = Date() 494→ goals[index] = updatedGoal 495→ saveGoals() 496→ updateTodaySnapshot() 497→ syncSingleToCloudKit(updatedGoal) 498→ } 499→ } 500→ 501→ func deleteGoal(_ goal: LifeGoal) { 502→ goals.removeAll { $0.id == goal.id } 503→ // Remove from parent's subGoals 504→ for i in goals.indices { 505→ goals[i].removeSubGoal(goal.id) 506→ } 507→ saveGoals() 508→ deleteFromCloudKit(goal) 509→ } 510→ 511→ func completeGoal(_ goalId: UUID) { 512→ if let index = goals.firstIndex(where: { $0.id == goalId }) { 513→ goals[index].markCompleted() 514→ saveGoals() 515→ updateTodaySnapshot() 516→ } 517→ } 518→ 519→ func goals(for timeframe: GoalTimeframe) -> [LifeGoal] { 520→ goals.filter { $0.timeframe == timeframe } 521→ .sorted { $0.createdAt < $1.createdAt } 522→ } 523→ 524→ func subGoals(for parentId: UUID) -> [LifeGoal] { 525→ goals.filter { $0.parentGoalId == parentId } 526→ } 527→ 528→ private func saveGoals() { 529→ save(goals, key: Keys.goals, backup1: Keys.goalsBackup1, backup2: Keys.goalsBackup2) 530→ } 531→ 532→ // MARK: - Journal Entries 533→ 534→ private func loadJournalEntries() { 535→ journalEntries = load(key: Keys.journal, backup1: Keys.journalBackup1, backup2: Keys.journalBackup2) ?? [] 536→ } 537→ 538→ func addJournalEntry(_ entry: JournalEntry) { 539→ var newEntry = entry 540→ newEntry.modifiedAt = Date() 541→ journalEntries.insert(newEntry, at: 0) 542→ userProgress.addJournalEntry() 543→ saveJournalEntries() 544→ saveUserProgress() 545→ updateTodaySnapshot() 546→ syncSingleToCloudKit(newEntry) 547→ } 548→ 549→ func updateJournalEntry(_ entry: JournalEntry) { 550→ if let index = journalEntries.firstIndex(where: { $0.id == entry.id }) { 551→ var updatedEntry = entry 552→ updatedEntry.modifiedAt = Date() 553→ journalEntries[index] = updatedEntry 554→ saveJournalEntries() 555→ syncSingleToCloudKit(updatedEntry) 556→ } 557→ } 558→ 559→ func deleteJournalEntry(_ entry: JournalEntry) { 560→ journalEntries.removeAll { $0.id == entry.id } 561→ saveJournalEntries() 562→ updateTodaySnapshot() 563→ deleteFromCloudKit(entry) 564→ } 565→ 566→ func journalEntries(for week: Int, year: Int) -> [JournalEntry] { 567→ journalEntries.filter { 568→ let entryWeek = Calendar.current.component(.weekOfYear, from: $0.date) 569→ let entryYear = Calendar.current.component(.year, from: $0.date) 570→ return entryWeek == week && entryYear == year 571→ } 572→ } 573→ 574→ var todayEntries: [JournalEntry] { 575→ journalEntries.filter { $0.isToday } 576→ } 577→ 578→ var thisWeekEntries: [JournalEntry] { 579→ let calendar = Calendar.current 580→ let currentWeek = calendar.component(.weekOfYear, from: Date()) 581→ let currentYear = calendar.component(.year, from: Date()) 582→ return journalEntries(for: currentWeek, year: currentYear) 583→ } 584→ 585→ private func saveJournalEntries() { 586→ save(journalEntries, key: Keys.journal, backup1: Keys.journalBackup1, backup2: Keys.journalBackup2) 587→ } 588→ 589→ // MARK: - User Progress 590→ 591→ private func loadUserProgress() { 592→ if let data = defaults.data(forKey: Keys.userProgress), 593→ let decoded = try? JSONDecoder().decode(UserProgress.self, from: data) { 594→ userProgress = decoded 595→ } 596→ userProgress.checkStreakStatus() 597→ } 598→ 599→ private func saveUserProgress() { 600→ userProgress.modifiedAt = Date() 601→ if let encoded = try? JSONEncoder().encode(userProgress) { 602→ defaults.set(encoded, forKey: Keys.userProgress) 603→ } 604→ syncSingleToCloudKit(userProgress) 605→ } 606→ 607→ // MARK: - Today Snapshot 608→ 609→ func updateTodaySnapshot() { 610→ var snapshot = DailySnapshot.createForToday() 611→ 612→ if let ritual = todayRitual { 613→ snapshot.updateFromRitual(ritual) 614→ } 615→ 616→ snapshot.updateFromJournal(journalEntries) 617→ snapshot.updateFromGoals(goals) 618→ 619→ todaySnapshot = snapshot 620→ } 621→ 622→ // MARK: - Daily Reset 623→ 624→ private func setupDailyReset() { 625→ // Check if we need to create a new ritual for today 626→ let calendar = Calendar.current 627→ let now = Date() 628→ let tomorrow = calendar.startOfDay(for: calendar.date(byAdding: .day, value: 1, to: now)!) 629→ let timeUntilMidnight = tomorrow.timeIntervalSince(now) 630→ 631→ Task { 632→ try? await Task.sleep(nanoseconds: UInt64(timeUntilMidnight * 1_000_000_000)) 633→ await MainActor.run { 634→ self.ensureTodayRitual() 635→ self.updateTodaySnapshot() 636→ self.setupDailyReset() 637→ } 638→ } 639→ } 640→ 641→ // MARK: - Generic Save/Load with 3-Layer Backup 642→ 643→ private func save(_ data: T, key: String, backup1: String, backup2: String) { 644→ saveDebounceTask?.cancel() 645→ saveDebounceTask = Task { 646→ do { 647→ try await Task.sleep(nanoseconds: saveDebounceDelay) 648→ 649→ // Rotate backups 650→ if let current = defaults.data(forKey: key) { 651→ if let backup1Data = defaults.data(forKey: backup1) { 652→ defaults.set(backup1Data, forKey: backup2) 653→ } 654→ defaults.set(current, forKey: backup1) 655→ } 656→ 657→ // Save new data 658→ let encoded = try JSONEncoder().encode(data) 659→ defaults.set(encoded, forKey: key) 660→ 661→ logger.debug("Saved data for key: \(key)") 662→ } catch { 663→ logger.error("Failed to save data for key \(key): \(error.localizedDescription)") 664→ } 665→ } 666→ } 667→ 668→ private func load(key: String, backup1: String, backup2: String) -> T? { 669→ // Try primary 670→ if let data = defaults.data(forKey: key), 671→ let decoded = try? JSONDecoder().decode(T.self, from: data) { 672→ return decoded 673→ } 674→ 675→ // Try backup 1 676→ if let data = defaults.data(forKey: backup1), 677→ let decoded = try? JSONDecoder().decode(T.self, from: data) { 678→ logger.warning("Loaded from backup1 for key: \(key)") 679→ return decoded 680→ } 681→ 682→ // Try backup 2 683→ if let data = defaults.data(forKey: backup2), 684→ let decoded = try? JSONDecoder().decode(T.self, from: data) { 685→ logger.warning("Loaded from backup2 for key: \(key)") 686→ return decoded 687→ } 688→ 689→ return nil 690→ } 691→ 692→ // MARK: - Reset Data (for testing) 693→ 694→ func resetAllData() { 695→ rituals = [] 696→ goals = [] 697→ journalEntries = [] 698→ userProgress = UserProgress() 699→ 700→ let keys = [ 701→ Keys.rituals, Keys.ritualsBackup1, Keys.ritualsBackup2, 702→ Keys.goals, Keys.goalsBackup1, Keys.goalsBackup2, 703→ Keys.journal, Keys.journalBackup1, Keys.journalBackup2, 704→ Keys.userProgress, Keys.hasLaunchedBefore 705→ ] 706→ 707→ keys.forEach { defaults.removeObject(forKey: $0) } 708→ 709→ ensureTodayRitual() 710→ populateInitialData() 711→ updateTodaySnapshot() 712→ 713→ logger.info("All data reset") 714→ } 715→} 716→ Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.