2025-12-26 20:46:48 +01:00
using System ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.Linq ;
2025-12-30 10:36:27 +01:00
using CommunityToolkit.Mvvm.Collections ;
2025-12-26 20:46:48 +01:00
using CommunityToolkit.Mvvm.ComponentModel ;
2026-01-20 00:30:24 +01:00
using CommunityToolkit.Mvvm.Messaging ;
2025-12-26 20:46:48 +01:00
using Wino.Calendar.ViewModels.Data ;
using Wino.Calendar.ViewModels.Interfaces ;
2025-12-30 10:02:24 +01:00
using Wino.Core.Domain.Entities.Shared ;
2026-04-11 12:57:51 +02:00
using Wino.Core.Domain.Enums ;
2026-01-20 00:30:24 +01:00
using Wino.Core.Domain.Interfaces ;
2026-04-11 12:57:51 +02:00
using Wino.Core.Services ;
2026-01-20 00:30:24 +01:00
using Wino.Messaging.Client.Calendar ;
2026-02-16 01:56:22 +01:00
using Wino.Messaging.UI ;
2025-12-26 20:46:48 +01:00
namespace Wino.Mail.WinUI.Services ;
/// <summary>
/// Encapsulated state manager for collectively managing the state of account calendars.
/// Callers must react to the events to update their state only from this service.
/// </summary>
2026-01-20 00:30:24 +01:00
public partial class AccountCalendarStateService : ObservableRecipient ,
IAccountCalendarStateService ,
IRecipient < CalendarListAdded > ,
IRecipient < CalendarListUpdated > ,
2026-02-16 01:56:22 +01:00
IRecipient < CalendarListDeleted > ,
2026-04-04 20:23:20 +02:00
IRecipient < AccountRemovedMessage > ,
IRecipient < AccountUpdatedMessage > ,
2026-04-11 12:57:51 +02:00
IRecipient < AccountSynchronizationProgressUpdatedMessage >
2025-12-26 20:46:48 +01:00
{
2026-02-27 10:22:52 +01:00
private readonly object _calendarStateLock = new ( ) ;
2026-01-20 00:30:24 +01:00
public IDispatcher ? Dispatcher { get ; set ; }
2025-12-30 10:02:24 +01:00
public event EventHandler < GroupedAccountCalendarViewModel > ? CollectiveAccountGroupSelectionStateChanged ;
public event EventHandler < AccountCalendarViewModel > ? AccountCalendarSelectionStateChanged ;
private readonly ObservableCollection < GroupedAccountCalendarViewModel > _internalGroupedAccountCalendars ;
2025-12-30 10:36:27 +01:00
private readonly ObservableGroupedCollection < MailAccount , AccountCalendarViewModel > _internalGroupedCalendars ;
2025-12-26 20:46:48 +01:00
[ObservableProperty]
public partial ReadOnlyObservableCollection < GroupedAccountCalendarViewModel > GroupedAccountCalendars { get ; set ; }
2025-12-30 10:36:27 +01:00
[ObservableProperty]
public partial ReadOnlyObservableGroupedCollection < MailAccount , AccountCalendarViewModel > GroupedCalendars { get ; set ; }
2026-04-04 20:23:20 +02:00
[ObservableProperty]
public partial bool IsAnySynchronizationInProgress { get ; set ; }
2025-12-26 20:46:48 +01:00
public IEnumerable < AccountCalendarViewModel > ActiveCalendars
{
get
{
2026-02-27 10:22:52 +01:00
lock ( _calendarStateLock )
{
return _internalGroupedAccountCalendars
. SelectMany ( a = > a . AccountCalendars )
. Where ( b = > b . IsChecked )
. ToList ( ) ;
}
2025-12-26 20:46:48 +01:00
}
}
2025-12-30 10:02:24 +01:00
public IEnumerable < AccountCalendarViewModel > AllCalendars
{
get
{
2026-02-27 10:22:52 +01:00
lock ( _calendarStateLock )
{
return _internalGroupedAccountCalendars
. SelectMany ( a = > a . AccountCalendars )
. ToList ( ) ;
}
2025-12-30 10:02:24 +01:00
}
}
2025-12-26 20:46:48 +01:00
2026-01-20 00:30:24 +01:00
private readonly IAccountService _accountService ;
public AccountCalendarStateService ( IAccountService accountService )
2025-12-26 20:46:48 +01:00
{
2026-01-20 00:30:24 +01:00
_accountService = accountService ;
2025-12-30 10:02:24 +01:00
_internalGroupedAccountCalendars = new ObservableCollection < GroupedAccountCalendarViewModel > ( ) ;
2025-12-26 20:46:48 +01:00
GroupedAccountCalendars = new ReadOnlyObservableCollection < GroupedAccountCalendarViewModel > ( _internalGroupedAccountCalendars ) ;
2025-12-30 10:36:27 +01:00
_internalGroupedCalendars = new ObservableGroupedCollection < MailAccount , AccountCalendarViewModel > ( ) ;
GroupedCalendars = new ReadOnlyObservableGroupedCollection < MailAccount , AccountCalendarViewModel > ( _internalGroupedCalendars ) ;
2026-01-20 00:30:24 +01:00
Messenger . Register < CalendarListAdded > ( this ) ;
Messenger . Register < CalendarListUpdated > ( this ) ;
Messenger . Register < CalendarListDeleted > ( this ) ;
2026-02-16 01:56:22 +01:00
Messenger . Register < AccountRemovedMessage > ( this ) ;
2026-04-04 20:23:20 +02:00
Messenger . Register < AccountUpdatedMessage > ( this ) ;
2026-04-11 12:57:51 +02:00
Messenger . Register < AccountSynchronizationProgressUpdatedMessage > ( this ) ;
2025-12-26 20:46:48 +01:00
}
2025-12-30 10:02:24 +01:00
private void SingleGroupCalendarCollectiveStateChanged ( object? sender , EventArgs e )
= > CollectiveAccountGroupSelectionStateChanged ? . Invoke ( this , sender as GroupedAccountCalendarViewModel ? ? throw new InvalidOperationException ( "Sender must be GroupedAccountCalendarViewModel" ) ) ;
2025-12-26 20:46:48 +01:00
2025-12-30 10:02:24 +01:00
private void SingleCalendarSelectionStateChanged ( object? sender , AccountCalendarViewModel e )
2025-12-26 20:46:48 +01:00
= > AccountCalendarSelectionStateChanged ? . Invoke ( this , e ) ;
public void AddGroupedAccountCalendar ( GroupedAccountCalendarViewModel groupedAccountCalendar )
{
2026-02-27 10:22:52 +01:00
lock ( _calendarStateLock )
{
groupedAccountCalendar . CalendarSelectionStateChanged + = SingleCalendarSelectionStateChanged ;
groupedAccountCalendar . CollectiveSelectionStateChanged + = SingleGroupCalendarCollectiveStateChanged ;
2026-04-11 12:57:51 +02:00
try
{
groupedAccountCalendar . ApplySynchronizationProgress ( SynchronizationManager . Instance . GetSynchronizationProgress (
groupedAccountCalendar . Account . Id ,
SynchronizationProgressCategory . Calendar ) ) ;
}
catch ( InvalidOperationException )
{
}
2025-12-26 20:46:48 +01:00
2026-02-27 10:22:52 +01:00
_internalGroupedAccountCalendars . Add ( groupedAccountCalendar ) ;
2025-12-30 10:36:27 +01:00
2026-02-27 10:22:52 +01:00
// Maintain the grouped calendars collection
var group = _internalGroupedCalendars . FirstOrDefault < ObservableGroup < MailAccount , AccountCalendarViewModel > > ( g = > g . Key . Id = = groupedAccountCalendar . Account . Id ) ;
if ( group = = null )
2025-12-30 10:36:27 +01:00
{
2026-02-27 10:22:52 +01:00
_internalGroupedCalendars . Add ( new ObservableGroup < MailAccount , AccountCalendarViewModel > ( groupedAccountCalendar . Account , groupedAccountCalendar . AccountCalendars ) ) ;
}
else
{
foreach ( var calendar in groupedAccountCalendar . AccountCalendars )
{
group . Add ( calendar ) ;
}
2025-12-30 10:36:27 +01:00
}
2026-04-04 20:23:20 +02:00
UpdateAggregateSynchronizationState ( ) ;
2025-12-30 10:36:27 +01:00
}
2025-12-26 20:46:48 +01:00
}
public void RemoveGroupedAccountCalendar ( GroupedAccountCalendarViewModel groupedAccountCalendar )
{
2026-02-27 10:22:52 +01:00
lock ( _calendarStateLock )
{
groupedAccountCalendar . CalendarSelectionStateChanged - = SingleCalendarSelectionStateChanged ;
groupedAccountCalendar . CollectiveSelectionStateChanged - = SingleGroupCalendarCollectiveStateChanged ;
2025-12-26 20:46:48 +01:00
2026-02-27 10:22:52 +01:00
_internalGroupedAccountCalendars . Remove ( groupedAccountCalendar ) ;
2025-12-30 10:36:27 +01:00
2026-02-27 10:22:52 +01:00
// Maintain the grouped calendars collection
var group = _internalGroupedCalendars . FirstOrDefault < ObservableGroup < MailAccount , AccountCalendarViewModel > > ( g = > g . Key . Id = = groupedAccountCalendar . Account . Id ) ;
if ( group ! = null )
2025-12-30 10:36:27 +01:00
{
2026-02-27 10:22:52 +01:00
foreach ( var calendar in groupedAccountCalendar . AccountCalendars . ToList ( ) )
{
group . Remove ( calendar ) ;
}
2025-12-30 10:36:27 +01:00
2026-02-27 10:22:52 +01:00
if ( group . Count = = 0 )
{
_internalGroupedCalendars . Remove ( group ) ;
}
2025-12-30 10:36:27 +01:00
}
2026-04-04 20:23:20 +02:00
UpdateAggregateSynchronizationState ( ) ;
2025-12-30 10:36:27 +01:00
}
2025-12-26 20:46:48 +01:00
}
2025-12-30 10:02:24 +01:00
public void ClearGroupedAccountCalendars ( )
2025-12-26 20:46:48 +01:00
{
2026-02-27 10:22:52 +01:00
lock ( _calendarStateLock )
2025-12-26 20:46:48 +01:00
{
2026-02-27 10:22:52 +01:00
while ( _internalGroupedAccountCalendars . Any ( ) )
{
RemoveGroupedAccountCalendar ( _internalGroupedAccountCalendars [ 0 ] ) ;
}
2025-12-26 20:46:48 +01:00
}
}
public void AddAccountCalendar ( AccountCalendarViewModel accountCalendar )
{
2026-02-27 10:22:52 +01:00
lock ( _calendarStateLock )
2025-12-26 20:46:48 +01:00
{
2026-02-27 10:22:52 +01:00
// Find the group that this calendar belongs to.
var group = _internalGroupedAccountCalendars . FirstOrDefault ( g = > g . Account . Id = = accountCalendar . Account . Id ) ;
2025-12-30 10:36:27 +01:00
2026-02-27 10:22:52 +01:00
if ( group = = null )
2025-12-30 10:36:27 +01:00
{
2026-02-27 10:22:52 +01:00
// If the group doesn't exist, create it.
group = new GroupedAccountCalendarViewModel ( accountCalendar . Account , new [ ] { accountCalendar } ) ;
AddGroupedAccountCalendar ( group ) ;
2025-12-30 10:36:27 +01:00
}
else
{
2026-02-27 10:22:52 +01:00
group . AccountCalendars . Add ( accountCalendar ) ;
// Maintain the grouped calendars collection
var calendarGroup = _internalGroupedCalendars . FirstOrDefault < ObservableGroup < MailAccount , AccountCalendarViewModel > > ( g = > g . Key . Id = = accountCalendar . Account . Id ) ;
if ( calendarGroup = = null )
{
_internalGroupedCalendars . Add ( new ObservableGroup < MailAccount , AccountCalendarViewModel > ( accountCalendar . Account , new [ ] { accountCalendar } ) ) ;
}
else
{
calendarGroup . Add ( accountCalendar ) ;
}
2025-12-30 10:36:27 +01:00
}
2025-12-26 20:46:48 +01:00
}
}
public void RemoveAccountCalendar ( AccountCalendarViewModel accountCalendar )
{
2026-02-27 10:22:52 +01:00
lock ( _calendarStateLock )
{
var group = _internalGroupedAccountCalendars . FirstOrDefault ( g = > g . Account . Id = = accountCalendar . Account . Id ) ;
2025-12-26 20:46:48 +01:00
2026-02-27 10:22:52 +01:00
// We don't expect but just in case.
if ( group = = null ) return ;
2025-12-26 20:46:48 +01:00
2026-02-27 10:22:52 +01:00
group . AccountCalendars . Remove ( accountCalendar ) ;
2025-12-30 10:36:27 +01:00
2026-02-27 10:22:52 +01:00
// Maintain the grouped calendars collection
var calendarGroup = _internalGroupedCalendars . FirstOrDefault < ObservableGroup < MailAccount , AccountCalendarViewModel > > ( g = > g . Key . Id = = accountCalendar . Account . Id ) ;
if ( calendarGroup ! = null )
2025-12-30 10:36:27 +01:00
{
2026-02-27 10:22:52 +01:00
calendarGroup . Remove ( accountCalendar ) ;
if ( calendarGroup . Count = = 0 )
{
_internalGroupedCalendars . Remove ( calendarGroup ) ;
}
2025-12-30 10:36:27 +01:00
}
2026-02-27 10:22:52 +01:00
if ( group . AccountCalendars . Count = = 0 )
{
RemoveGroupedAccountCalendar ( group ) ;
}
2025-12-26 20:46:48 +01:00
}
}
2026-01-20 00:30:24 +01:00
public async void Receive ( CalendarListAdded message )
{
var accountCalendar = message . AccountCalendar ;
var mailAccount = await _accountService . GetAccountAsync ( accountCalendar . AccountId ) ;
if ( mailAccount = = null ) return ;
var accountCalendarViewModel = new AccountCalendarViewModel ( mailAccount , accountCalendar ) ;
if ( Dispatcher ! = null )
{
await Dispatcher . ExecuteOnUIThread ( ( ) = > AddAccountCalendar ( accountCalendarViewModel ) ) ;
}
else
{
AddAccountCalendar ( accountCalendarViewModel ) ;
}
}
public async void Receive ( CalendarListUpdated message )
{
var accountCalendar = message . AccountCalendar ;
if ( Dispatcher ! = null )
{
await Dispatcher . ExecuteOnUIThread ( ( ) = >
{
// Find the existing calendar view model
var existingCalendar = AllCalendars . FirstOrDefault ( c = > c . Id = = accountCalendar . Id ) ;
if ( existingCalendar ! = null )
{
// Update properties
existingCalendar . Name = accountCalendar . Name ;
existingCalendar . TextColorHex = accountCalendar . TextColorHex ;
existingCalendar . BackgroundColorHex = accountCalendar . BackgroundColorHex ;
existingCalendar . IsExtended = accountCalendar . IsExtended ;
existingCalendar . IsPrimary = accountCalendar . IsPrimary ;
2026-02-12 18:04:29 +01:00
existingCalendar . IsSynchronizationEnabled = accountCalendar . IsSynchronizationEnabled ;
existingCalendar . DefaultShowAs = accountCalendar . DefaultShowAs ;
2026-01-20 00:30:24 +01:00
}
} ) ;
}
else
{
// Find the existing calendar view model
var existingCalendar = AllCalendars . FirstOrDefault ( c = > c . Id = = accountCalendar . Id ) ;
if ( existingCalendar ! = null )
{
// Update properties
existingCalendar . Name = accountCalendar . Name ;
existingCalendar . TextColorHex = accountCalendar . TextColorHex ;
existingCalendar . BackgroundColorHex = accountCalendar . BackgroundColorHex ;
existingCalendar . IsExtended = accountCalendar . IsExtended ;
existingCalendar . IsPrimary = accountCalendar . IsPrimary ;
2026-02-12 18:04:29 +01:00
existingCalendar . IsSynchronizationEnabled = accountCalendar . IsSynchronizationEnabled ;
existingCalendar . DefaultShowAs = accountCalendar . DefaultShowAs ;
2026-01-20 00:30:24 +01:00
}
}
}
public async void Receive ( CalendarListDeleted message )
{
var accountCalendar = message . AccountCalendar ;
if ( Dispatcher ! = null )
{
await Dispatcher . ExecuteOnUIThread ( ( ) = >
{
// Find and remove the calendar view model
var existingCalendar = AllCalendars . FirstOrDefault ( c = > c . Id = = accountCalendar . Id ) ;
if ( existingCalendar ! = null )
{
RemoveAccountCalendar ( existingCalendar ) ;
}
} ) ;
}
else
{
// Find and remove the calendar view model
var existingCalendar = AllCalendars . FirstOrDefault ( c = > c . Id = = accountCalendar . Id ) ;
if ( existingCalendar ! = null )
{
RemoveAccountCalendar ( existingCalendar ) ;
}
}
}
2026-02-16 01:56:22 +01:00
public async void Receive ( AccountRemovedMessage message )
{
var removedAccountId = message . Account . Id ;
if ( Dispatcher ! = null )
{
await Dispatcher . ExecuteOnUIThread ( ( ) = >
{
2026-02-27 10:22:52 +01:00
GroupedAccountCalendarViewModel ? groupedAccount ;
lock ( _calendarStateLock )
{
groupedAccount = _internalGroupedAccountCalendars . FirstOrDefault ( a = > a . Account . Id = = removedAccountId ) ;
}
2026-02-16 01:56:22 +01:00
if ( groupedAccount ! = null )
{
RemoveGroupedAccountCalendar ( groupedAccount ) ;
}
} ) ;
}
else
{
2026-02-27 10:22:52 +01:00
GroupedAccountCalendarViewModel ? groupedAccount ;
lock ( _calendarStateLock )
{
groupedAccount = _internalGroupedAccountCalendars . FirstOrDefault ( a = > a . Account . Id = = removedAccountId ) ;
}
2026-02-16 01:56:22 +01:00
if ( groupedAccount ! = null )
{
RemoveGroupedAccountCalendar ( groupedAccount ) ;
}
}
}
2026-04-04 20:23:20 +02:00
public async void Receive ( AccountUpdatedMessage message )
{
if ( Dispatcher ! = null )
{
await Dispatcher . ExecuteOnUIThread ( ( ) = > UpdateGroupedAccount ( message . Account ) ) ;
}
else
{
UpdateGroupedAccount ( message . Account ) ;
}
}
2026-04-11 12:57:51 +02:00
public async void Receive ( AccountSynchronizationProgressUpdatedMessage message )
2026-04-04 20:23:20 +02:00
{
2026-04-11 12:57:51 +02:00
if ( message . Progress . Category ! = SynchronizationProgressCategory . Calendar )
return ;
2026-04-04 20:23:20 +02:00
if ( Dispatcher ! = null )
{
2026-04-11 12:57:51 +02:00
await Dispatcher . ExecuteOnUIThread ( ( ) = > UpdateCalendarSynchronizationState ( message . Progress ) ) ;
2026-04-04 20:23:20 +02:00
}
else
{
2026-04-11 12:57:51 +02:00
UpdateCalendarSynchronizationState ( message . Progress ) ;
2026-04-04 20:23:20 +02:00
}
}
private void UpdateGroupedAccount ( MailAccount updatedAccount )
{
GroupedAccountCalendarViewModel ? groupedAccount ;
lock ( _calendarStateLock )
{
groupedAccount = _internalGroupedAccountCalendars . FirstOrDefault ( a = > a . Account . Id = = updatedAccount . Id ) ;
}
groupedAccount ? . UpdateAccount ( updatedAccount ) ;
}
2026-04-11 12:57:51 +02:00
private void UpdateCalendarSynchronizationState ( Wino . Core . Domain . Models . Synchronization . AccountSynchronizationProgress progress )
2026-04-04 20:23:20 +02:00
{
GroupedAccountCalendarViewModel ? groupedAccount ;
lock ( _calendarStateLock )
{
2026-04-11 12:57:51 +02:00
groupedAccount = _internalGroupedAccountCalendars . FirstOrDefault ( a = > a . Account . Id = = progress . AccountId ) ;
2026-04-04 20:23:20 +02:00
}
if ( groupedAccount = = null )
return ;
2026-04-11 12:57:51 +02:00
groupedAccount . ApplySynchronizationProgress ( progress ) ;
2026-04-04 20:23:20 +02:00
UpdateAggregateSynchronizationState ( ) ;
}
private void UpdateAggregateSynchronizationState ( )
{
IsAnySynchronizationInProgress = _internalGroupedAccountCalendars . Any ( a = > a . IsSynchronizationInProgress ) ;
}
2025-12-26 20:46:48 +01:00
}