// // DataControllerADD.swift // Reflect // // SwiftData CREATE operations. // import SwiftData import Foundation import os.log private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.88oakapps.reflect", category: "DataControllerADD") extension DataController { func add(mood: Mood, forDate date: Date, entryType: EntryType) { // Delete ALL existing entries for this date (handles duplicates) let existing = getAllEntries(byDate: date) for entry in existing { cleanupPhotoIfNeeded(for: entry) modelContext.delete(entry) } if !existing.isEmpty { do { try modelContext.save() } catch { logger.error("Failed to save after deleting existing entries: \(error.localizedDescription)") } } let entry = MoodEntryModel( forDate: date, mood: mood, entryType: entryType ) modelContext.insert(entry) AnalyticsManager.shared.track(.moodLogged(mood: mood.rawValue, entryType: String(describing: entryType))) saveAndRunDataListeners() } func fillInMissingDates() { let currentOnboarding = UserDefaultsStore.getOnboarding() var endDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: currentOnboarding) // Since it's for views, take away the last date so vote is enabled guard let adjustedEndDate = Calendar.current.date(byAdding: .day, value: -1, to: endDate) else { logger.error("Failed to calculate adjusted end date") return } endDate = adjustedEndDate let descriptor = FetchDescriptor( sortBy: [SortDescriptor(\.forDate, order: .reverse)] ) let entries: [MoodEntryModel] do { entries = try modelContext.fetch(descriptor) } catch { logger.error("Failed to fetch entries for fill-in: \(error.localizedDescription)") return } guard let firstEntry = entries.last else { return } let allDates: [Date] = Date.dates(from: firstEntry.forDate, toDate: endDate, includingToDate: true).compactMap { Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: $0) } let existingDates: Set = Set(entries.compactMap { Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: $0.forDate) }) let missing = Array(Set(allDates).subtracting(existingDates)).sorted(by: >) guard !missing.isEmpty else { return } // Batch insert all missing dates without triggering listeners for date in missing { // Add 12 hours to avoid UTC offset issues guard let adjustedDate = Calendar.current.date(byAdding: .hour, value: 12, to: date) else { logger.error("Failed to calculate adjusted date for \(date)") continue } let entry = MoodEntryModel( forDate: adjustedDate, mood: .missing, entryType: .filledInMissing ) modelContext.insert(entry) } // Single save and listener notification at the end saveAndRunDataListeners() AnalyticsManager.shared.track(.missingEntriesFilled(count: missing.count)) } func fixWrongWeekdays() { let data = getData(startDate: Date(timeIntervalSince1970: 0), endDate: Date(), includedDays: []) for entry in data { entry.weekDay = Calendar.current.component(.weekday, from: entry.forDate) } saveAndRunDataListeners() } func removeNoForDates() { // Note: With SwiftData's non-optional forDate, this is essentially a no-op // Keeping for API compatibility } /// Batch insert mood entries without per-entry analytics or listener notifications. /// Used for CSV import where side effects should fire once at the end. func addBatch(entries: [(mood: Mood, date: Date, entryType: EntryType)]) { for (mood, date, entryType) in entries { let existing = getAllEntries(byDate: date) for entry in existing { cleanupPhotoIfNeeded(for: entry) modelContext.delete(entry) } let entry = MoodEntryModel(forDate: date, mood: mood, entryType: entryType) modelContext.insert(entry) } saveAndRunDataListeners() } }