learning_ai_clock/ios/ChronoMind/Shared/TimerEngine/Cascade.swift
saravanakumardb1 424e804396 fix(ios): resolve Swift compile errors for iOS 26 / Xcode 26
- Fix CloudKitSyncManager deinit concurrency violation
- Replace deprecated List(selection:) with VStack+ForEach sidebar
- Replace removed Animation.none with Animation.linear(duration: 0)
- Fix CountdownRing initializer parameter mismatch
- Unwrap optional timer.duration in ShareableTimerManager and DataExportManager
- Add missing .event case to exhaustive switch
- Change CascadeWarning.scheduledTime from let to var
- Fix CalendarSyncManager CMTimer init (add elapsedBeforePause, remove non-existent params)
- Add missing UserNotifications import in SleepManager
- Remove parameterized App Intents phrases (iOS 26 restriction)
- Temporarily remove watchOS target dependency for iOS build
2026-03-19 14:22:50 -07:00

124 lines
3.7 KiB
Swift

// Pre-Warning Cascade System
// Cascade presets aligned with PRD: Aggressive, Standard, Light, Minimal, None, Custom
// Ported from web/src/lib/cascade.ts
import Foundation
// MARK: - Cascade Preset
enum CascadePreset: String, Codable, CaseIterable, Identifiable {
case aggressive
case standard
case light
case minimal
case none
case custom
var id: String { rawValue }
var label: String {
switch self {
case .aggressive: return "Aggressive"
case .standard: return "Standard"
case .light: return "Light"
case .minimal: return "Minimal"
case .none: return "None (fire only)"
case .custom: return "Custom"
}
}
/// Default intervals in minutes before target time
var defaultIntervals: [Int] {
switch self {
case .aggressive: return [240, 180, 120, 90, 60, 30, 15, 5, 1]
case .standard: return [120, 60, 30, 15, 5]
case .light: return [60, 15, 5]
case .minimal: return [15]
case .none: return []
case .custom: return []
}
}
}
// MARK: - Cascade Warning
struct CascadeWarning: Codable, Identifiable, Equatable {
let id: String
let minutesBefore: Int
var fired: Bool
var firedAt: Date?
var scheduledTime: Date
static func == (lhs: CascadeWarning, rhs: CascadeWarning) -> Bool {
lhs.id == rhs.id
}
}
// MARK: - Cascade Config
struct CascadeConfig: Codable {
let preset: CascadePreset
let intervals: [Int] // minutes before target time (for custom preset)
}
// MARK: - Cascade Functions
/// Calculate all warning timestamps from target time and cascade intervals.
/// Filters out warnings that would be in the past relative to `now`.
func calculateCascadeWarnings(
targetTime: Date,
intervals: [Int],
now: Date = Date()
) -> [CascadeWarning] {
let sorted = intervals.sorted(by: >) // largest first (earliest warning)
return sorted.enumerated().map { idx, minutesBefore in
let scheduledTime = targetTime.addingTimeInterval(-Double(minutesBefore) * 60.0)
return CascadeWarning(
id: "w-\(idx)-\(minutesBefore)m",
minutesBefore: minutesBefore,
fired: scheduledTime <= now,
firedAt: scheduledTime <= now ? scheduledTime : nil,
scheduledTime: scheduledTime
)
}
}
/// Get the next unfired warning from a cascade.
func getNextWarning(_ warnings: [CascadeWarning]) -> CascadeWarning? {
warnings.first(where: { !$0.fired })
}
/// Check which warnings should fire given the current time.
/// Returns newly-fired warning IDs and mutates the warnings array.
@discardableResult
func checkWarnings(_ warnings: inout [CascadeWarning], now: Date = Date()) -> [String] {
var newlyFired: [String] = []
for i in warnings.indices {
if !warnings[i].fired && warnings[i].scheduledTime <= now {
warnings[i].fired = true
warnings[i].firedAt = now
newlyFired.append(warnings[i].id)
}
}
return newlyFired
}
/// Get intervals for a preset, or custom intervals if preset is 'custom'.
func getCascadeIntervals(_ config: CascadeConfig) -> [Int] {
if config.preset == .custom {
return config.intervals.sorted(by: >)
}
return config.preset.defaultIntervals
}
/// Format minutes into human-readable string.
func formatMinutesBefore(_ minutes: Int) -> String {
if minutes >= 60 {
let hours = minutes / 60
let remaining = minutes % 60
if remaining == 0 { return "\(hours)h" }
return "\(hours)h \(remaining)m"
}
return "\(minutes)m"
}