// ── Data Export & Account Deletion Manager ──────────────────── // GDPR + App Store requirement: export all data, delete account // All data is local — export as JSON, delete clears everything import Foundation import Combine @MainActor final class DataExportManager: ObservableObject { static let shared = DataExportManager() @Published var isExporting = false @Published var isDeleting = false @Published var lastExportURL: URL? private init() {} // MARK: - Export All Data func exportAllData( timers: [CMTimer], moodCheckIns: [MoodCheckIn], sleepData: SleepSummary?, bedtimeRoutine: BedtimeRoutine?, savedLocations: [SavedLocation], locationTriggers: [LocationTrigger] ) -> URL? { isExporting = true defer { isExporting = false } let export = DataExport( exportDate: Date(), appVersion: "1.0.0", dataVersion: 1, timers: timers.map { TimerExport(from: $0) }, moodCheckIns: moodCheckIns, sleepSummary: sleepData, bedtimeRoutine: bedtimeRoutine, savedLocations: savedLocations, locationTriggers: locationTriggers, statistics: generateStatistics(timers: timers, checkIns: moodCheckIns) ) do { let encoder = JSONEncoder() encoder.outputFormatting = [.prettyPrinted, .sortedKeys] encoder.dateEncodingStrategy = .iso8601 let data = try encoder.encode(export) let fileName = "chronomind-export-\(ISO8601DateFormatter().string(from: Date())).json" let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(fileName) try data.write(to: tempURL) lastExportURL = tempURL return tempURL } catch { return nil } } // MARK: - Delete All Data func deleteAllData() { isDeleting = true // Clear all UserDefaults keys let keysToRemove = [ "chronomind-timers", "chronomind-timers-cloud", "chronomind-cloud-sync-enabled", "chronomind-last-sync", "chronomind-mood-checkins", "chronomind-mood-enabled", "chronomind-bedtime-routine", "chronomind-smart-alarm", "chronomind-last-sleep", "chronomind-saved-locations", "chronomind-location-triggers", "chronomind-synced-calendars", "chronomind-calendar-sync-enabled", "chronomind-calendar-last-sync", "chronomind-crash-reports", "chronomind-feedback", "chronomind-gamification-streaks", "chronomind-gamification-badges", "chronomind-gamification-scores", "cm_defaultUrgency", "cm_defaultCascade", "cm_hapticEnabled", "cm_soundEnabled", ] for key in keysToRemove { UserDefaults.standard.removeObject(forKey: key) } // Clear shared App Group defaults if let sharedDefaults = UserDefaults(suiteName: "group.com.chronomind.shared") { for key in keysToRemove { sharedDefaults.removeObject(forKey: key) } } // Clear iCloud KV store let kvStore = NSUbiquitousKeyValueStore.default for key in kvStore.dictionaryRepresentation.keys { if key.hasPrefix("chronomind") { kvStore.removeObject(forKey: key) } } kvStore.synchronize() // Remove all pending notifications UNUserNotificationCenter.current().removeAllPendingNotificationRequests() UNUserNotificationCenter.current().removeAllDeliveredNotifications() // Clear temp files let tempDir = FileManager.default.temporaryDirectory if let contents = try? FileManager.default.contentsOfDirectory(at: tempDir, includingPropertiesForKeys: nil) { for url in contents where url.lastPathComponent.hasPrefix("chronomind") { try? FileManager.default.removeItem(at: url) } } isDeleting = false } // MARK: - Statistics private func generateStatistics(timers: [CMTimer], checkIns: [MoodCheckIn]) -> ExportStatistics { let completed = timers.filter { $0.state == .completed } let dismissed = timers.filter { $0.state == .dismissed } let avgEnergy: Double? = checkIns.isEmpty ? nil : Double(checkIns.reduce(0) { $0 + $1.energy.numericValue }) / Double(checkIns.count) return ExportStatistics( totalTimersCreated: timers.count, timersCompleted: completed.count, timersDismissed: dismissed.count, totalSnoozes: timers.reduce(0) { $0 + $1.snoozeCount }, totalMoodCheckIns: checkIns.count, averageEnergyLevel: avgEnergy, firstTimerDate: timers.map(\.createdAt).min(), lastTimerDate: timers.map(\.createdAt).max() ) } } // MARK: - Import support import UserNotifications // MARK: - Export Models struct DataExport: Codable { let exportDate: Date let appVersion: String let dataVersion: Int let timers: [TimerExport] let moodCheckIns: [MoodCheckIn] let sleepSummary: SleepSummary? let bedtimeRoutine: BedtimeRoutine? let savedLocations: [SavedLocation] let locationTriggers: [LocationTrigger] let statistics: ExportStatistics } struct TimerExport: Codable { let id: String let label: String let type: String let state: String let urgency: String let duration: TimeInterval let targetTime: Date let createdAt: Date let completedAt: Date? let dismissedAt: Date? let snoozeCount: Int let category: String? let cascadePreset: String init(from timer: CMTimer) { self.id = timer.id self.label = timer.label self.type = timer.type.rawValue self.state = timer.state.rawValue self.urgency = timer.urgency.rawValue self.duration = timer.duration ?? 0 self.targetTime = timer.targetTime self.createdAt = timer.createdAt self.completedAt = timer.completedAt self.dismissedAt = timer.dismissedAt self.snoozeCount = timer.snoozeCount self.category = timer.category self.cascadePreset = timer.cascade.preset.rawValue } } struct ExportStatistics: Codable { let totalTimersCreated: Int let timersCompleted: Int let timersDismissed: Int let totalSnoozes: Int let totalMoodCheckIns: Int let averageEnergyLevel: Double? let firstTimerDate: Date? let lastTimerDate: Date? }