// // DataControllerDELETE.swift // Reflect // // SwiftData DELETE operations. // import SwiftData import Foundation import os.log extension DataController { // MARK: - Photo Cleanup func cleanupPhotoIfNeeded(for entry: MoodEntryModel) { if let photoID = entry.photoID { if !PhotoManager.shared.deletePhoto(id: photoID) { AppLogger.general.warning("Failed to delete orphaned photo \(photoID.uuidString) for entry on \(entry.forDate)") } } } // MARK: - Delete Operations func clearDB() { do { // Clean up photo files before batch delete let descriptor = FetchDescriptor() if let allEntries = try? modelContext.fetch(descriptor) { for entry in allEntries { cleanupPhotoIfNeeded(for: entry) } } try modelContext.delete(model: MoodEntryModel.self) saveAndRunDataListeners() AnalyticsManager.shared.track(.allDataCleared) } catch { AppLogger.general.error("Failed to clear database: \(error.localizedDescription)") } } func deleteLast(numberOfEntries: Int) { guard let startDate = Calendar.current.date(byAdding: .day, value: -numberOfEntries, to: Date()) else { return } let entries = getData(startDate: startDate, endDate: Date(), includedDays: []) for entry in entries { cleanupPhotoIfNeeded(for: entry) modelContext.delete(entry) } saveAndRunDataListeners() } func deleteRandomFromLast(numberOfEntries: Int) { guard let startDate = Calendar.current.date(byAdding: .day, value: -numberOfEntries, to: Date()) else { return } let entries = getData(startDate: startDate, endDate: Date(), includedDays: []) for entry in entries where Bool.random() { cleanupPhotoIfNeeded(for: entry) modelContext.delete(entry) } saveAndRunDataListeners() } /// Get ALL entries for a specific date (not just the first one) func getAllEntries(byDate date: Date) -> [MoodEntryModel] { let startDate = Calendar.current.startOfDay(for: date) let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate)! let descriptor = FetchDescriptor( predicate: #Predicate { entry in entry.forDate >= startDate && entry.forDate < endDate }, sortBy: [SortDescriptor(\.forDate, order: .forward)] ) return (try? modelContext.fetch(descriptor)) ?? [] } /// Delete ALL entries for a specific date func deleteAllEntries(forDate date: Date) { let entries = getAllEntries(byDate: date) for entry in entries { cleanupPhotoIfNeeded(for: entry) modelContext.delete(entry) } saveAndRunDataListeners() } /// Find and remove duplicate entries, keeping only the most recent for each date /// Returns the number of duplicates removed @discardableResult func removeDuplicates() -> Int { let allEntries = getData(startDate: Date(timeIntervalSince1970: 0), endDate: Date(), includedDays: []) // Group by day var entriesByDay = [Date: [MoodEntryModel]]() let calendar = Calendar.current for entry in allEntries { let dayStart = calendar.startOfDay(for: entry.forDate) entriesByDay[dayStart, default: []].append(entry) } var duplicatesRemoved = 0 for (_, dayEntries) in entriesByDay where dayEntries.count > 1 { // Sort by timestamp (most recent first), preferring non-missing moods let sorted = dayEntries.sorted { a, b in // Prefer non-missing moods if a.mood != .missing && b.mood == .missing { return true } if a.mood == .missing && b.mood != .missing { return false } // Then by timestamp (most recent first) return a.timestamp > b.timestamp } // Keep the first (best) entry, delete the rest for entry in sorted.dropFirst() { cleanupPhotoIfNeeded(for: entry) modelContext.delete(entry) duplicatesRemoved += 1 } } if duplicatesRemoved > 0 { saveAndRunDataListeners() AppLogger.general.info("Removed \(duplicatesRemoved) duplicate entries") AnalyticsManager.shared.track(.duplicatesRemoved(count: duplicatesRemoved)) } return duplicatesRemoved } }