using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using Itenso.TimePeriod;
using MailKit;
using MailKit.Net.Imap;
using MailKit.Search;
using MimeKit;
using Serilog;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Extensions;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Calendar;
using Wino.Core.Domain.Models.Connectivity;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.Extensions;
using Wino.Core.Integration;
using Wino.Core.Integration.Processors;
using Wino.Core.Requests.Bundles;
using Wino.Core.Requests.Calendar;
using Wino.Core.Requests.Folder;
using Wino.Core.Requests.Mail;
using Wino.Core.Synchronizers.ImapSync;
using Wino.Core.Misc;
using Wino.Messaging.Server;
using Wino.Messaging.UI;
using Wino.Services.Extensions;

namespace Wino.Core.Synchronizers.Mail;

public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreationPackage, object>, IImapSynchronizer
{
    /// <summary>
    /// N/A for IMAP as it doesn't support batch modifications natively.
    /// </summary>
    public override uint BatchModificationSize => 1000;
    public override uint InitialMessageDownloadCountPerFolder => 500;

    #region Idle Implementation

    private static readonly Random IdleReconnectJitter = new();
    private readonly object _idleDebounceLock = new();
    private CancellationTokenSource _idleLoopCancellationTokenSource;
    private Task _idleLoopTask;
    private int _lastIdleInboxCount = -1;
    private DateTime _lastIdleSyncRequestUtc = DateTime.MinValue;
    private readonly TimeSpan _idleSyncDebounceWindow = TimeSpan.FromSeconds(15);

    #endregion

    private readonly ILogger _logger = Log.ForContext<ImapSynchronizer>();
    private readonly ImapClientPool _clientPool;
    private readonly IImapChangeProcessor _imapChangeProcessor;
    private readonly IApplicationConfiguration _applicationConfiguration;
    private readonly UnifiedImapSynchronizer _unifiedSynchronizer;
    private readonly IImapSynchronizerErrorHandlerFactory _errorHandlerFactory;
    private readonly ICalDavClient _calDavClient;
    private readonly IAutoDiscoveryService _autoDiscoveryService;
    private readonly ICalendarService _calendarService;
    private readonly SemaphoreSlim _calDavDiscoveryLock = new(1, 1);
    private Uri _cachedCalDavServiceUri;
    private bool _isCalDavDiscoveryAttempted;
    private readonly IImapCalendarOperationHandler _localCalendarOperationHandler;
    private readonly IImapCalendarOperationHandler _calDavCalendarOperationHandler;
    private bool _isFolderStructureChanged;

    public ImapSynchronizer(MailAccount account,
                            IImapChangeProcessor imapChangeProcessor,
                            IApplicationConfiguration applicationConfiguration,
                            UnifiedImapSynchronizer unifiedSynchronizer,
                            IImapSynchronizerErrorHandlerFactory errorHandlerFactory,
                            ICalDavClient calDavClient,
                            IAutoDiscoveryService autoDiscoveryService,
                            ICalendarService calendarService) : base(account, WeakReferenceMessenger.Default)
    {
        _imapChangeProcessor = imapChangeProcessor;
        _applicationConfiguration = applicationConfiguration;
        _unifiedSynchronizer = unifiedSynchronizer;
        _errorHandlerFactory = errorHandlerFactory;
        _calDavClient = calDavClient;
        _autoDiscoveryService = autoDiscoveryService;
        _calendarService = calendarService;

        var poolOptions = ImapClientPoolOptions.CreateDefault(Account.ServerInformation);

        _clientPool = new ImapClientPool(poolOptions);
        _localCalendarOperationHandler = new LocalCalendarOperationHandler(Account, _imapChangeProcessor, _calendarService, _applicationConfiguration.ApplicationDataFolderPath, "local");
        _calDavCalendarOperationHandler = new CalDavCalendarOperationHandler(this, Account, _calendarService, _calDavClient);
    }

    /// <summary>
    /// Returns UniqueId for the given mail copy id.
    /// </summary>
    private UniqueId GetUniqueId(string mailCopyId) => new(MailkitClientExtensions.ResolveUid(mailCopyId));

    #region Mail Integrations

    // Items are grouped before being passed to this method.
    // Meaning that all items will come from and to the same folder.
    // It's fine to assume that here.

    public override List<IRequestBundle<ImapRequest>> Move(BatchMoveRequest requests)
    {
        return CreateTaskBundle(async (client, item) =>
        {
            var sourceFolder = await client.GetFolderAsync(item.FromFolder.RemoteFolderId);
            var destinationFolder = await client.GetFolderAsync(item.ToFolder.RemoteFolderId);

            // Only opening source folder is enough.
            await sourceFolder.OpenAsync(FolderAccess.ReadWrite).ConfigureAwait(false);
            await sourceFolder.MoveToAsync(GetUniqueId(item.Item.Id), destinationFolder).ConfigureAwait(false);
            await sourceFolder.CloseAsync().ConfigureAwait(false);
        }, requests);
    }

    public override List<IRequestBundle<ImapRequest>> ChangeFlag(BatchChangeFlagRequest requests)
    {
        return CreateTaskBundle(async (client, item) =>
        {
            var folder = item.Item.AssignedFolder;
            var remoteFolder = await client.GetFolderAsync(folder.RemoteFolderId);

            await remoteFolder.OpenAsync(FolderAccess.ReadWrite).ConfigureAwait(false);
            await remoteFolder.StoreAsync(GetUniqueId(item.Item.Id), new StoreFlagsRequest(item.IsFlagged ? StoreAction.Add : StoreAction.Remove, MessageFlags.Flagged) { Silent = true }).ConfigureAwait(false);
            await remoteFolder.CloseAsync().ConfigureAwait(false);
        }, requests);
    }

    public override List<IRequestBundle<ImapRequest>> Delete(BatchDeleteRequest requests)
    {
        return CreateTaskBundle(async (client, request) =>
        {
            var folder = request.Item.AssignedFolder;
            var remoteFolder = await client.GetFolderAsync(folder.RemoteFolderId).ConfigureAwait(false);

            await remoteFolder.OpenAsync(FolderAccess.ReadWrite).ConfigureAwait(false);
            await remoteFolder.AddFlagsAsync(GetUniqueId(request.Item.Id), MessageFlags.Deleted, true);
            await remoteFolder.ExpungeAsync().ConfigureAwait(false);
            await remoteFolder.CloseAsync().ConfigureAwait(false);
        }, requests);
    }

    public override List<IRequestBundle<ImapRequest>> MarkRead(BatchMarkReadRequest requests)
    {
        return CreateTaskBundle(async (client, request) =>
        {
            var folder = request.Item.AssignedFolder;
            var remoteFolder = await client.GetFolderAsync(folder.RemoteFolderId);

            await remoteFolder.OpenAsync(FolderAccess.ReadWrite).ConfigureAwait(false);
            await remoteFolder.StoreAsync(GetUniqueId(request.Item.Id), new StoreFlagsRequest(request.IsRead ? StoreAction.Add : StoreAction.Remove, MessageFlags.Seen) { Silent = true }).ConfigureAwait(false);
            await remoteFolder.CloseAsync().ConfigureAwait(false);
        }, requests);
    }

    public override List<IRequestBundle<ImapRequest>> CreateDraft(CreateDraftRequest request)
    {
        return CreateSingleTaskBundle(async (client, item) =>
        {
            var remoteDraftFolder = await client.GetFolderAsync(request.DraftPreperationRequest.CreatedLocalDraftCopy.AssignedFolder.RemoteFolderId).ConfigureAwait(false);

            await remoteDraftFolder.OpenAsync(FolderAccess.ReadWrite).ConfigureAwait(false);
            await remoteDraftFolder.AppendAsync(request.DraftPreperationRequest.CreatedLocalDraftMimeMessage, MessageFlags.Draft).ConfigureAwait(false);
            await remoteDraftFolder.CloseAsync().ConfigureAwait(false);
        }, request, request);
    }

    public override List<IRequestBundle<ImapRequest>> Archive(BatchArchiveRequest request)
    {
        var batchMoveRequest = new BatchMoveRequest(request.Select(item => new MoveRequest(item.Item, item.FromFolder, item.ToFolder)));
        return Move(batchMoveRequest);
    }


    public override List<IRequestBundle<ImapRequest>> EmptyFolder(EmptyFolderRequest request)
        => Delete(new BatchDeleteRequest(request.MailsToDelete.Select(a => new DeleteRequest(a))));

    public override List<IRequestBundle<ImapRequest>> MarkFolderAsRead(MarkFolderAsReadRequest request)
        => MarkRead(new BatchMarkReadRequest(request.MailsToMarkRead.Select(a => new MarkReadRequest(a, true))));

    public override List<IRequestBundle<ImapRequest>> SendDraft(SendDraftRequest request)
    {
        return CreateSingleTaskBundle(async (client, item) =>
        {
            // Batch sending is not supported. It will always be a single request therefore no need for a loop here.

            var singleRequest = request.Request;

            using var smtpClient = new MailKit.Net.Smtp.SmtpClient();

            if (smtpClient.IsConnected && client.IsAuthenticated) return;

            if (!smtpClient.IsConnected)
                await smtpClient.ConnectAsync(Account.ServerInformation.OutgoingServer, int.Parse(Account.ServerInformation.OutgoingServerPort), MailKit.Security.SecureSocketOptions.Auto);

            if (!smtpClient.IsAuthenticated)
                await smtpClient.AuthenticateAsync(Account.ServerInformation.OutgoingServerUsername, Account.ServerInformation.OutgoingServerPassword);

            // Remove local draft header before sending to prevent leaking to recipients.
            singleRequest.Mime.Headers.Remove(Domain.Constants.WinoLocalDraftHeader);

            // TODO: Transfer progress implementation as popup in the UI.
            await smtpClient.SendAsync(singleRequest.Mime, default);
            await smtpClient.DisconnectAsync(true);

            // SMTP sent the message, but we need to remove it from the Draft folder.
            var draftFolder = singleRequest.MailItem.AssignedFolder;

            var folder = await client.GetFolderAsync(draftFolder.RemoteFolderId);

            await folder.OpenAsync(FolderAccess.ReadWrite);
            await folder.AddFlagsAsync(new UniqueId(MailkitClientExtensions.ResolveUid(singleRequest.MailItem.Id)), MessageFlags.Deleted, true);
            await folder.ExpungeAsync();
            await folder.CloseAsync();

            // Check whether we need to create a copy of the message to Sent folder.
            // This comes from the account preferences.

            if (singleRequest.AccountPreferences.ShouldAppendMessagesToSentFolder && singleRequest.SentFolder != null)
            {
                var sentFolder = await client.GetFolderAsync(singleRequest.SentFolder.RemoteFolderId);

                await sentFolder.OpenAsync(FolderAccess.ReadWrite);
                await sentFolder.AppendAsync(singleRequest.Mime, MessageFlags.Seen);
                await sentFolder.CloseAsync();
            }
        }, request, request);
    }

    public override async Task DownloadMissingMimeMessageAsync(MailCopy mailItem,
                                                           ITransferProgress transferProgress = null,
                                                           CancellationToken cancellationToken = default)
    {
        var folder = mailItem.AssignedFolder;
        var remoteFolderId = folder.RemoteFolderId;

        var client = await _clientPool.GetClientAsync().ConfigureAwait(false);

        try
        {
            var remoteFolder = await client.GetFolderAsync(remoteFolderId, cancellationToken).ConfigureAwait(false);

            var uniqueId = new UniqueId(MailkitClientExtensions.ResolveUid(mailItem.Id));

            await remoteFolder.OpenAsync(FolderAccess.ReadOnly, cancellationToken).ConfigureAwait(false);

            var message = await remoteFolder.GetMessageAsync(uniqueId, cancellationToken, transferProgress).ConfigureAwait(false);

            await _imapChangeProcessor.SaveMimeFileAsync(mailItem.FileId, message, Account.Id).ConfigureAwait(false);
            await remoteFolder.CloseAsync(false, cancellationToken).ConfigureAwait(false);
        }
        catch (FolderNotFoundException ex)
        {
            _logger.Warning("IMAP folder {FolderId} not found during MIME download for {MailId}. Deleting locally.", remoteFolderId, mailItem.Id);
            await _imapChangeProcessor.DeleteMailAsync(Account.Id, mailItem.Id).ConfigureAwait(false);
            throw new SynchronizerEntityNotFoundException(ex.Message);
        }
        catch (ImapCommandException ex) when (ex.Response == ImapCommandResponse.No)
        {
            _logger.Warning("IMAP message {MailId} not found during MIME download (NO response). Deleting locally.", mailItem.Id);
            await _imapChangeProcessor.DeleteMailAsync(Account.Id, mailItem.Id).ConfigureAwait(false);
            throw new SynchronizerEntityNotFoundException(ex.Message);
        }
        finally
        {
            _clientPool.Release(client);
        }
    }

    public override Task DownloadCalendarAttachmentAsync(
        Wino.Core.Domain.Entities.Calendar.CalendarItem calendarItem,
        Wino.Core.Domain.Entities.Calendar.CalendarAttachment attachment,
        string localFilePath,
        CancellationToken cancellationToken = default)
    {
        // IMAP protocol doesn't support calendar operations natively
        // Calendar functionality would require CalDAV protocol
        _logger.Warning("IMAP protocol does not support calendar attachments. CalDAV would be required.");
        throw new NotSupportedException("IMAP does not support calendar attachments. Use Outlook or Gmail for calendar functionality.");
    }

    public override List<IRequestBundle<ImapRequest>> RenameFolder(RenameFolderRequest request)
    {
        return CreateSingleTaskBundle(async (client, item) =>
        {
            var folder = await client.GetFolderAsync(request.Folder.RemoteFolderId).ConfigureAwait(false);
            await folder.RenameAsync(folder.ParentFolder, request.NewFolderName).ConfigureAwait(false);
        }, request, request);
    }

    public override List<IRequestBundle<ImapRequest>> DeleteFolder(DeleteFolderRequest request)
    {
        return CreateSingleTaskBundle(async (client, item) =>
        {
            var folder = await client.GetFolderAsync(request.Folder.RemoteFolderId).ConfigureAwait(false);
            await folder.DeleteAsync().ConfigureAwait(false);
        }, request, request);
    }

    public override List<IRequestBundle<ImapRequest>> CreateSubFolder(CreateSubFolderRequest request)
    {
        return CreateSingleTaskBundle(async (client, item) =>
        {
            var parentFolder = await client.GetFolderAsync(request.Folder.RemoteFolderId).ConfigureAwait(false);
            await parentFolder.CreateAsync(request.NewFolderName, true).ConfigureAwait(false);
        }, request, request);
    }

    public override List<IRequestBundle<ImapRequest>> CreateCalendarEvent(CreateCalendarEventRequest request)
    {
        var handler = ResolveCalendarOperationHandler();
        return CreateCalendarOperationTaskBundle(
            request,
            async value => await handler.CreateCalendarEventAsync(value).ConfigureAwait(false),
            handler.RequiresConnectedClient);
    }

    public override List<IRequestBundle<ImapRequest>> UpdateCalendarEvent(UpdateCalendarEventRequest request)
    {
        var handler = ResolveCalendarOperationHandler();
        return CreateCalendarOperationTaskBundle(
            request,
            async value => await handler.UpdateCalendarEventAsync(value).ConfigureAwait(false),
            handler.RequiresConnectedClient);
    }

    public override List<IRequestBundle<ImapRequest>> ChangeStartAndEndDate(ChangeStartAndEndDateRequest request)
    {
        var handler = ResolveCalendarOperationHandler();
        return CreateCalendarOperationTaskBundle(
            request,
            async value => await handler.UpdateCalendarEventAsync(value).ConfigureAwait(false),
            handler.RequiresConnectedClient);
    }

    public override List<IRequestBundle<ImapRequest>> DeleteCalendarEvent(DeleteCalendarEventRequest request)
    {
        var handler = ResolveCalendarOperationHandler();
        return CreateCalendarOperationTaskBundle(
            request,
            async value => await handler.DeleteCalendarEventAsync(value).ConfigureAwait(false),
            handler.RequiresConnectedClient);
    }

    public override List<IRequestBundle<ImapRequest>> AcceptEvent(AcceptEventRequest request)
    {
        var handler = ResolveCalendarOperationHandler();
        return CreateCalendarOperationTaskBundle(
            request,
            async value => await handler.AcceptEventAsync(value).ConfigureAwait(false),
            handler.RequiresConnectedClient);
    }

    public override List<IRequestBundle<ImapRequest>> DeclineEvent(DeclineEventRequest request)
    {
        var handler = ResolveCalendarOperationHandler();
        return CreateCalendarOperationTaskBundle(
            request,
            async value => await handler.DeclineEventAsync(value).ConfigureAwait(false),
            handler.RequiresConnectedClient);
    }

    public override List<IRequestBundle<ImapRequest>> TentativeEvent(TentativeEventRequest request)
    {
        var handler = ResolveCalendarOperationHandler();
        return CreateCalendarOperationTaskBundle(
            request,
            async value => await handler.TentativeEventAsync(value).ConfigureAwait(false),
            handler.RequiresConnectedClient);
    }

    private IImapCalendarOperationHandler ResolveCalendarOperationHandler()
    {
        var mode = Account.ServerInformation?.CalendarSupportMode ?? ImapCalendarSupportMode.Disabled;

        return mode switch
        {
            ImapCalendarSupportMode.LocalOnly => _localCalendarOperationHandler,
            ImapCalendarSupportMode.CalDav => _calDavCalendarOperationHandler,
            _ => throw new NotSupportedException("Calendar operations are disabled for this IMAP account.")
        };
    }

    private List<IRequestBundle<ImapRequest>> CreateCalendarOperationTaskBundle<TRequest>(
        TRequest request,
        Func<TRequest, Task> operation,
        bool requiresConnectedClient)
        where TRequest : IRequestBase, IUIChangeRequest
    {
        return
        [
            new ImapRequestBundle(
                new ImapRequest<TRequest>((client, value) => operation(value), request, requiresConnectedClient),
                request,
                request)
        ];
    }

    #endregion

    public override async Task<List<NewMailItemPackage>> CreateNewMailPackagesAsync(ImapMessageCreationPackage message, MailItemFolder assignedFolder, CancellationToken cancellationToken = default)
    {
        var mailCopy = message.MessageSummary.GetMailDetails(assignedFolder, message.MimeMessage);

        // Draft folder message updates must be updated as IsDraft.
        // I couldn't find it in MimeMesssage...

        mailCopy.IsDraft = assignedFolder.SpecialFolderType == SpecialFolderType.Draft;

        // Check draft mapping.
        // This is the same implementation as in the OutlookSynchronizer.

        string draftHeaderValue = null;

        if (message.MimeMessage?.Headers?.Contains(Domain.Constants.WinoLocalDraftHeader) == true)
        {
            draftHeaderValue = message.MimeMessage.Headers[Domain.Constants.WinoLocalDraftHeader];
        }
        else if (message.MessageSummary?.Headers?.Contains(Domain.Constants.WinoLocalDraftHeader) == true)
        {
            draftHeaderValue = message.MessageSummary.Headers[Domain.Constants.WinoLocalDraftHeader];
        }

        if (Guid.TryParse(draftHeaderValue, out Guid localDraftCopyUniqueId))
        {
            // This message belongs to existing local draft copy.
            // We don't need to create a new mail copy for this message, just update the existing one.

            bool isMappingSuccessful = await _imapChangeProcessor.MapLocalDraftAsync(Account.Id, localDraftCopyUniqueId, mailCopy.Id, draftHeaderValue, mailCopy.ThreadId);

            if (isMappingSuccessful) return null;

            // Local copy doesn't exists. Continue execution to insert mail copy.
        }

        var contacts = message.MimeMessage != null
            ? ExtractContactsFromMimeMessage(message.MimeMessage)
            : ExtractContactsFromMessageSummary(message.MessageSummary);

        var package = new NewMailItemPackage(mailCopy, message.MimeMessage, assignedFolder.RemoteFolderId, contacts);

        return
        [
            package
        ];
    }

    private static IReadOnlyList<AccountContact> ExtractContactsFromMimeMessage(MimeMessage mimeMessage)
    {
        if (mimeMessage == null) return [];

        var contacts = new Dictionary<string, AccountContact>(StringComparer.OrdinalIgnoreCase);

        AddFromInternetAddressList(mimeMessage.From);
        AddFromInternetAddressList(mimeMessage.To);
        AddFromInternetAddressList(mimeMessage.Cc);
        AddFromInternetAddressList(mimeMessage.Bcc);
        AddFromInternetAddressList(mimeMessage.ReplyTo);

        if (mimeMessage.Sender is MailboxAddress senderMailbox)
        {
            AddContact(senderMailbox.Address, senderMailbox.Name);
        }

        return contacts.Values.ToList();

        void AddFromInternetAddressList(InternetAddressList addresses)
        {
            if (addresses == null) return;

            foreach (var mailbox in addresses.Mailboxes)
            {
                AddContact(mailbox.Address, mailbox.Name);
            }
        }

        void AddContact(string address, string name)
        {
            var trimmedAddress = address?.Trim();
            if (string.IsNullOrWhiteSpace(trimmedAddress)) return;

            var displayName = string.IsNullOrWhiteSpace(name) ? trimmedAddress : name.Trim();

            contacts[trimmedAddress] = new AccountContact
            {
                Address = trimmedAddress,
                Name = displayName
            };
        }
    }

    private static IReadOnlyList<AccountContact> ExtractContactsFromMessageSummary(IMessageSummary summary)
    {
        if (summary?.Envelope == null) return [];

        var contacts = new Dictionary<string, AccountContact>(StringComparer.OrdinalIgnoreCase);

        AddFromInternetAddressList(summary.Envelope.From);
        AddFromInternetAddressList(summary.Envelope.To);
        AddFromInternetAddressList(summary.Envelope.Cc);
        AddFromInternetAddressList(summary.Envelope.Bcc);
        AddFromInternetAddressList(summary.Envelope.ReplyTo);

        var senderMailbox = summary.Envelope.Sender?.Mailboxes?.FirstOrDefault();
        if (senderMailbox != null)
        {
            AddContact(senderMailbox.Address, senderMailbox.Name);
        }

        return contacts.Values.ToList();

        void AddFromInternetAddressList(InternetAddressList addresses)
        {
            if (addresses == null) return;

            foreach (var mailbox in addresses.Mailboxes)
            {
                AddContact(mailbox.Address, mailbox.Name);
            }
        }

        void AddContact(string address, string name)
        {
            var trimmedAddress = address?.Trim();
            if (string.IsNullOrWhiteSpace(trimmedAddress)) return;

            var displayName = string.IsNullOrWhiteSpace(name) ? trimmedAddress : name.Trim();

            contacts[trimmedAddress] = new AccountContact
            {
                Address = trimmedAddress,
                Name = displayName
            };
        }
    }

    protected override async Task<MailSynchronizationResult> SynchronizeMailsInternalAsync(MailSynchronizationOptions options, CancellationToken cancellationToken = default)
    {
        var downloadedMessageIds = new List<string>();
        var folderResults = new List<FolderSyncResult>();

        _logger.Information("Internal synchronization started for {Name}", Account.Name);
        _logger.Information("Options: {Options}", options);

        try
        {
            _isFolderStructureChanged = false;

            // Set indeterminate progress initially
            UpdateSyncProgress(0, 0, "Synchronizing...");

            bool shouldDoFolderSync = options.Type == MailSynchronizationType.FullFolders || options.Type == MailSynchronizationType.FoldersOnly;

            if (shouldDoFolderSync)
            {
                await SynchronizeFoldersAsync(cancellationToken).ConfigureAwait(false);

                if (_isFolderStructureChanged)
                {
                    WeakReferenceMessenger.Default.Send(new AccountFolderConfigurationUpdated(Account.Id));
                }
            }

            if (options.Type != MailSynchronizationType.FoldersOnly)
            {
                var synchronizationFolders = await _imapChangeProcessor.GetSynchronizationFoldersAsync(options).ConfigureAwait(false);

                var totalFolders = synchronizationFolders.Count;
                const int maxParallelFolderSyncClients = 3;
                var folderSyncSemaphore = new SemaphoreSlim(maxParallelFolderSyncClients, maxParallelFolderSyncClients);
                using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
                var linkedToken = linkedCancellationTokenSource.Token;
                var resultLock = new object();
                int completedFolders = 0;

                var syncTasks = synchronizationFolders.Select(async folder =>
                {
                    await folderSyncSemaphore.WaitAsync(linkedToken).ConfigureAwait(false);

                    try
                    {
                        IImapClient client = null;

                        try
                        {
                            client = await _clientPool.GetClientAsync(linkedToken).ConfigureAwait(false);
                            var folderResult = await _unifiedSynchronizer
                                .SynchronizeFolderAsync(client, folder, this, Account.ServerInformation?.IncomingServer, linkedToken)
                                .ConfigureAwait(false);

                            List<string> folderDownloadedIds = null;
                            if (folderResult.Success && folderResult.DownloadedCount > 0)
                            {
                                folderDownloadedIds = await GetDownloadedIdsForFolderAsync(folder, folderResult.DownloadedCount).ConfigureAwait(false);
                            }

                            lock (resultLock)
                            {
                                folderResults.Add(folderResult);
                                if (folderDownloadedIds != null && folderDownloadedIds.Count > 0)
                                {
                                    downloadedMessageIds.AddRange(folderDownloadedIds);
                                }
                            }
                        }
                        finally
                        {
                            if (client != null)
                            {
                                _clientPool.Release(client);
                            }
                        }
                    }
                    catch (OperationCanceledException)
                    {
                        throw;
                    }
                    catch (Exception ex)
                    {
                        var errorContext = new SynchronizerErrorContext
                        {
                            Account = Account,
                            ErrorMessage = ex.Message,
                            Exception = ex,
                            FolderId = folder.Id,
                            FolderName = folder.FolderName,
                            OperationType = "ImapFolderSync"
                        };

                        _ = await _errorHandlerFactory.HandleErrorAsync(errorContext).ConfigureAwait(false);
                        var failedResult = FolderSyncResult.Failed(folder.Id, folder.FolderName, errorContext);

                        lock (resultLock)
                        {
                            folderResults.Add(failedResult);
                        }

                        if (!errorContext.CanContinueSync)
                        {
                            _logger.Error(ex, "Folder {FolderName} sync failed with fatal error", folder.FolderName);
                            linkedCancellationTokenSource.Cancel();
                            throw;
                        }

                        _logger.Warning(ex, "Folder {FolderName} sync failed, continuing with other folders", folder.FolderName);
                    }
                    finally
                    {
                        folderSyncSemaphore.Release();

                        var completed = Interlocked.Increment(ref completedFolders);
                        UpdateSyncProgress(totalFolders, totalFolders - completed, $"Syncing {folder.FolderName}...");
                    }
                }).ToList();

                await Task.WhenAll(syncTasks).ConfigureAwait(false);

                if (cancellationToken.IsCancellationRequested) return MailSynchronizationResult.Canceled;
            }
        }
        catch (OperationCanceledException)
        {
            _logger.Information("Synchronization was canceled for {Name}", Account.Name);
            return MailSynchronizationResult.Canceled;
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "Synchronization failed for {Name}", Account.Name);
            return MailSynchronizationResult.Failed(ex);
        }
        finally
        {
            // Reset progress
            ResetSyncProgress();
        }

        // Get all unread new downloaded items and return in the result.
        // This is primarily used in notifications.

        var unreadNewItems = await _imapChangeProcessor.GetDownloadedUnreadMailsAsync(Account.Id, downloadedMessageIds).ConfigureAwait(false);

        return MailSynchronizationResult.CompletedWithFolderResults(unreadNewItems, folderResults);
    }

    /// <summary>
    /// Gets the most recent downloaded message IDs for a folder.
    /// Used for notification purposes after sync completes.
    /// </summary>
    private async Task<List<string>> GetDownloadedIdsForFolderAsync(MailItemFolder folder, int count)
    {
        // Get the most recent mail IDs from the folder
        var recentMails = await _imapChangeProcessor.GetRecentMailIdsForFolderAsync(folder.Id, count).ConfigureAwait(false);
        return recentMails?.ToList() ?? new List<string>();
    }

    public override async Task ExecuteNativeRequestsAsync(List<IRequestBundle<ImapRequest>> batchedRequests, CancellationToken cancellationToken = default)
    {
        // First apply the UI changes for each bundle.
        // This is important to reflect changes to the UI before the network call is done.

        foreach (var item in batchedRequests)
        {
            if (ShouldApplyOptimisticUIChanges(item.Request))
            {
                item.Request.ApplyUIChanges();
            }
        }

        // All task bundles will execute on the same client.
        // Tasks themselves don't pull the client from the pool
        // because exception handling is easier this way.
        // Also we might parallelize these bundles later on for additional performance.

        foreach (var item in batchedRequests)
        {
            // At this point this client is ready to execute async commands.
            // Each task bundle will await and execution will continue in case of error.

            IImapClient executorClient = null;

            bool isCrashed = false;

            try
            {
                if (item.NativeRequest.RequiresConnectedClient)
                {
                    executorClient = await _clientPool.GetClientAsync();
                }
            }
            catch (ImapClientPoolException)
            {
                // Client pool failed to get a client.
                // Requests may not be executed at this point.

                if (ShouldApplyOptimisticUIChanges(item.Request))
                {
                    item.Request.RevertUIChanges();
                }

                isCrashed = true;
                throw;
            }
            finally
            {
                // Make sure that the client is released from the pool for next usages if error occurs.
                if (isCrashed && executorClient != null)
                {
                    _clientPool.Release(executorClient);
                }
            }

            try
            {
                await item.NativeRequest.IntegratorTask(executorClient, item.Request).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                var errorContext = new SynchronizerErrorContext
                {
                    Account = Account,
                    ErrorCode = ex is FolderNotFoundException ? 404 : null,
                    ErrorMessage = ex.Message,
                    Exception = ex,
                    RequestBundle = item,
                    Request = item.Request,
                    OperationType = "RequestExecution",
                    IsEntityNotFound = ex is FolderNotFoundException || ex is SynchronizerEntityNotFoundException
                };

                var handled = await _errorHandlerFactory.HandleErrorAsync(errorContext).ConfigureAwait(false);

                if (!handled)
                {
                    CaptureSynchronizationIssue(errorContext);

                    if (ShouldApplyOptimisticUIChanges(item.Request))
                    {
                        item.Request.RevertUIChanges();
                    }
                    throw;
                }
            }
            finally
            {
                if (executorClient != null)
                {
                    _clientPool.Release(executorClient);
                }
            }
        }
    }

    private bool ShouldApplyOptimisticUIChanges(IRequestBase request)
    {
        // Mail changes are always applied.
        // Calendar changes are applied only if calendar is not in local mode.
        // Database updates are immidiate and will be reflected in the UI right after the request is processed, so no need for optimistic changes.

        if (request is not ICalendarActionRequest)
        {
            return true;
        }

        var mode = Account.ServerInformation?.CalendarSupportMode ?? ImapCalendarSupportMode.Disabled;
        return mode != ImapCalendarSupportMode.LocalOnly;
    }

    /// <summary>
    /// Assigns special folder type for the given local folder.
    /// If server doesn't support special folders, we can't determine the type. MailKit will throw for GetFolder.
    /// Default type is Other.
    /// </summary>
    /// <param name="executorClient">ImapClient from the pool</param>
    /// <param name="remoteFolder">Assigning remote folder.</param>
    /// <param name="localFolder">Assigning local folder.</param>
    private void AssignSpecialFolderType(IImapClient executorClient, IMailFolder remoteFolder, MailItemFolder localFolder)
    {
        // Inbox is awlawys available. Don't miss it for assignment even though XList or SpecialUser is not supported.
        if (executorClient.Inbox == remoteFolder)
        {
            localFolder.SpecialFolderType = SpecialFolderType.Inbox;
            return;

        }

        bool isSpecialFoldersSupported = executorClient.Capabilities.HasFlag(ImapCapabilities.SpecialUse) || executorClient.Capabilities.HasFlag(ImapCapabilities.XList);

        if (!isSpecialFoldersSupported)
        {
            localFolder.SpecialFolderType = SpecialFolderType.Other;
            return;
        }

        if (remoteFolder == executorClient.Inbox)
            localFolder.SpecialFolderType = SpecialFolderType.Inbox;
        else if (remoteFolder == executorClient.GetFolder(SpecialFolder.Drafts))
            localFolder.SpecialFolderType = SpecialFolderType.Draft;
        else if (remoteFolder == executorClient.GetFolder(SpecialFolder.Junk))
            localFolder.SpecialFolderType = SpecialFolderType.Junk;
        else if (remoteFolder == executorClient.GetFolder(SpecialFolder.Trash))
            localFolder.SpecialFolderType = SpecialFolderType.Deleted;
        else if (remoteFolder == executorClient.GetFolder(SpecialFolder.Sent))
            localFolder.SpecialFolderType = SpecialFolderType.Sent;
        else if (remoteFolder == executorClient.GetFolder(SpecialFolder.Archive))
            localFolder.SpecialFolderType = SpecialFolderType.Archive;
        else if (remoteFolder == executorClient.GetFolder(SpecialFolder.Important))
            localFolder.SpecialFolderType = SpecialFolderType.Important;
        else if (remoteFolder == executorClient.GetFolder(SpecialFolder.Flagged))
            localFolder.SpecialFolderType = SpecialFolderType.Starred;
    }

    private async Task SynchronizeFoldersAsync(CancellationToken cancellationToken = default)
    {
        // https://www.rfc-editor.org/rfc/rfc4549#section-1.1

        var localFolders = await _imapChangeProcessor.GetLocalFoldersAsync(Account.Id).ConfigureAwait(false);

        IImapClient executorClient = null;

        try
        {
            List<MailItemFolder> insertedFolders = new();
            List<MailItemFolder> updatedFolders = new();
            List<MailItemFolder> deletedFolders = new();

            executorClient = await _clientPool.GetClientAsync().ConfigureAwait(false);

            var remoteFolders = (await executorClient.GetFoldersAsync(executorClient.PersonalNamespaces[0], cancellationToken: cancellationToken)).ToList();

            // 1. First check deleted folders.

            // 1.a If local folder doesn't exists remotely, delete it.
            // 1.b If local folder exists remotely, check if it is still a valid folder. If UidValidity is changed, delete it.

            foreach (var localFolder in localFolders)
            {
                IMailFolder remoteFolder = null;

                try
                {
                    remoteFolder = remoteFolders.FirstOrDefault(a => a.FullName == localFolder.RemoteFolderId);

                    bool shouldDeleteLocalFolder = false;

                    // Check UidValidity of the remote folder if exists.

                    if (remoteFolder != null)
                    {
                        // UidValidity won't be available until it's opened.
                        await remoteFolder.OpenAsync(FolderAccess.ReadOnly, cancellationToken).ConfigureAwait(false);

                        shouldDeleteLocalFolder = remoteFolder.UidValidity != localFolder.UidValidity;
                    }
                    else
                    {
                        // Remote folder doesn't exist. Delete it.
                        shouldDeleteLocalFolder = true;
                    }

                    if (shouldDeleteLocalFolder)
                    {
                        await _imapChangeProcessor.DeleteFolderAsync(Account.Id, localFolder.RemoteFolderId).ConfigureAwait(false);

                        deletedFolders.Add(localFolder);
                    }
                }
                catch (Exception)
                {
                    throw;
                }
                finally
                {
                    if (remoteFolder != null)
                    {
                        await remoteFolder.CloseAsync().ConfigureAwait(false);
                    }
                }
            }

            deletedFolders.ForEach(a => localFolders.Remove(a));

            // 2. Get all remote folders and insert/update each of them.

            var nameSpace = executorClient.PersonalNamespaces[0];

            IMailFolder inbox = executorClient.Inbox;

            // Sometimes Inbox is the root namespace. We need to check for that.
            if (inbox != null && !remoteFolders.Contains(inbox))
                remoteFolders.Add(inbox);

            foreach (var remoteFolder in remoteFolders)
            {
                // Namespaces are not needed as folders.
                // Non-existed folders don't need to be synchronized.

                if (remoteFolder.IsNamespace && !remoteFolder.Attributes.HasFlag(FolderAttributes.Inbox) || !remoteFolder.Exists)
                    continue;

                // Ignore folders that can't be opened.
                if (!remoteFolder.CanOpen) continue;

                var existingLocalFolder = localFolders.FirstOrDefault(a => a.RemoteFolderId == remoteFolder.FullName);

                if (existingLocalFolder == null)
                {
                    // Folder doesn't exist locally. Insert it.

                    var localFolder = remoteFolder.GetLocalFolder();

                    // Check whether this is a special folder.
                    AssignSpecialFolderType(executorClient, remoteFolder, localFolder);

                    bool isSystemFolder = localFolder.SpecialFolderType != SpecialFolderType.Other;

                    localFolder.IsSynchronizationEnabled = isSystemFolder;
                    localFolder.IsSticky = isSystemFolder;

                    // By default, all special folders update unread count in the UI except Trash.
                    localFolder.ShowUnreadCount = localFolder.SpecialFolderType != SpecialFolderType.Deleted || localFolder.SpecialFolderType != SpecialFolderType.Other;

                    localFolder.MailAccountId = Account.Id;

                    // Sometimes sub folders are parented under Inbox.
                    // Even though this makes sense in server level, in the client it sucks.
                    // That will make sub folders to be parented under Inbox in the client.
                    // Instead, we will mark them as non-parented folders.
                    // This is better. Model allows personalized folder structure anyways
                    // even though we don't have the page/control to adjust it.

                    if (remoteFolder.ParentFolder == executorClient.Inbox)
                        localFolder.ParentRemoteFolderId = string.Empty;

                    // Set UidValidity for cache expiration.
                    // Folder must be opened for this.

                    await remoteFolder.OpenAsync(FolderAccess.ReadOnly, cancellationToken);

                    localFolder.UidValidity = remoteFolder.UidValidity;

                    await remoteFolder.CloseAsync(cancellationToken: cancellationToken);

                    insertedFolders.Add(localFolder);
                }
                else
                {
                    // Update existing folder. Right now we only update the name.

                    // TODO: Moving folders around different parents. This is not supported right now.
                    // We will need more comphrensive folder update mechanism to support this.

                    if (ShouldUpdateFolder(remoteFolder, existingLocalFolder))
                    {
                        existingLocalFolder.FolderName = remoteFolder.Name;
                        updatedFolders.Add(existingLocalFolder);
                    }
                    else
                    {
                        // Remove it from the local folder list to skip additional folder updates.
                        localFolders.Remove(existingLocalFolder);
                    }
                }
            }

            // Process changes in order-> Insert, Update. Deleted ones are already processed.

            foreach (var folder in insertedFolders)
            {
                await _imapChangeProcessor.InsertFolderAsync(folder).ConfigureAwait(false);
            }

            foreach (var folder in updatedFolders)
            {
                await _imapChangeProcessor.UpdateFolderAsync(folder).ConfigureAwait(false);
            }

            if (insertedFolders.Any() || deletedFolders.Any() || updatedFolders.Any())
            {
                _isFolderStructureChanged = true;
            }
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "Synchronizing IMAP folders failed.");

            throw;
        }
        finally
        {
            if (executorClient != null)
            {
                _clientPool.Release(executorClient);
            }
        }
    }

    public override async Task<List<MailCopy>> OnlineSearchAsync(string queryText, List<IMailItemFolder> folders, CancellationToken cancellationToken = default)
    {
        IImapClient client = null;

        try
        {
            client = await _clientPool.GetClientAsync().ConfigureAwait(false);

            List<MailCopy> searchResults = [];
            List<string> searchResultFolderMailUids = [];

            foreach (var folder in folders)
            {
                if (folder is not MailItemFolder localFolder)
                    continue;

                var remoteFolder = await client.GetFolderAsync(folder.RemoteFolderId, cancellationToken).ConfigureAwait(false);
                await remoteFolder.OpenAsync(FolderAccess.ReadOnly, cancellationToken).ConfigureAwait(false);

                // Look for subject and body.
                var query = SearchQuery.BodyContains(queryText).Or(SearchQuery.SubjectContains(queryText));

                var searchResultsInFolder = await remoteFolder.SearchAsync(query, cancellationToken).ConfigureAwait(false);
                Dictionary<string, UniqueId> searchResultsIdsInFolder = [];

                foreach (var searchResultId in searchResultsInFolder)
                {
                    var folderMailUid = MailkitClientExtensions.CreateUid(folder.Id, searchResultId.Id);
                    searchResultFolderMailUids.Add(folderMailUid);
                    searchResultsIdsInFolder.Add(folderMailUid, searchResultId);
                }

                // Populate no foundIds
                var foundIds = await _imapChangeProcessor.AreMailsExistsAsync(searchResultsIdsInFolder.Select(a => a.Key));
                var notFoundIds = searchResultsIdsInFolder.Keys.Except(foundIds);

                List<UniqueId> nonExistingUniqueIds = [];
                foreach (var nonExistingId in notFoundIds)
                {
                    nonExistingUniqueIds.Add(searchResultsIdsInFolder[nonExistingId]);
                }

                if (nonExistingUniqueIds.Count != 0)
                {
                    await _unifiedSynchronizer
                        .DownloadMessagesByUidsAsync(client, remoteFolder, localFolder, nonExistingUniqueIds, this, cancellationToken)
                        .ConfigureAwait(false);
                }

                await remoteFolder.CloseAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
            }

            return await _imapChangeProcessor.GetMailCopiesAsync(searchResultFolderMailUids);
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Failed to perform online imap search.");
            throw;
        }
        finally
        {
            _clientPool.Release(client);
        }
    }

    /// <summary>
    /// Whether the local folder should be updated with the remote folder.
    /// IMAP only compares folder name for now.
    /// </summary>
    /// <param name="remoteFolder">Remote folder</param>
    /// <param name="localFolder">Local folder.</param>
    public bool ShouldUpdateFolder(IMailFolder remoteFolder, MailItemFolder localFolder)
        => !localFolder.FolderName.Equals(remoteFolder.Name, StringComparison.OrdinalIgnoreCase);

    protected override async Task<CalendarSynchronizationResult> SynchronizeCalendarEventsInternalAsync(CalendarSynchronizationOptions options, CancellationToken cancellationToken = default)
    {
        if (Account.ProviderType != MailProviderType.IMAP4 || !Account.IsCalendarAccessGranted || Account.ServerInformation == null)
            return CalendarSynchronizationResult.Empty;

        if (Account.ServerInformation.CalendarSupportMode is ImapCalendarSupportMode.Disabled or ImapCalendarSupportMode.LocalOnly)
            return CalendarSynchronizationResult.Empty;

        var calDavServiceUri = await ResolveCalDavServiceUriAsync(cancellationToken).ConfigureAwait(false);
        if (calDavServiceUri == null)
        {
            _logger.Information("Skipping calendar sync for {AccountName}: CalDAV endpoint is not configured.", Account.Name);
            return CalendarSynchronizationResult.Empty;
        }

        var password = ResolveCalDavPassword();
        if (string.IsNullOrWhiteSpace(password))
        {
            _logger.Warning("Skipping calendar sync for {AccountName}: empty credentials.", Account.Name);
            return CalendarSynchronizationResult.Empty;
        }

        cancellationToken.ThrowIfCancellationRequested();

        var calDavUsername = ResolveCalDavUsername();
        if (string.IsNullOrWhiteSpace(calDavUsername))
        {
            _logger.Warning("Skipping calendar sync for {AccountName}: account email address is empty for CalDAV credentials.", Account.Name);
            return CalendarSynchronizationResult.Empty;
        }

        var activeConnection = new CalDavConnectionSettings
        {
            ServiceUri = calDavServiceUri,
            Username = calDavUsername,
            Password = password
        };

        IReadOnlyList<CalDavCalendar> remoteCalendars;

        try
        {
            remoteCalendars = await _calDavClient
                .DiscoverCalendarsAsync(activeConnection, cancellationToken)
                .ConfigureAwait(false);
        }
        catch (UnauthorizedAccessException)
        {
            _logger.Warning("Skipping calendar sync for {AccountName}: CalDAV authentication failed for username {Username}.", Account.Name, calDavUsername);
            return CalendarSynchronizationResult.Empty;
        }

        await SynchronizeCalendarMetadataAsync(remoteCalendars).ConfigureAwait(false);

        if (options?.Type == CalendarSynchronizationType.CalendarMetadata)
            return CalendarSynchronizationResult.Empty;

        var localCalendars = await _imapChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);
        var remoteCalendarsById = remoteCalendars.ToDictionary(c => c.RemoteCalendarId, StringComparer.OrdinalIgnoreCase);

        if (options?.Type == CalendarSynchronizationType.SingleCalendar && options.SynchronizationCalendarIds?.Count > 0)
        {
            localCalendars = localCalendars
                .Where(c => options.SynchronizationCalendarIds.Contains(c.Id))
                .ToList();
        }

        localCalendars = localCalendars
            .Where(c => c.IsSynchronizationEnabled)
            .ToList();

        var periodStartUtc = DateTimeOffset.UtcNow.AddYears(-1);
        var periodEndUtc = DateTimeOffset.UtcNow.AddYears(2);

        var totalCalendars = localCalendars.Count;
        if (totalCalendars > 0)
        {
            UpdateSyncProgress(totalCalendars, totalCalendars, Translator.SyncAction_SynchronizingCalendarEvents);
        }

        for (int i = 0; i < totalCalendars; i++)
        {
            var localCalendar = localCalendars[i];

            cancellationToken.ThrowIfCancellationRequested();

            if (!remoteCalendarsById.TryGetValue(localCalendar.RemoteCalendarId, out var remoteCalendar))
                continue;

            var remoteToken = BuildCalendarDeltaToken(remoteCalendar);

            var isInitialSync = string.IsNullOrWhiteSpace(localCalendar.SynchronizationDeltaToken);
            var tokenChanged = !string.Equals(localCalendar.SynchronizationDeltaToken, remoteToken, StringComparison.Ordinal);
            var forceSync = options?.Type is CalendarSynchronizationType.ExecuteRequests or CalendarSynchronizationType.SingleCalendar;

            if (!isInitialSync && !tokenChanged && !forceSync)
                continue;

            var remoteEvents = await _calDavClient.GetCalendarEventsAsync(
                activeConnection,
                remoteCalendar,
                periodStartUtc,
                periodEndUtc,
                cancellationToken).ConfigureAwait(false);
            var remoteEventIds = new HashSet<string>(
                remoteEvents
                    .Where(e => !string.IsNullOrWhiteSpace(e.RemoteEventId))
                    .Select(e => e.RemoteEventId),
                StringComparer.OrdinalIgnoreCase);

            foreach (var remoteEvent in remoteEvents)
            {
                var existingLocalItem = await _imapChangeProcessor
                    .GetCalendarItemAsync(localCalendar.Id, remoteEvent.RemoteEventId)
                    .ConfigureAwait(false);

                var shouldSkipUnchangedEvent = await ShouldSkipUnchangedCalDavEventAsync(
                    localCalendar,
                    existingLocalItem,
                    remoteEvent).ConfigureAwait(false);

                if (shouldSkipUnchangedEvent)
                    continue;

                await _imapChangeProcessor
                    .ManageCalendarEventAsync(remoteEvent, localCalendar, Account)
                    .ConfigureAwait(false);

                if (string.IsNullOrWhiteSpace(remoteEvent.IcsContent))
                    continue;

                var localItem = existingLocalItem ?? await _imapChangeProcessor
                    .GetCalendarItemAsync(localCalendar.Id, remoteEvent.RemoteEventId)
                    .ConfigureAwait(false);

                if (localItem == null)
                    continue;

                await _imapChangeProcessor
                    .SaveCalendarItemIcsAsync(
                        Account.Id,
                        localCalendar.Id,
                        localItem.Id,
                        remoteEvent.RemoteEventId,
                        remoteEvent.RemoteResourceHref,
                        remoteEvent.ETag,
                        remoteEvent.IcsContent)
                    .ConfigureAwait(false);
            }

            await ReconcileDeletedCalendarItemsAsync(localCalendar, periodStartUtc, periodEndUtc, remoteEventIds)
                .ConfigureAwait(false);

            localCalendar.SynchronizationDeltaToken = remoteToken;
            await _imapChangeProcessor.UpdateAccountCalendarAsync(localCalendar).ConfigureAwait(false);
            UpdateSyncProgress(totalCalendars, totalCalendars - (i + 1), Translator.SyncAction_SynchronizingCalendarEvents);
        }

        return CalendarSynchronizationResult.Empty;
    }

    private async Task<bool> ShouldSkipUnchangedCalDavEventAsync(
        AccountCalendar localCalendar,
        CalendarItem existingLocalItem,
        CalDavCalendarEvent remoteEvent)
    {
        if (localCalendar == null || existingLocalItem == null || remoteEvent == null)
            return false;

        // Ensure unresolved parent-child linkage still gets corrected when required.
        if (!string.IsNullOrWhiteSpace(remoteEvent.SeriesMasterRemoteEventId) &&
            existingLocalItem.RecurringCalendarItemId == null)
        {
            return false;
        }

        if (string.IsNullOrWhiteSpace(remoteEvent.ETag))
            return false;

        var savedETag = await _imapChangeProcessor
            .GetCalendarItemIcsETagAsync(Account.Id, localCalendar.Id, existingLocalItem.Id)
            .ConfigureAwait(false);

        if (string.IsNullOrWhiteSpace(savedETag))
            return false;

        return string.Equals(savedETag.Trim(), remoteEvent.ETag.Trim(), StringComparison.Ordinal);
    }

    private async Task ReconcileDeletedCalendarItemsAsync(
        AccountCalendar localCalendar,
        DateTimeOffset periodStartUtc,
        DateTimeOffset periodEndUtc,
        HashSet<string> remoteEventIds)
    {
        var syncPeriod = new TimeRange(periodStartUtc.UtcDateTime, periodEndUtc.UtcDateTime);
        var localEventsInWindow = await _calendarService
            .GetCalendarEventsAsync(localCalendar, syncPeriod)
            .ConfigureAwait(false);

        foreach (var localEvent in localEventsInWindow)
        {
            if (string.IsNullOrWhiteSpace(localEvent.RemoteEventId))
                continue;

            if (remoteEventIds.Contains(localEvent.RemoteEventId))
                continue;

            await _imapChangeProcessor.DeleteCalendarItemAsync(localEvent.Id).ConfigureAwait(false);
        }
    }

    private static string BuildCalendarDeltaToken(CalDavCalendar calendar)
    {
        if (calendar == null)
            return string.Empty;

        var syncToken = calendar.SyncToken?.Trim() ?? string.Empty;
        var ctag = calendar.CTag?.Trim() ?? string.Empty;

        if (!string.IsNullOrWhiteSpace(syncToken) && !string.IsNullOrWhiteSpace(ctag))
            return $"{syncToken}|{ctag}";

        return !string.IsNullOrWhiteSpace(syncToken) ? syncToken : ctag;
    }

    private async Task<Uri> ResolveCalDavServiceUriAsync(CancellationToken cancellationToken)
    {
        var explicitCalDavUri = TryGetExplicitCalDavServiceUri();
        if (explicitCalDavUri != null)
        {
            _cachedCalDavServiceUri = explicitCalDavUri;
            _isCalDavDiscoveryAttempted = true;
            return _cachedCalDavServiceUri;
        }

        if (_cachedCalDavServiceUri != null)
            return _cachedCalDavServiceUri;

        if (_isCalDavDiscoveryAttempted)
            return null;

        await _calDavDiscoveryLock.WaitAsync(cancellationToken).ConfigureAwait(false);

        try
        {
            if (_cachedCalDavServiceUri != null)
                return _cachedCalDavServiceUri;

            if (_isCalDavDiscoveryAttempted)
                return null;

            _isCalDavDiscoveryAttempted = true;

            var emailCandidates = new[]
            {
                Account.ServerInformation?.Address,
                Account.Address
            }
            .Where(value => !string.IsNullOrWhiteSpace(value) && value.Contains('@'))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToList();

            foreach (var email in emailCandidates)
            {
                var discoveredUri = await _autoDiscoveryService
                    .DiscoverCalDavServiceUriAsync(email, cancellationToken)
                    .ConfigureAwait(false);

                if (discoveredUri == null)
                    continue;

                _cachedCalDavServiceUri = discoveredUri;
                return _cachedCalDavServiceUri;
            }

            if (Account.SpecialImapProvider == SpecialImapProvider.iCloud)
            {
                _cachedCalDavServiceUri = new Uri("https://caldav.icloud.com/");
                return _cachedCalDavServiceUri;
            }

            if (Account.SpecialImapProvider == SpecialImapProvider.Yahoo)
            {
                _cachedCalDavServiceUri = new Uri("https://caldav.calendar.yahoo.com/");
                return _cachedCalDavServiceUri;
            }

            return null;
        }
        finally
        {
            _calDavDiscoveryLock.Release();
        }
    }

    private string ResolveCalDavPassword()
    {
        if (!string.IsNullOrWhiteSpace(Account.ServerInformation?.CalDavPassword))
            return Account.ServerInformation.CalDavPassword;

        if (!string.IsNullOrWhiteSpace(Account.ServerInformation?.IncomingServerPassword))
            return Account.ServerInformation.IncomingServerPassword;

        if (!string.IsNullOrWhiteSpace(Account.ServerInformation?.OutgoingServerPassword))
            return Account.ServerInformation.OutgoingServerPassword;

        return string.Empty;
    }

    private string ResolveCalDavUsername()
    {
        if (!string.IsNullOrWhiteSpace(Account.ServerInformation?.CalDavUsername))
            return Account.ServerInformation.CalDavUsername.Trim();

        if (!string.IsNullOrWhiteSpace(Account.ServerInformation?.Address))
            return Account.ServerInformation.Address.Trim();

        if (!string.IsNullOrWhiteSpace(Account.Address))
            return Account.Address.Trim();

        return string.Empty;
    }

    private Uri TryGetExplicitCalDavServiceUri()
    {
        var configuredUrl = Account.ServerInformation?.CalDavServiceUrl;
        if (string.IsNullOrWhiteSpace(configuredUrl))
            return null;

        if (!Uri.TryCreate(configuredUrl, UriKind.Absolute, out var uri))
        {
            _logger.Warning("Configured CalDAV URL is invalid for account {AccountName}: {Url}", Account.Name, configuredUrl);
            return null;
        }

        return uri;
    }

    private async Task SynchronizeCalendarMetadataAsync(IReadOnlyList<CalDavCalendar> remoteCalendars)
    {
        var localCalendars = await _imapChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);
        var remoteCalendarsById = remoteCalendars
            .GroupBy(c => c.RemoteCalendarId, StringComparer.OrdinalIgnoreCase)
            .ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
        var usedCalendarColors = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        var remotePrimaryCalendarId = remoteCalendars.FirstOrDefault()?.RemoteCalendarId;

        foreach (var localCalendar in localCalendars.ToList())
        {
            if (remoteCalendarsById.ContainsKey(localCalendar.RemoteCalendarId))
                continue;

            await _imapChangeProcessor
                .DeleteCalendarIcsForCalendarAsync(Account.Id, localCalendar.Id)
                .ConfigureAwait(false);
            await _imapChangeProcessor.DeleteAccountCalendarAsync(localCalendar).ConfigureAwait(false);
            localCalendars.Remove(localCalendar);
        }

        foreach (var remoteCalendar in remoteCalendars)
        {
            var existingLocal = localCalendars.FirstOrDefault(c =>
                string.Equals(c.RemoteCalendarId, remoteCalendar.RemoteCalendarId, StringComparison.OrdinalIgnoreCase));

            var isPrimary = string.Equals(remoteCalendar.RemoteCalendarId, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);

            if (existingLocal == null)
            {
                var newCalendar = new AccountCalendar
                {
                    Id = Guid.NewGuid(),
                    AccountId = Account.Id,
                    RemoteCalendarId = remoteCalendar.RemoteCalendarId,
                    Name = remoteCalendar.Name,
                    IsPrimary = isPrimary,
                    IsSynchronizationEnabled = true,
                    IsExtended = true,
                    BackgroundColorHex = ColorHelpers.GetDistinctFlatColorHex(usedCalendarColors),
                    TimeZone = "UTC",
                    SynchronizationDeltaToken = string.Empty
                };

                newCalendar.TextColorHex = ColorHelpers.GetReadableTextColorHex(newCalendar.BackgroundColorHex);
                usedCalendarColors.Add(newCalendar.BackgroundColorHex);
                await _imapChangeProcessor.InsertAccountCalendarAsync(newCalendar).ConfigureAwait(false);
                continue;
            }

            var resolvedColor = ColorHelpers.GetDistinctFlatColorHex(usedCalendarColors, existingLocal.BackgroundColorHex);
            var shouldUpdate = !string.Equals(existingLocal.Name, remoteCalendar.Name, StringComparison.Ordinal)
                               || existingLocal.IsPrimary != isPrimary
                               || !string.Equals(existingLocal.BackgroundColorHex, resolvedColor, StringComparison.OrdinalIgnoreCase);

            if (!shouldUpdate)
            {
                usedCalendarColors.Add(resolvedColor);
                continue;
            }

            existingLocal.Name = remoteCalendar.Name;
            existingLocal.IsPrimary = isPrimary;
            existingLocal.BackgroundColorHex = resolvedColor;
            existingLocal.TextColorHex = ColorHelpers.GetReadableTextColorHex(existingLocal.BackgroundColorHex);
            usedCalendarColors.Add(existingLocal.BackgroundColorHex);
            await _imapChangeProcessor.UpdateAccountCalendarAsync(existingLocal).ConfigureAwait(false);
        }
    }

    private interface IImapCalendarOperationHandler
    {
        bool RequiresConnectedClient { get; }
        Task CreateCalendarEventAsync(CreateCalendarEventRequest request);
        Task UpdateCalendarEventAsync(UpdateCalendarEventRequest request);
        Task DeleteCalendarEventAsync(DeleteCalendarEventRequest request);
        Task AcceptEventAsync(AcceptEventRequest request);
        Task DeclineEventAsync(DeclineEventRequest request);
        Task TentativeEventAsync(TentativeEventRequest request);
    }

    private class LocalCalendarOperationHandler : IImapCalendarOperationHandler
    {
        private readonly MailAccount _account;
        private readonly IImapChangeProcessor _changeProcessor;
        private readonly ICalendarService _calendarService;
        private readonly string _applicationDataFolderPath;
        private readonly string _resourceScheme;

        public bool RequiresConnectedClient => false;

        public LocalCalendarOperationHandler(MailAccount account, IImapChangeProcessor changeProcessor, ICalendarService calendarService, string applicationDataFolderPath, string resourceScheme)
        {
            _account = account;
            _changeProcessor = changeProcessor;
            _calendarService = calendarService;
            _applicationDataFolderPath = applicationDataFolderPath;
            _resourceScheme = resourceScheme;
        }

        public async Task CreateCalendarEventAsync(CreateCalendarEventRequest request)
        {
            var item = request.PreparedItem;
            var attendees = request.PreparedEvent.Attendees;
            var reminders = request.PreparedEvent.Reminders;
            EnsureCalendarItemDefaults(item, _account, "local");
            item.AssignedCalendar ??= await _calendarService.GetAccountCalendarAsync(item.CalendarId).ConfigureAwait(false);

            var existing = await _calendarService.GetCalendarItemAsync(item.Id).ConfigureAwait(false);

            if (existing == null)
                await _calendarService.CreateNewCalendarItemAsync(item, attendees).ConfigureAwait(false);
            else
                await _calendarService.UpdateCalendarItemAsync(item, attendees).ConfigureAwait(false);

            await _calendarService.SaveRemindersAsync(item.Id, reminders).ConfigureAwait(false);
            await SaveAttachmentsAsync(request.ComposeResult, item.Id).ConfigureAwait(false);
            await PersistIcsAsync(item, attendees).ConfigureAwait(false);
        }

        public async Task UpdateCalendarEventAsync(UpdateCalendarEventRequest request)
        {
            var item = request.Item;
            EnsureCalendarItemDefaults(item, _account, "local");
            item.AssignedCalendar ??= await _calendarService.GetAccountCalendarAsync(item.CalendarId).ConfigureAwait(false);

            var attendees = request.Attendees ?? await _calendarService.GetAttendeesAsync(item.Id).ConfigureAwait(false);

            await _calendarService.UpdateCalendarItemAsync(item, attendees).ConfigureAwait(false);
            await PersistIcsAsync(item, attendees).ConfigureAwait(false);
        }

        public Task DeleteCalendarEventAsync(DeleteCalendarEventRequest request)
            => _changeProcessor.DeleteCalendarItemAsync(request.Item.Id);

        public async Task AcceptEventAsync(AcceptEventRequest request)
        {
            request.Item.Status = CalendarItemStatus.Accepted;
            await UpdateStatusAsync(request.Item).ConfigureAwait(false);
        }

        public async Task DeclineEventAsync(DeclineEventRequest request)
        {
            request.Item.Status = CalendarItemStatus.Cancelled;
            await UpdateStatusAsync(request.Item).ConfigureAwait(false);
        }

        public async Task TentativeEventAsync(TentativeEventRequest request)
        {
            request.Item.Status = CalendarItemStatus.Tentative;
            await UpdateStatusAsync(request.Item).ConfigureAwait(false);
        }

        private async Task UpdateStatusAsync(CalendarItem item)
        {
            EnsureCalendarItemDefaults(item, _account, "local");
            item.AssignedCalendar ??= await _calendarService.GetAccountCalendarAsync(item.CalendarId).ConfigureAwait(false);

            var attendees = await _calendarService.GetAttendeesAsync(item.Id).ConfigureAwait(false);
            await _calendarService.UpdateCalendarItemAsync(item, attendees).ConfigureAwait(false);
            await PersistIcsAsync(item, attendees).ConfigureAwait(false);
        }

        private Task PersistIcsAsync(CalendarItem item, List<CalendarEventAttendee> attendees)
        {
            var resourceHref = $"{_resourceScheme}://calendar/{item.CalendarId:N}/{item.Id:N}";
            var icsContent = BuildIcsContent(item, attendees);

            return _changeProcessor.SaveCalendarItemIcsAsync(
                _account.Id,
                item.CalendarId,
                item.Id,
                item.RemoteEventId,
                resourceHref,
                DateTimeOffset.UtcNow.ToString("O"),
                icsContent);
        }

        private async Task SaveAttachmentsAsync(CalendarEventComposeResult composeResult, Guid calendarItemId)
        {
            await _calendarService.DeleteAttachmentsAsync(calendarItemId).ConfigureAwait(false);

            var attachments = composeResult?.Attachments;
            if (attachments == null || attachments.Count == 0)
                return;

            var attachmentsRoot = Path.Combine(_applicationDataFolderPath, "CalendarAttachments", calendarItemId.ToString("N"));
            Directory.CreateDirectory(attachmentsRoot);

            var storedAttachments = new List<CalendarAttachment>();

            foreach (var attachment in attachments.Where(a => !string.IsNullOrWhiteSpace(a.FilePath) && File.Exists(a.FilePath)))
            {
                var fileName = string.IsNullOrWhiteSpace(attachment.FileName) ? Path.GetFileName(attachment.FilePath) : attachment.FileName;
                var destinationPath = Path.Combine(attachmentsRoot, fileName);
                File.Copy(attachment.FilePath, destinationPath, overwrite: true);

                storedAttachments.Add(new CalendarAttachment
                {
                    Id = Guid.NewGuid(),
                    CalendarItemId = calendarItemId,
                    RemoteAttachmentId = attachment.Id.ToString("N"),
                    FileName = fileName,
                    Size = attachment.Size,
                    ContentType = MimeTypes.GetMimeType(fileName),
                    IsDownloaded = true,
                    LocalFilePath = destinationPath,
                    LastModified = DateTimeOffset.UtcNow
                });
            }

            if (storedAttachments.Count > 0)
            {
                await _calendarService.InsertOrReplaceAttachmentsAsync(storedAttachments).ConfigureAwait(false);
            }
        }
    }

    private sealed class CalDavCalendarOperationHandler : IImapCalendarOperationHandler
    {
        private readonly ImapSynchronizer _owner;
        private readonly MailAccount _account;
        private readonly ICalendarService _calendarService;
        private readonly ICalDavClient _calDavClient;

        public bool RequiresConnectedClient => false;

        public CalDavCalendarOperationHandler(
            ImapSynchronizer owner,
            MailAccount account,
            ICalendarService calendarService,
            ICalDavClient calDavClient)
        {
            _owner = owner;
            _account = account;
            _calendarService = calendarService;
            _calDavClient = calDavClient;
        }

        public Task CreateCalendarEventAsync(CreateCalendarEventRequest request)
            => UpsertCalendarEventAsync(request.PreparedItem, request.PreparedEvent.Attendees);

        public Task UpdateCalendarEventAsync(UpdateCalendarEventRequest request)
            => UpsertCalendarEventAsync(request.Item, request.Attendees);

        public async Task DeleteCalendarEventAsync(DeleteCalendarEventRequest request)
        {
            var (connection, calendar) = await ResolveCalDavContextAsync(request.Item.CalendarId).ConfigureAwait(false);
            if (string.IsNullOrWhiteSpace(request.Item?.RemoteEventId))
            {
                throw new InvalidOperationException("Cannot delete CalDAV event because remote event ID is missing.");
            }

            await _calDavClient
                .DeleteCalendarEventAsync(connection, calendar, request.Item.RemoteEventId.GetProviderRemoteEventId())
                .ConfigureAwait(false);
        }

        public Task AcceptEventAsync(AcceptEventRequest request)
        {
            request.Item.Status = CalendarItemStatus.Accepted;
            return UpsertCalendarEventAsync(request.Item, null);
        }

        public Task DeclineEventAsync(DeclineEventRequest request)
        {
            request.Item.Status = CalendarItemStatus.Cancelled;
            return UpsertCalendarEventAsync(request.Item, null);
        }

        public Task TentativeEventAsync(TentativeEventRequest request)
        {
            request.Item.Status = CalendarItemStatus.Tentative;
            return UpsertCalendarEventAsync(request.Item, null);
        }

        private async Task UpsertCalendarEventAsync(CalendarItem item, List<CalendarEventAttendee> attendees)
        {
            EnsureCalendarItemDefaults(item, _account, "caldav");

            if (attendees == null)
            {
                attendees = await _calendarService.GetAttendeesAsync(item.Id).ConfigureAwait(false);
            }

            var (connection, calendar) = await ResolveCalDavContextAsync(item.CalendarId).ConfigureAwait(false);
            var icsContent = BuildIcsContent(item, attendees);

            await _calDavClient
                .UpsertCalendarEventAsync(connection, calendar, item.RemoteEventId.GetProviderRemoteEventId(), icsContent)
                .ConfigureAwait(false);
        }

        private async Task<(CalDavConnectionSettings Connection, CalDavCalendar Calendar)> ResolveCalDavContextAsync(Guid calendarId)
        {
            var assignedCalendar = await _calendarService.GetAccountCalendarAsync(calendarId).ConfigureAwait(false);
            if (assignedCalendar == null || string.IsNullOrWhiteSpace(assignedCalendar.RemoteCalendarId))
            {
                throw new InvalidOperationException("Cannot execute CalDAV operation because the target calendar has no remote ID.");
            }

            var serviceUri = await _owner.ResolveCalDavServiceUriAsync(CancellationToken.None).ConfigureAwait(false);
            if (serviceUri == null)
            {
                throw new InvalidOperationException("Cannot execute CalDAV operation because no CalDAV service URI is configured.");
            }

            var username = _owner.ResolveCalDavUsername();
            var password = _owner.ResolveCalDavPassword();

            if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
            {
                throw new InvalidOperationException("Cannot execute CalDAV operation because credentials are missing.");
            }

            var connection = new CalDavConnectionSettings
            {
                ServiceUri = serviceUri,
                Username = username,
                Password = password
            };

            var remoteCalendar = new CalDavCalendar
            {
                RemoteCalendarId = assignedCalendar.RemoteCalendarId,
                Name = assignedCalendar.Name
            };

            return (connection, remoteCalendar);
        }
    }

    private static void EnsureCalendarItemDefaults(CalendarItem item, MailAccount account, string idPrefix)
    {
        if (item == null)
            throw new ArgumentNullException(nameof(item));

        if (item.Id == Guid.Empty)
            item.Id = Guid.NewGuid();

        if (string.IsNullOrWhiteSpace(item.RemoteEventId))
            item.RemoteEventId = $"{idPrefix}-{item.Id:N}";

        if (item.CreatedAt == default)
            item.CreatedAt = DateTimeOffset.UtcNow;

        item.UpdatedAt = DateTimeOffset.UtcNow;
        item.OrganizerDisplayName ??= account?.SenderName ?? string.Empty;
        item.OrganizerEmail ??= account?.Address ?? string.Empty;
        item.StartTimeZone ??= TimeZoneInfo.Local.Id;
        item.EndTimeZone ??= item.StartTimeZone;
    }

    private static string BuildIcsContent(CalendarItem item, List<CalendarEventAttendee> attendees)
    {
        var uid = item.RemoteEventId?.Split(new[] { "::" }, StringSplitOptions.None)[0] ?? item.Id.ToString("N");
        var dtStamp = DateTimeOffset.UtcNow.ToString("yyyyMMdd'T'HHmmss'Z'");

        var lines = new List<string>
        {
            "BEGIN:VCALENDAR",
            "VERSION:2.0",
            "PRODID:-//Wino Mail//Calendar//EN",
            "CALSCALE:GREGORIAN",
            "BEGIN:VEVENT",
            $"UID:{EscapeIcs(uid)}",
            $"DTSTAMP:{dtStamp}",
        };

        if (item.IsAllDayEvent)
        {
            lines.Add($"DTSTART;VALUE=DATE:{item.StartDate:yyyyMMdd}");
            lines.Add($"DTEND;VALUE=DATE:{item.EndDate:yyyyMMdd}");
        }
        else
        {
            var startUtc = ConvertEventTimeToUtc(item.StartDate, item.StartTimeZone);
            var endUtc = ConvertEventTimeToUtc(item.EndDate, item.EndTimeZone ?? item.StartTimeZone);

            lines.Add($"DTSTART:{startUtc:yyyyMMdd'T'HHmmss'Z'}");
            lines.Add($"DTEND:{endUtc:yyyyMMdd'T'HHmmss'Z'}");
        }

        if (!string.IsNullOrWhiteSpace(item.Title))
            lines.Add($"SUMMARY:{EscapeIcs(item.Title)}");

        if (!string.IsNullOrWhiteSpace(item.Description))
            lines.Add($"DESCRIPTION:{EscapeIcs(item.Description)}");

        if (!string.IsNullOrWhiteSpace(item.Location))
            lines.Add($"LOCATION:{EscapeIcs(item.Location)}");

        lines.Add($"STATUS:{MapStatus(item.Status)}");
        lines.Add($"TRANSP:{(item.ShowAs == CalendarItemShowAs.Free ? "TRANSPARENT" : "OPAQUE")}");
        lines.Add($"CLASS:{MapVisibility(item.Visibility)}");

        if (!string.IsNullOrWhiteSpace(item.Recurrence))
        {
            var recurrenceLines = item.Recurrence
                .Split(Wino.Core.Domain.Constants.CalendarEventRecurrenceRuleSeperator, StringSplitOptions.RemoveEmptyEntries)
                .Select(l => l.Trim())
                .Where(l => !string.IsNullOrWhiteSpace(l));

            lines.AddRange(recurrenceLines);
        }

        if (!string.IsNullOrWhiteSpace(item.OrganizerEmail))
        {
            var organizerName = string.IsNullOrWhiteSpace(item.OrganizerDisplayName)
                ? item.OrganizerEmail
                : item.OrganizerDisplayName;
            lines.Add($"ORGANIZER;CN={EscapeIcs(organizerName)}:mailto:{EscapeIcs(item.OrganizerEmail)}");
        }

        if (attendees != null)
        {
            foreach (var attendee in attendees.Where(a => !string.IsNullOrWhiteSpace(a.Email)))
            {
                var role = attendee.IsOptionalAttendee ? "OPT-PARTICIPANT" : "REQ-PARTICIPANT";
                var partStat = attendee.AttendenceStatus switch
                {
                    AttendeeStatus.Accepted => "ACCEPTED",
                    AttendeeStatus.Declined => "DECLINED",
                    AttendeeStatus.Tentative => "TENTATIVE",
                    _ => "NEEDS-ACTION"
                };

                var cn = string.IsNullOrWhiteSpace(attendee.Name) ? attendee.Email : attendee.Name;
                lines.Add($"ATTENDEE;CN={EscapeIcs(cn)};ROLE={role};PARTSTAT={partStat}:mailto:{EscapeIcs(attendee.Email)}");
            }
        }

        lines.Add("END:VEVENT");
        lines.Add("END:VCALENDAR");

        return string.Join(Environment.NewLine, lines);
    }

    private static DateTime ConvertEventTimeToUtc(DateTime eventDateTime, string eventTimeZoneId)
    {
        if (string.IsNullOrWhiteSpace(eventTimeZoneId))
            return eventDateTime.ToUniversalTime();

        try
        {
            var eventTimeZone = TimeZoneInfo.FindSystemTimeZoneById(eventTimeZoneId);
            var unspecifiedDateTime = DateTime.SpecifyKind(eventDateTime, DateTimeKind.Unspecified);
            return TimeZoneInfo.ConvertTimeToUtc(unspecifiedDateTime, eventTimeZone);
        }
        catch
        {
            return eventDateTime.ToUniversalTime();
        }
    }

    private static string EscapeIcs(string value)
    {
        if (string.IsNullOrEmpty(value))
            return string.Empty;

        return value
            .Replace("\\", "\\\\", StringComparison.Ordinal)
            .Replace(";", "\\;", StringComparison.Ordinal)
            .Replace(",", "\\,", StringComparison.Ordinal)
            .Replace("\r\n", "\\n", StringComparison.Ordinal)
            .Replace("\n", "\\n", StringComparison.Ordinal);
    }

    private static string MapStatus(CalendarItemStatus status)
    {
        return status switch
        {
            CalendarItemStatus.Cancelled => "CANCELLED",
            CalendarItemStatus.Tentative => "TENTATIVE",
            _ => "CONFIRMED"
        };
    }

    private static string MapVisibility(CalendarItemVisibility visibility)
    {
        return visibility switch
        {
            CalendarItemVisibility.Public => "PUBLIC",
            CalendarItemVisibility.Private => "PRIVATE",
            CalendarItemVisibility.Confidential => "CONFIDENTIAL",
            _ => "PUBLIC"
        };
    }

    public Task StartIdleClientAsync()
    {
        if (IsDisposing)
            return Task.CompletedTask;

        if (_idleLoopTask != null && !_idleLoopTask.IsCompleted)
            return Task.CompletedTask;

        _idleLoopCancellationTokenSource = new CancellationTokenSource();
        _idleLoopTask = RunIdleLoopAsync(_idleLoopCancellationTokenSource.Token);

        return Task.CompletedTask;
    }

    private async Task RunIdleLoopAsync(CancellationToken cancellationToken)
    {
        int reconnectAttempt = 0;

        while (!cancellationToken.IsCancellationRequested && !IsDisposing)
        {
            IImapClient idleClient = null;
            IMailFolder inboxFolder = null;
            bool shouldReconnect = false;

            try
            {
                idleClient = await _clientPool.GetIdleClientAsync(cancellationToken).ConfigureAwait(false);

                if (idleClient == null)
                {
                    _logger.Warning("Dedicated IDLE client could not be allocated for {AccountName}.", Account.Name);
                    return;
                }

                if (!idleClient.Capabilities.HasFlag(ImapCapabilities.Idle))
                {
                    _logger.Information("{AccountName} does not support IMAP IDLE. Automatic updates rely on global sync interval.", Account.Name);
                    return;
                }

                if (idleClient.Inbox == null)
                {
                    _logger.Warning("{AccountName} does not expose Inbox for IDLE listening.", Account.Name);
                    return;
                }

                inboxFolder = idleClient.Inbox;

                await inboxFolder.OpenAsync(FolderAccess.ReadOnly, cancellationToken).ConfigureAwait(false);

                _lastIdleInboxCount = inboxFolder.Count;
                inboxFolder.CountChanged += IdleInboxCountChanged;

                reconnectAttempt = 0;
                _logger.Debug("Started dedicated IDLE loop for {AccountName}.", Account.Name);

                while (!cancellationToken.IsCancellationRequested && !IsDisposing && idleClient.IsConnected)
                {
                    using var idleDoneTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(9));
                    await idleClient.IdleAsync(idleDoneTokenSource.Token, cancellationToken).ConfigureAwait(false);
                }
            }
            catch (ImapProtocolException protocolException)
            {
                _logger.Information(protocolException, "Idle client received protocol exception for {AccountName}.", Account.Name);
                shouldReconnect = true;
            }
            catch (IOException ioException)
            {
                _logger.Information(ioException, "Idle client received IO exception for {AccountName}.", Account.Name);
                shouldReconnect = true;
            }
            catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested || IsDisposing)
            {
                break;
            }
            catch (OperationCanceledException)
            {
                shouldReconnect = true;
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "Idle client loop failed for {AccountName}.", Account.Name);
                shouldReconnect = true;
            }
            finally
            {
                if (inboxFolder != null)
                {
                    inboxFolder.CountChanged -= IdleInboxCountChanged;

                    if (inboxFolder.IsOpen && !cancellationToken.IsCancellationRequested)
                    {
                        await inboxFolder.CloseAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
                    }
                }

                _clientPool.ReleaseIdleClient(isFaulted: shouldReconnect);
            }

            if (!shouldReconnect)
            {
                break;
            }

            reconnectAttempt++;
            var reconnectDelay = GetIdleReconnectDelay(reconnectAttempt);
            _logger.Information("Reconnecting IDLE client for {AccountName} in {Delay}.", Account.Name, reconnectDelay);

            try
            {
                await Task.Delay(reconnectDelay, cancellationToken).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                break;
            }
        }
    }

    private static TimeSpan GetIdleReconnectDelay(int attempt)
    {
        var backoffSeconds = Math.Min(60, Math.Pow(2, Math.Min(attempt, 6)));
        int jitterMs;

        lock (IdleReconnectJitter)
        {
            jitterMs = IdleReconnectJitter.Next(250, 1250);
        }

        return TimeSpan.FromSeconds(backoffSeconds) + TimeSpan.FromMilliseconds(jitterMs);
    }

    private void RequestIdleChangeSynchronization()
    {
        if (!ShouldTriggerIdleSynchronization(DateTime.UtcNow))
            return;

        var options = new MailSynchronizationOptions()
        {
            AccountId = Account.Id,
            Type = MailSynchronizationType.IMAPIdle
        };

        WeakReferenceMessenger.Default.Send(new NewMailSynchronizationRequested(options));
    }

    internal bool ShouldTriggerIdleSynchronization(DateTime nowUtc)
    {
        lock (_idleDebounceLock)
        {
            if (nowUtc - _lastIdleSyncRequestUtc < _idleSyncDebounceWindow)
            {
                return false;
            }

            _lastIdleSyncRequestUtc = nowUtc;
            return true;
        }
    }

    private void IdleInboxCountChanged(object sender, EventArgs e)
    {
        if (sender is not IMailFolder inboxFolder)
            return;

        var currentCount = inboxFolder.Count;
        var previousCount = _lastIdleInboxCount;
        _lastIdleInboxCount = currentCount;

        if (currentCount > previousCount)
        {
            RequestIdleChangeSynchronization();
        }
    }

    public async Task StopIdleClientAsync()
    {
        if (_idleLoopCancellationTokenSource != null)
        {
            _idleLoopCancellationTokenSource.Cancel();
        }

        if (_idleLoopTask != null)
        {
            try
            {
                await _idleLoopTask.ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                // no-op
            }
        }

        _idleLoopCancellationTokenSource?.Dispose();
        _idleLoopCancellationTokenSource = null;
        _idleLoopTask = null;
    }

    public override async Task KillSynchronizerAsync()
    {
        await base.KillSynchronizerAsync();
        await StopIdleClientAsync();

        // Make sure the client pool safely disconnects all ImapClients.
        _clientPool.Dispose();
    }

    public Task PreWarmClientPoolAsync() => _clientPool.PreWarmPoolAsync();
}


