// // RuleEnforcer.swift // Severed // import Foundation import Observation /// Turns the current set of rules into shield state: schedule rules with an /// active, un-paused window are shielded; everything else is cleared. /// /// Time-limit and open-limit rules need a DeviceActivity monitor extension to /// react to usage thresholds in the background; they are persisted and shown /// in the UI but not yet enforced here. @Observable final class RuleEnforcer { private(set) var blockingRuleIDs: Set = [] private let shields: ShieldApplying init(shields: ShieldApplying) { self.shields = shields } /// Recomputes shields from scratch. Call on launch, on any rule change, /// and periodically while the app is visible. Also expires stale pauses. func refresh(rules: [BlockingRule], at now: Date = .now, calendar: Calendar = .current) { var active: Set = [] for rule in rules { if let pausedUntil = rule.pausedUntil, pausedUntil <= now { rule.pausedUntil = nil } guard rule.kind == .schedule, rule.status(at: now, calendar: calendar).isActive else { continue } active.insert(rule.id) shields.applyShield( ruleID: rule.id, selectionData: rule.selectionData, mode: rule.selectionMode, blockAdultContent: rule.blockAdultContent ) } shields.clearShields(except: active) blockingRuleIDs = active } }