import Foundation import CoreData /// Per-category allocation target (percentage). /// /// Backed by the `Category.allocationTarget` Core Data attribute so targets sync across /// devices via iCloud/CloudKit. Previously stored in `UserDefaults`, which is device-local /// and did NOT sync — `migrateIfNeeded(context:)` moves any legacy values over. enum AllocationTargetStore { /// Legacy UserDefaults key (pre-iCloud). Only read once during migration. private static let legacyKey = "allocationTargets" private static var viewContext: NSManagedObjectContext { CoreDataStack.shared.viewContext } static func target(for categoryId: UUID) -> Double? { guard let category = fetchCategory(categoryId), let target = category.allocationTarget?.doubleValue, target > 0 else { return nil } return target } static func setTarget(_ value: Double?, for categoryId: UUID) { guard let category = fetchCategory(categoryId) else { return } if let value, value > 0 { category.allocationTarget = NSNumber(value: value) } else { category.allocationTarget = nil } try? viewContext.save() } static func totalTargetPercentage(for categoryIds: [UUID]) -> Double { categoryIds.reduce(0) { total, id in total + (target(for: id) ?? 0) } } private static func fetchCategory(_ id: UUID) -> Category? { let request = Category.fetchRequest() request.predicate = NSPredicate(format: "id == %@", id as CVarArg) request.fetchLimit = 1 return try? viewContext.fetch(request).first } // MARK: - Migration /// One-time migration of legacy UserDefaults targets into Core Data so they sync via /// iCloud. Runs on every launch but is a no-op once the legacy key is cleared. static func migrateIfNeeded(context: NSManagedObjectContext) { guard let data = UserDefaults.standard.data(forKey: legacyKey), let dict = try? JSONDecoder().decode([String: Double].self, from: data), !dict.isEmpty else { return } let categories = (try? context.fetch(Category.fetchRequest())) ?? [] var changed = false for category in categories where category.allocationTarget == nil { if let value = dict[category.id.uuidString], value > 0 { category.allocationTarget = NSNumber(value: value) changed = true } } if changed { try? context.save() } UserDefaults.standard.removeObject(forKey: legacyKey) } }