import Foundation import SwiftData // SwiftData cache entities. These are a rebuildable read-through cache of the // iCloud Drive JSON documents — never the source of truth. App code reads them // (via @Query); all writes go through `SyncEngine` to the document files, and the // metadata observer mirrors the files back into these entities (see Mappers). // // `id` is the aggregate/child ULID (stable across cache rebuilds, unlike the // SwiftData PersistentIdentifier). Computed helpers preserve the API the views // used against the old Core Data classes. // MARK: - Split @Model final class Split { @Attribute(.unique) var id: String = ULID.make() var name: String = "" var color: String = "indigo" var systemImage: String = "dumbbell.fill" var order: Int = 0 var createdAt: Date = Date() var updatedAt: Date = Date() var jsonRelativePath: String = "" @Relationship(deleteRule: .cascade, inverse: \Exercise.split) var exercises: [Exercise] = [] init(id: String, name: String, color: String, systemImage: String, order: Int, createdAt: Date, updatedAt: Date, jsonRelativePath: String) { self.id = id self.name = name self.color = color self.systemImage = systemImage self.order = order self.createdAt = createdAt self.updatedAt = updatedAt self.jsonRelativePath = jsonRelativePath } static let unnamed = "Unnamed Split" var exercisesArray: [Exercise] { exercises.sorted { $0.order < $1.order } } } // MARK: - Exercise @Model final class Exercise { @Attribute(.unique) var id: String = ULID.make() var name: String = "" var order: Int = 0 var sets: Int = 0 var reps: Int = 0 var weight: Int = 0 var loadType: Int = LoadType.weight.rawValue var durationTotalSeconds: Int = 0 var weightLastUpdated: Date? var weightReminderTimeIntervalWeeks: Int = 2 var split: Split? init(id: String, name: String, order: Int, sets: Int, reps: Int, weight: Int, loadType: Int, durationTotalSeconds: Int, weightLastUpdated: Date?, weightReminderTimeIntervalWeeks: Int) { self.id = id self.name = name self.order = order self.sets = sets self.reps = reps self.weight = weight self.loadType = loadType self.durationTotalSeconds = durationTotalSeconds self.weightLastUpdated = weightLastUpdated self.weightReminderTimeIntervalWeeks = weightReminderTimeIntervalWeeks } var loadTypeEnum: LoadType { get { LoadType(rawValue: loadType) ?? .weight } set { loadType = newValue.rawValue } } /// Minutes component of the total duration (for min:sec pickers). var durationMinutes: Int { get { durationTotalSeconds / 60 } set { durationTotalSeconds = newValue * 60 + durationSeconds } } /// Seconds component (0–59) of the total duration. var durationSeconds: Int { get { durationTotalSeconds % 60 } set { durationTotalSeconds = durationMinutes * 60 + newValue } } } // MARK: - Workout @Model final class Workout { @Attribute(.unique) var id: String = ULID.make() var splitID: String? var splitName: String? var start: Date = Date() var end: Date? var statusRaw: String = WorkoutStatus.notStarted.rawValue var createdAt: Date = Date() var updatedAt: Date = Date() var jsonRelativePath: String = "" @Relationship(deleteRule: .cascade, inverse: \WorkoutLog.workout) var logs: [WorkoutLog] = [] init(id: String, splitID: String?, splitName: String?, start: Date, end: Date?, statusRaw: String, createdAt: Date, updatedAt: Date, jsonRelativePath: String) { self.id = id self.splitID = splitID self.splitName = splitName self.start = start self.end = end self.statusRaw = statusRaw self.createdAt = createdAt self.updatedAt = updatedAt self.jsonRelativePath = jsonRelativePath } var status: WorkoutStatus { get { WorkoutStatus(rawValue: statusRaw) ?? .notStarted } set { statusRaw = newValue.rawValue } } var statusName: String { status.displayName } var logsArray: [WorkoutLog] { logs.sorted { $0.order < $1.order } } var label: String { if status == .completed, let endDate = end { if start.isSameDay(as: endDate) { return "\(start.formattedDate())—\(endDate.formattedTime())" } else { return "\(start.formattedDate())—\(endDate.formattedDate())" } } else { return start.formattedDate() } } } // MARK: - WorkoutLog @Model final class WorkoutLog { @Attribute(.unique) var id: String = ULID.make() var exerciseName: String = "" var order: Int = 0 var sets: Int = 0 var reps: Int = 0 var weight: Int = 0 var loadType: Int = LoadType.weight.rawValue var durationTotalSeconds: Int = 0 var currentStateIndex: Int = 0 var completed: Bool = false var statusRaw: String = WorkoutStatus.notStarted.rawValue var notes: String? var date: Date = Date() var workout: Workout? init(id: String, exerciseName: String, order: Int, sets: Int, reps: Int, weight: Int, loadType: Int, durationTotalSeconds: Int, currentStateIndex: Int, completed: Bool, statusRaw: String, notes: String?, date: Date) { self.id = id self.exerciseName = exerciseName self.order = order self.sets = sets self.reps = reps self.weight = weight self.loadType = loadType self.durationTotalSeconds = durationTotalSeconds self.currentStateIndex = currentStateIndex self.completed = completed self.statusRaw = statusRaw self.notes = notes self.date = date } var status: WorkoutStatus { get { WorkoutStatus(rawValue: statusRaw) ?? .notStarted } set { statusRaw = newValue.rawValue } } var loadTypeEnum: LoadType { get { LoadType(rawValue: loadType) ?? .weight } set { loadType = newValue.rawValue } } var durationMinutes: Int { get { durationTotalSeconds / 60 } set { durationTotalSeconds = newValue * 60 + durationSeconds } } var durationSeconds: Int { get { durationTotalSeconds % 60 } set { durationTotalSeconds = durationMinutes * 60 + newValue } } }