Andromida/Andromida/App/Models/RitualArc.swift

102 lines
3.4 KiB
Swift

import Foundation
import SwiftData
/// Represents a time-bound period of a ritual. Each arc has its own habits and
/// completion tracking. When a ritual is renewed, a new arc is created while
/// the old arc's data remains frozen for historical accuracy.
@Model
final class RitualArc {
var id: UUID = UUID()
var startDate: Date = Date()
var endDate: Date = Date()
var arcNumber: Int = 1
var isActive: Bool = true
@Relationship(deleteRule: .cascade)
var habits: [ArcHabit]? = []
@Relationship(inverse: \Ritual.arcs)
var ritual: Ritual?
init(
id: UUID = UUID(),
startDate: Date = Date(),
endDate: Date,
arcNumber: Int = 1,
isActive: Bool = true,
habits: [ArcHabit] = []
) {
self.id = id
self.startDate = startDate
self.endDate = endDate
self.arcNumber = arcNumber
self.isActive = isActive
self.habits = habits
}
/// Convenience initializer using duration in days.
convenience init(
startDate: Date = Date(),
durationDays: Int,
arcNumber: Int = 1,
isActive: Bool = true,
habits: [ArcHabit] = []
) {
let calendar = Calendar.current
let start = calendar.startOfDay(for: startDate)
let end = calendar.date(byAdding: .day, value: durationDays - 1, to: start) ?? start
self.init(
startDate: start,
endDate: end,
arcNumber: arcNumber,
isActive: isActive,
habits: habits
)
}
/// The number of days in this arc.
var durationDays: Int {
let calendar = Calendar.current
let days = calendar.dateComponents([.day], from: startDate, to: endDate).day ?? 0
return days + 1
}
/// Checks if a specific date falls within this arc's date range.
func contains(date: Date) -> Bool {
let calendar = Calendar.current
let checkDate = calendar.startOfDay(for: date)
let start = calendar.startOfDay(for: startDate)
let end = calendar.startOfDay(for: endDate)
return checkDate >= start && checkDate <= end
}
/// Returns the day index (1-based) for a given date within this arc.
func dayIndex(for date: Date) -> Int {
let calendar = Calendar.current
let checkDate = calendar.startOfDay(for: date)
let start = calendar.startOfDay(for: startDate)
let days = calendar.dateComponents([.day], from: start, to: checkDate).day ?? 0
return max(1, min(days + 1, durationDays))
}
/// Creates a new arc that continues from this one with the same habits.
/// - Parameter durationDays: The duration for the new arc (defaults to same as this arc)
/// - Returns: A new arc with copied habits and incremented arc number
func createRenewalArc(durationDays: Int? = nil) -> RitualArc {
let calendar = Calendar.current
let newStartDate = calendar.date(byAdding: .day, value: 1, to: endDate) ?? Date()
let newDuration = durationDays ?? self.durationDays
let copiedHabits = (habits ?? []).map { $0.copyForNewArc() }
return RitualArc(
startDate: newStartDate,
durationDays: newDuration,
arcNumber: arcNumber + 1,
isActive: true,
habits: copiedHabits
)
}
}