import SwiftUI import SwiftData import Foundation struct SymptomRange: Identifiable, Hashable { let id: String let name: String let startDay: Date let endDay: Date let severity: Int let isOngoing: Bool var color: Color { switch severity { case 1, 2: return Tj.Palette.leaf case 3: return Tj.Palette.amber default: return Tj.Palette.brick } } func contains(_ day: Date, calendar: Calendar = .current) -> Bool { let d = calendar.startOfDay(for: day) return d >= startDay && d <= endDay } func position(_ day: Date, calendar: Calendar = .current) -> Position { let d = calendar.startOfDay(for: day) let isStart = d == startDay let isEnd = d == endDay if isStart && isEnd { return .single } if isStart { return .start } if isEnd { return .end } return .middle } enum Position { case single, start, middle, end } } struct DayMarks: Hashable { var abnormalCount: Int = 0 var normalCount: Int = 0 var reportCount: Int = 0 var diaryCount: Int = 0 var hasAnyEvent: Bool { abnormalCount + normalCount + reportCount + diaryCount > 0 } } struct CalendarData { let dayMarks: [Date: DayMarks] let symptomRanges: [SymptomRange] func marks(for day: Date, calendar: Calendar = .current) -> DayMarks { dayMarks[calendar.startOfDay(for: day)] ?? DayMarks() } func ranges(touching day: Date, calendar: Calendar = .current) -> [SymptomRange] { symptomRanges.filter { $0.contains(day, calendar: calendar) } } static func build(indicators: [Indicator], reports: [Report], diaries: [DiaryEntry], symptoms: [Symptom], now: Date = .now, calendar: Calendar = .current) -> CalendarData { var buckets: [Date: DayMarks] = [:] for i in indicators { let day = calendar.startOfDay(for: i.capturedAt) var m = buckets[day] ?? DayMarks() if i.status == .normal { m.normalCount += 1 } else { m.abnormalCount += 1 } buckets[day] = m } for r in reports { let day = calendar.startOfDay(for: r.reportDate) var m = buckets[day] ?? DayMarks() m.reportCount += 1 buckets[day] = m } for d in diaries { let day = calendar.startOfDay(for: d.createdAt) var m = buckets[day] ?? DayMarks() m.diaryCount += 1 buckets[day] = m } let ranges: [SymptomRange] = symptoms.map { s in SymptomRange( id: "\(s.persistentModelID)", name: s.name, startDay: calendar.startOfDay(for: s.startedAt), endDay: calendar.startOfDay(for: s.endedAt ?? now), severity: s.severity, isOngoing: s.isOngoing ) } return CalendarData(dayMarks: buckets, symptomRanges: ranges) } }