using System.Text.RegularExpressions; namespace DECompanion.Windows; public sealed record StudyProgram( string Id, string Title, string PlanUrl, string? DetailsUrl, string? OverviewUrl, string? Code, string? FamilyTitle, string? DegreeTitle) { public string CohortLabel { get { if (!string.IsNullOrWhiteSpace(Code)) return Code.Trim().ToUpperInvariant(); var match = Regex.Match(Title, @"[A-Z]{2,}\d{2}(?:-\d+)?"); return match.Success ? match.Value.ToUpperInvariant() : Id.ToUpperInvariant(); } } public string DisplayFamilyTitle { get { if (!string.IsNullOrWhiteSpace(FamilyTitle)) return FamilyTitle.Trim(); var label = CohortLabel; var cleaned = Title.Replace(label, "", StringComparison.OrdinalIgnoreCase).Trim(); return string.IsNullOrWhiteSpace(cleaned) ? Title : cleaned; } } public string SelectionSubtitle => string.Join(" ยท ", new[] { DegreeTitle, CohortLabel }.Where(v => !string.IsNullOrWhiteSpace(v))); } public sealed record TimetableEvent( string Id, string Subject, DateTimeOffset StartDate, DateTimeOffset EndDate, string Lecturer, string Room, string? Note, string? ColorHex, bool IsAllDay) { public bool IsToday => StartDate.LocalDateTime.Date == DateTime.Today; public bool IsUpcoming => EndDate >= DateTimeOffset.Now; public bool IsLive => StartDate <= DateTimeOffset.Now && EndDate >= DateTimeOffset.Now; public string RoomLabel => string.IsNullOrWhiteSpace(Room) || Room.Trim() == "." ? "Raum folgt" : Room.Trim(); public string LecturerLabel => string.IsNullOrWhiteSpace(Lecturer) ? "Dozent folgt" : Lecturer.Trim(); public string TimeRangeText => $"{StartDate.LocalDateTime:HH:mm} - {EndDate.LocalDateTime:HH:mm}"; public string DayText => StartDate.LocalDateTime.ToString("dddd, dd.MM.yyyy"); } public sealed record MensaMeal( string Id, string Category, string Title, string PriceText, string? ImageUrl) { public string PreviewTitle => Regex.Replace(Title, @"\s*\(\s*\d+(?:\s*,\s*\d+)*\s*\)", "").Trim(); } public sealed record MensaDayMenu( string Id, string LocationTitle, DateTime Date, IReadOnlyList Meals) { public string Title => Date.Date == DateTime.Today ? "Heute" : Date.Date == DateTime.Today.AddDays(1) ? "Morgen" : Date.ToString("dddd, dd.MM."); } public sealed record CampusLiveEntry( string ProgramCode, string Subject, string Room, string TimeRange) { public string Id => ProgramCode; } public enum FeedStateStyle { Live, Fallback, Error } public sealed record FeedState(string Label, string Detail, FeedStateStyle Style) { public static FeedState Idle { get; } = new("Noch nicht geladen", "Warte auf die erste Synchronisation.", FeedStateStyle.Fallback); } public sealed record DataSnapshot( DateTimeOffset GeneratedAt, StudyProgram? SelectedProgram, IReadOnlyList Programs, IReadOnlyList TimetableEvents, IReadOnlyList MensaMenus, IReadOnlyList CampusEntries, FeedState ProgramState, FeedState TimetableState, FeedState MensaState, FeedState CampusState) { public static DataSnapshot Empty { get; } = new( DateTimeOffset.Now, null, Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), FeedState.Idle, FeedState.Idle, FeedState.Idle, FeedState.Idle); public TimetableEvent? NextEvent => TimetableEvents.Where(e => e.IsUpcoming).OrderBy(e => e.StartDate).FirstOrDefault(); public IReadOnlyList TodayEvents => TimetableEvents.Where(e => e.IsToday).OrderBy(e => e.StartDate).ToList(); public MensaDayMenu? PrimaryMensaMenu => MensaMenus.FirstOrDefault(m => m.Date.Date >= DateTime.Today && m.Meals.Count > 0) ?? MensaMenus.FirstOrDefault(m => m.Meals.Count > 0); }