using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Itenso.TimePeriod; using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Calendar; namespace Wino.Core.Domain.Collections; public class CalendarEventCollection { public event EventHandler CalendarItemAdded; public event EventHandler CalendarItemRemoved; public event EventHandler CalendarItemUpdated; public event EventHandler CalendarItemsCleared; private ObservableRangeCollection _internalRegularEvents = []; private ObservableRangeCollection _internalAllDayEvents = []; public ReadOnlyObservableCollection RegularEvents { get; } public ReadOnlyObservableCollection AllDayEvents { get; } // TODO: Rename this to include multi-day events. public ITimePeriod Period { get; } public CalendarSettings Settings { get; } private readonly List _allItems = new List(); public CalendarEventCollection(ITimePeriod period, CalendarSettings settings) { Period = period; Settings = settings; RegularEvents = new ReadOnlyObservableCollection(_internalRegularEvents); AllDayEvents = new ReadOnlyObservableCollection(_internalAllDayEvents); } public bool HasCalendarEvent(AccountCalendar accountCalendar) => _allItems.Any(x => x.AssignedCalendar.Id == accountCalendar.Id); public ICalendarItem GetCalendarItem(Guid calendarItemId) { return _allItems.FirstOrDefault(x => x.Id == calendarItemId); } public void ClearSelectionStates() { foreach (var item in _allItems) { if (item is ICalendarItemViewModel calendarItemViewModel) { calendarItemViewModel.IsSelected = false; } } } public void FilterByCalendars(IEnumerable visibleCalendarIds) { foreach (var item in _allItems) { var collections = GetProperCollectionsForCalendarItem(item); foreach (var collection in collections) { if (!visibleCalendarIds.Contains(item.AssignedCalendar.Id) && collection.Contains(item)) { RemoveCalendarItemInternal(collection, item, false); } else if (visibleCalendarIds.Contains(item.AssignedCalendar.Id) && !collection.Contains(item)) { AddCalendarItemInternal(collection, item, false); } } } } private IEnumerable> GetProperCollectionsForCalendarItem(ICalendarItem calendarItem) { // All-day events go to all days. // Multi-day events go to both. // Anything else goes to regular. if (calendarItem.IsAllDayEvent) { return [_internalAllDayEvents]; } else if (calendarItem.IsMultiDayEvent) { return [_internalRegularEvents, _internalAllDayEvents]; } else { return [_internalRegularEvents]; } } public void AddCalendarItem(ICalendarItem calendarItem) { var collections = GetProperCollectionsForCalendarItem(calendarItem); foreach (var collection in collections) { AddCalendarItemInternal(collection, calendarItem); } } public void RemoveCalendarItem(ICalendarItem calendarItem) { var collections = GetProperCollectionsForCalendarItem(calendarItem); foreach (var collection in collections) { RemoveCalendarItemInternal(collection, calendarItem); } } public void RemoveCalendarItems(Func predicate) { if (predicate == null) return; var itemsToRemove = _allItems.Where(predicate).ToList(); foreach (var item in itemsToRemove) { RemoveCalendarItem(item); } } private void AddCalendarItemInternal(ObservableRangeCollection collection, ICalendarItem calendarItem, bool create = true) { if (calendarItem is not ICalendarItemViewModel viewModel) throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem)); // Set the displaying context for proper title calculation viewModel.DisplayingPeriod = Period; viewModel.CalendarSettings = Settings; collection.Add(calendarItem); if (create) { _allItems.Add(calendarItem); } CalendarItemAdded?.Invoke(this, calendarItem); } private void RemoveCalendarItemInternal(ObservableRangeCollection collection, ICalendarItem calendarItem, bool destroy = true) { if (calendarItem is not ICalendarItemViewModel) throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem)); collection.Remove(calendarItem); if (destroy) { _allItems.Remove(calendarItem); } CalendarItemRemoved?.Invoke(this, calendarItem); } /// /// Updates an existing calendar item in-place. If the item's type changed (all-day vs regular), /// it will be moved to the appropriate collection. /// /// The updated calendar item data. /// True if the item was found and updated; false otherwise. public bool UpdateCalendarItem(CalendarItem calendarItem) { var existingItem = _allItems.FirstOrDefault(x => x.Id == calendarItem.Id); if (existingItem == null) return false; // Get the collections this item is currently in (before update) var oldCollections = GetProperCollectionsForCalendarItem(existingItem).ToList(); // Update the underlying data if (existingItem is ICalendarItemViewModel viewModel) { viewModel.UpdateFrom(calendarItem); } // Get the collections this item should be in (after update) var newCollections = GetProperCollectionsForCalendarItem(existingItem).ToList(); // Check if the collections changed var collectionsToRemoveFrom = oldCollections.Except(newCollections).ToList(); var collectionsToAddTo = newCollections.Except(oldCollections).ToList(); // Remove from old collections that are no longer applicable foreach (var collection in collectionsToRemoveFrom) { collection.Remove(existingItem); } // Add to new collections that are now applicable foreach (var collection in collectionsToAddTo) { if (!collection.Contains(existingItem)) { collection.Add(existingItem); } } CalendarItemUpdated?.Invoke(this, existingItem); return true; } public void Clear() { _internalAllDayEvents.Clear(); _internalRegularEvents.Clear(); _allItems.Clear(); CalendarItemsCleared?.Invoke(this, EventArgs.Empty); } }