using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Wino.Mail.ViewModels.Data;

namespace Wino.Mail.ViewModels.Collections;

/// <summary>
/// Grouping options for emails
/// </summary>
public enum EmailGroupingType
{
    ByFromName,
    ByDate
}

/// <summary>
/// Sorting options for emails within groups
/// </summary>
public enum EmailSortDirection
{
    Ascending,
    Descending
}

/// <summary>
/// Collection that automatically groups MailItemViewModels with ThreadMailItemViewModels in a flat structure for ItemsView.
/// All emails are in the same flat list with proper selection support. Thread emails are placed consecutively after their expander.
/// </summary>
public partial class GroupedEmailCollection : ObservableObject, IRecipient<PropertyChangedMessage<bool>>, IDisposable
{
    private readonly ObservableCollection<MailItemViewModel> _sourceItems = [];
    private readonly Dictionary<string, GroupHeaderBase> _groupHeaders = [];
    private readonly Dictionary<string, int> _groupHeaderIndexCache = [];
    private readonly Dictionary<string, List<object>> _groupItems = [];
    private readonly Dictionary<string, ThreadMailItemViewModel> _threadExpanders = [];
    private readonly HashSet<Guid> _mailCopyIdHashSet = [];
    private bool _disposed;
    private bool _isUpdating;

    [ObservableProperty]
    private EmailGroupingType groupingType = EmailGroupingType.ByDate;

    [ObservableProperty]
    private EmailSortDirection sortDirection = EmailSortDirection.Descending;

    public GroupedEmailCollection()
    {
        // Create a flat collection for ItemsView with headers, expanders and emails mixed
        Items = [];

        // Subscribe to source collection changes to update grouping
        _sourceItems.CollectionChanged += OnSourceItemsChanged;

        // Register for PropertyChanged messages
        WeakReferenceMessenger.Default.Register<PropertyChangedMessage<bool>>(this);

        RefreshGrouping();
    }

    /// <summary>
    /// Flat observable collection containing group headers, thread expanders, and email items for ItemsView binding.
    /// Structure: GroupHeader -> [ThreadExpander -> ThreadEmail1, ThreadEmail2, ...] -> StandaloneEmail1 -> StandaloneEmail2
    /// </summary>
    public ObservableCollection<object> Items { get; }

    /// <summary>
    /// Total number of emails across all groups
    /// </summary>
    public int TotalCount => _sourceItems.Count;

    /// <summary>
    /// Total number of unread emails across all groups
    /// </summary>
    public int TotalUnreadCount => _sourceItems.Count(e => e.MailCopy?.IsRead == false);

    /// <summary>
    /// HashSet containing unique IDs of all mail copies in the collection for pagination tracking
    /// </summary>
    public HashSet<Guid> MailCopyIdHashSet => _mailCopyIdHashSet;

    /// <summary>
    /// Gets all email items across all groups as a flat collection
    /// </summary>
    public IEnumerable<MailItemViewModel> AllItems => _sourceItems;

    /// <summary>
    /// Gets all currently selected email items.
    /// Includes:
    /// - Standalone mail items where IsSelected=true
    /// - Mail items inside threads where the mail item's IsSelected=true (regardless of thread expansion)
    /// - All mail items inside a thread where the thread's IsSelected=true
    /// </summary>
    public IEnumerable<MailItemViewModel> SelectedItems
    {
        get
        {
            var selectedItems = new List<MailItemViewModel>();

            // Add selected standalone emails (not in threads)
            selectedItems.AddRange(_sourceItems.Where(e => e.IsSelected && !e.IsDisplayedInThread));

            // Process thread expanders
            foreach (var expander in _threadExpanders.Values)
            {
                if (expander.IsSelected)
                {
                    // If thread is selected, add all emails in the thread
                    selectedItems.AddRange(expander.ThreadEmails);
                }
                else
                {
                    // If thread is not selected, add only individually selected emails within the thread
                    selectedItems.AddRange(expander.ThreadEmails.Where(e => e.IsSelected));
                }
            }

            return selectedItems;
        }
    }

    /// <summary>
    /// Gets the count of all currently selected email items.
    /// Counts:
    /// - Standalone mail items where IsSelected=true
    /// - Mail items inside threads where the mail item's IsSelected=true (regardless of thread expansion)
    /// - All mail items inside a thread where the thread's IsSelected=true
    /// </summary>
    public int SelectedItemsCount => SelectedItems.Count();

    /// <summary>
    /// Gets the number of visible email items (excluding group headers).
    /// For threads, counts the expander as 1 if collapsed, or all thread emails if expanded.
    /// </summary>
    public int Count
    {
        get
        {
            int count = 0;

            foreach (var item in Items)
            {
                switch (item)
                {
                    case GroupHeaderBase:
                        // Skip group headers
                        break;
                    case ThreadMailItemViewModel thread:
                        count += thread.ThreadEmails.Count;
                        break;
                    case MailItemViewModel:
                        count += 1;
                        break;
                }
            }
            return count;
        }
    }

    /// <summary>
    /// Handles PropertyChanged messages for thread expansion
    /// </summary>
    public void Receive(PropertyChangedMessage<bool> message)
    {
        // Only handle IsThreadExpanded property changes from ThreadMailItemViewModel
        if (_isUpdating)
            return;

        if (message.PropertyName == nameof(ThreadMailItemViewModel.IsThreadExpanded) && message.Sender is ThreadMailItemViewModel expander)
        {
            HandleThreadExpansion(expander);
        }
    }

    private void HandleThreadExpansion(ThreadMailItemViewModel expander)
    {
        _isUpdating = true;
        try
        {
            var expanderIndex = Items.IndexOf(expander);
            if (expanderIndex == -1)
                return;

            if (expander.IsThreadExpanded)
            {
                // Add thread emails after the expander
                var insertIndex = expanderIndex + 1;
                var sortedThreadEmails = SortDirection == EmailSortDirection.Descending
                    ? expander.ThreadEmails.OrderByDescending(e => e.MailCopy.CreationDate).ToList()
                    : expander.ThreadEmails.OrderBy(e => e.MailCopy?.CreationDate).ToList();

                foreach (var email in sortedThreadEmails)
                {
                    Items.Insert(insertIndex, email);
                    insertIndex++;
                }

                UpdateHeaderIndicesAfterInsertion(expanderIndex + 1, expander.EmailCount);
            }
            else
            {
                // Remove thread emails from UI
                foreach (var email in expander.ThreadEmails.ToList())
                {
                    var emailIndex = Items.IndexOf(email);
                    if (emailIndex >= 0)
                    {
                        var sourceItem = _sourceItems.FirstOrDefault(a => a == email);

                        Items.RemoveAt(emailIndex);
                        UpdateHeaderIndicesAfterRemoval(emailIndex);
                    }
                }
            }
        }
        finally
        {
            _isUpdating = false;
        }
    }

    /// <summary>
    /// Adds an email to the collection which will automatically group it.
    /// If an email with the same ThreadId exists, adds it to the existing thread or creates a new thread.
    /// </summary>
    public void AddEmail(MailItemViewModel email)
    {
        if (email?.MailCopy == null)
            return;

        // Add to unique ID tracking
        _mailCopyIdHashSet.Add(email.MailCopy.UniqueId);

        _isUpdating = true;
        try
        {
            // Check if this email belongs to a thread
            if (!string.IsNullOrEmpty(email.MailCopy.ThreadId))
            {
                // Look for existing emails with the same ThreadId
                var existingThreadEmails = _sourceItems
                    .Where(e => e.MailCopy?.ThreadId == email.MailCopy.ThreadId)
                    .ToList();

                var existingExpander = _threadExpanders.GetValueOrDefault(email.MailCopy.ThreadId);

                if (existingThreadEmails.Any() || existingExpander != null)
                {
                    // Add to existing thread
                    if (existingExpander == null)
                    {
                        // Create thread expander for the first time (existing emails become part of thread)
                        existingExpander = new ThreadMailItemViewModel(email.MailCopy.ThreadId);
                        _threadExpanders[email.MailCopy.ThreadId] = existingExpander;

                        // Remove existing standalone emails from UI and add them to the thread
                        foreach (var existingEmail in existingThreadEmails)
                        {
                            RemoveEmailFromUI(existingEmail);
                            existingExpander.AddEmail(existingEmail);
                            existingEmail.IsDisplayedInThread = true;
                        }
                    }

                    // Add the new email to the thread
                    existingExpander.AddEmail(email);
                    email.IsDisplayedInThread = true;

                    // Add to source collection
                    var insertIndex = FindInsertionIndex(email);
                    _sourceItems.Insert(insertIndex, email);

                    // Add thread expander and all emails to UI in correct positions
                    RefreshThreadInUI(existingExpander);
                }
                else
                {
                    // First email with this ThreadId - treat as standalone for now
                    email.IsDisplayedInThread = false;
                    var insertIndex = FindInsertionIndex(email);
                    _sourceItems.Insert(insertIndex, email);
                    AddEmailToUI(email);
                }
            }
            else
            {
                // No ThreadId - standalone email
                email.IsDisplayedInThread = false;
                var insertIndex = FindInsertionIndex(email);
                _sourceItems.Insert(insertIndex, email);
                AddEmailToUI(email);
            }

            OnPropertyChanged(nameof(TotalCount));
            OnPropertyChanged(nameof(TotalUnreadCount));
        }
        finally
        {
            _isUpdating = false;
        }
    }

    /// <summary>
    /// Removes an email from the collection.
    /// If the email is part of a thread and removing it would leave only 1 item in the thread,
    /// the thread is converted back to a single email.
    /// </summary>
    public void RemoveEmail(MailItemViewModel email)
    {
        if (email?.MailCopy == null)
            return;

        // Remove from unique ID tracking
        _mailCopyIdHashSet.Remove(email.MailCopy.UniqueId);

        _isUpdating = true;
        try
        {
            var threadId = email.MailCopy.ThreadId;

            // Remove from source collection
            if (!_sourceItems.Remove(email))
                return; // Email not found

            if (!string.IsNullOrEmpty(threadId) && _threadExpanders.TryGetValue(threadId, out var expander))
            {
                // Remove from thread
                expander.RemoveEmail(email);
                email.IsDisplayedInThread = false;

                // Remove email from UI
                RemoveEmailFromUI(email);

                // Check if thread now has only 1 email - convert back to standalone
                if (expander.EmailCount == 1)
                {
                    var remainingEmail = expander.ThreadEmails.First();

                    // Remove thread expander and remaining email from UI
                    RemoveThreadFromUI(expander);

                    // Set remaining email as no longer displayed in thread
                    remainingEmail.IsDisplayedInThread = false;

                    // Remove thread expander tracking
                    _threadExpanders.Remove(threadId);
                    expander.Dispose();

                    // Add remaining email as standalone
                    AddEmailToUI(remainingEmail);
                }
                else if (expander.EmailCount == 0)
                {
                    // Thread is empty - remove completely
                    RemoveThreadFromUI(expander);
                    _threadExpanders.Remove(threadId);
                    expander.Dispose();
                }
                else
                {
                    // Thread still has multiple emails - refresh its position
                    RefreshThreadInUI(expander);
                }
            }
            else
            {
                // Standalone email
                email.IsDisplayedInThread = false;
                RemoveEmailFromUI(email);
            }

            // Update group headers
            UpdateGroupAfterChanges();

            OnPropertyChanged(nameof(TotalCount));
            OnPropertyChanged(nameof(TotalUnreadCount));
        }
        finally
        {
            _isUpdating = false;
        }
    }

    /// <summary>
    /// Adds multiple emails to the collection efficiently using bulk operations
    /// </summary>
    public void AddEmails(IEnumerable<MailItemViewModel> emails)
    {
        var emailList = emails.Where(e => e?.MailCopy != null).ToList();
        if (!emailList.Any())
            return;

        _isUpdating = true;
        try
        {
            // Add to unique ID tracking
            foreach (var email in emailList)
            {
                _mailCopyIdHashSet.Add(email.MailCopy.UniqueId);
            }

            // For bulk loading, add to source and refresh
            foreach (var email in emailList)
            {
                var insertIndex = FindInsertionIndex(email);
                _sourceItems.Insert(insertIndex, email);
            }

            RefreshGrouping();

            OnPropertyChanged(nameof(TotalCount));
            OnPropertyChanged(nameof(TotalUnreadCount));
        }
        finally
        {
            _isUpdating = false;
        }
    }

    /// <summary>
    /// Clears all emails, threads, and headers
    /// </summary>
    public void Clear()
    {
        _isUpdating = true;
        try
        {
            // Reset IsDisplayedInThread for all emails before clearing
            foreach (var email in _sourceItems)
            {
                email.IsDisplayedInThread = false;
            }

            // Dispose all thread expanders
            foreach (var expander in _threadExpanders.Values)
            {
                expander.Dispose();
            }

            _sourceItems.Clear();
            Items.Clear();
            _groupHeaders.Clear();
            _groupHeaderIndexCache.Clear();
            _groupItems.Clear();
            _threadExpanders.Clear();
            _mailCopyIdHashSet.Clear();

            OnPropertyChanged(nameof(TotalCount));
            OnPropertyChanged(nameof(TotalUnreadCount));
        }
        finally
        {
            _isUpdating = false;
        }
    }

    /// <summary>
    /// Changes the grouping type and rebuilds the collection
    /// </summary>
    public void ChangeGrouping(EmailGroupingType newGroupingType, EmailSortDirection newSortDirection = EmailSortDirection.Descending)
    {
        if (GroupingType == newGroupingType && SortDirection == newSortDirection)
            return;

        GroupingType = newGroupingType;
        SortDirection = newSortDirection;
        RefreshGrouping();
    }

    /// <summary>
    /// Manually refreshes the grouping (useful after bulk operations)
    /// </summary>
    public void RefreshGrouping()
    {
        _isUpdating = true;
        try
        {
            // Clear UI items but preserve source and expanders
            Items.Clear();
            _groupHeaders.Clear();
            _groupHeaderIndexCache.Clear();
            _groupItems.Clear();

            if (!_sourceItems.Any())
                return;

            // Rebuild thread expanders based on current emails
            RebuildThreadExpanders();

            // Group all items (standalone emails and thread expanders) by criteria
            var allItems = new List<object>();

            // Add standalone emails (emails without threads or not in any expander)
            var standaloneEmails = _sourceItems
                .Where(e => string.IsNullOrEmpty(e.MailCopy?.ThreadId) ||
                           !_threadExpanders.ContainsKey(e.MailCopy.ThreadId))
                .ToList();

            allItems.AddRange(standaloneEmails.Cast<object>());
            allItems.AddRange(_threadExpanders.Values.Cast<object>());

            // Group by criteria
            var groupedItems = allItems
                .GroupBy(item => GetGroupKeyForItem(item))
                .OrderBy(g => g.Key, GetGroupComparer());

            var currentIndex = 0;

            // Process each group
            foreach (var group in groupedItems)
            {
                // Create group header
                var groupHeader = CreateGroupHeader(group.Key);
                _groupHeaders[group.Key] = groupHeader;
                _groupHeaderIndexCache[group.Key] = currentIndex;

                // Sort items within the group
                var sortedGroupItems = SortDirection == EmailSortDirection.Descending
                    ? group.OrderByDescending(GetEffectiveDate).ToList()
                    : group.OrderBy(GetEffectiveDate).ToList();

                _groupItems[group.Key] = sortedGroupItems;

                // Add header to flat collection
                Items.Add(groupHeader);
                currentIndex++;

                // Add all items in this group to flat collection
                foreach (var item in sortedGroupItems)
                {
                    if (item is ThreadMailItemViewModel expander)
                    {
                        // Add expander
                        Items.Add(expander);
                        currentIndex++;

                        // Only add thread emails if the thread is expanded
                        if (expander.IsThreadExpanded)
                        {
                            var sortedThreadEmails = SortDirection == EmailSortDirection.Descending
                                ? expander.ThreadEmails.OrderByDescending(e => e.MailCopy?.CreationDate).ToList()
                                : expander.ThreadEmails.OrderBy(e => e.MailCopy?.CreationDate).ToList();

                            foreach (var threadEmail in sortedThreadEmails)
                            {
                                Items.Add(threadEmail);
                                currentIndex++;
                            }
                        }
                    }
                    else if (item is MailItemViewModel email)
                    {
                        // Add standalone email
                        Items.Add(email);
                        currentIndex++;
                    }
                }
            }

            // Update group header counts
            UpdateAllGroupHeaderCounts();
        }
        finally
        {
            _isUpdating = false;
        }
    }

    /// <summary>
    /// Rebuilds thread expanders based on current source emails
    /// </summary>
    private void RebuildThreadExpanders()
    {
        // Group emails by ThreadId
        var threadGroups = _sourceItems
            .Where(e => !string.IsNullOrEmpty(e.MailCopy?.ThreadId))
            .GroupBy(e => e.MailCopy!.ThreadId!)
            .Where(g => g.Count() >= 2) // Only create threads with 2+ emails
            .ToList();

        // Remove expanders for threads that no longer have 2+ emails
        var expandersToRemove = _threadExpanders.Keys
            .Where(threadId => !threadGroups.Any(g => g.Key == threadId))
            .ToList();

        foreach (var threadId in expandersToRemove)
        {
            // Set emails back to not displayed in thread before removing expander
            foreach (var email in _threadExpanders[threadId].ThreadEmails)
            {
                email.IsDisplayedInThread = false;
            }

            _threadExpanders[threadId].Dispose();
            _threadExpanders.Remove(threadId);
        }

        // Create or update expanders for threads with 2+ emails
        foreach (var threadGroup in threadGroups)
        {
            if (!_threadExpanders.TryGetValue(threadGroup.Key, out var threadExpander))
            {
                threadExpander = new ThreadMailItemViewModel(threadGroup.Key);
                _threadExpanders[threadGroup.Key] = threadExpander;
            }

            // Clear and re-add emails to ensure consistency
            var currentEmails = threadExpander.ThreadEmails.ToList();
            foreach (var email in currentEmails)
            {
                threadExpander.RemoveEmail(email);
                email.IsDisplayedInThread = false;
            }

            foreach (var email in threadGroup)
            {
                threadExpander.AddEmail(email);
                email.IsDisplayedInThread = true;
            }
        }

        // Set standalone emails to not displayed in thread
        var standaloneEmails = _sourceItems
            .Where(e => string.IsNullOrEmpty(e.MailCopy?.ThreadId) ||
                       !_threadExpanders.ContainsKey(e.MailCopy.ThreadId))
            .ToList();

        foreach (var email in standaloneEmails)
        {
            email.IsDisplayedInThread = false;
        }
    }

    public int IndexOf(object item) => Items.IndexOf(item);

    private void RefreshThreadInUI(ThreadMailItemViewModel expander)
    {
        // Remove thread completely from UI
        RemoveThreadFromUI(expander);

        // Find correct position for thread expander based on latest email
        var groupKey = GetGroupKeyForItem(expander);
        AddThreadToUI(expander, groupKey);
    }

    public MailItemContainer GetMailItemContainer(Guid uniqueId)
    {
        // First, search in standalone mail items (not displayed in threads)
        var standaloneMailItem = _sourceItems.FirstOrDefault(item =>
            item.MailCopy.UniqueId == uniqueId && !item.IsDisplayedInThread);

        if (standaloneMailItem != null)
        {
            // Check if the standalone item is visible in the UI
            var isItemVisible = Items.Contains(standaloneMailItem);

            return new MailItemContainer(standaloneMailItem)
            {
                IsItemVisible = isItemVisible,
                IsThreadVisible = false // Not a threaded item
            };
        }

        // Search in thread expanders for threaded mail items
        foreach (var threadExpander in _threadExpanders.Values)
        {
            if (threadExpander.HasUniqueId(uniqueId))
            {
                // Find the specific mail item within the thread
                var threadMailItem = threadExpander.ThreadEmails.FirstOrDefault(email =>
                    email.MailCopy.UniqueId == uniqueId);

                if (threadMailItem != null)
                {
                    // Check visibility: thread expander must be visible, and for individual item visibility,
                    // the thread must be expanded and the item must be in the visible Items collection
                    var isThreadVisible = Items.Contains(threadExpander);
                    var isItemVisible = isThreadVisible && threadExpander.IsThreadExpanded && Items.Contains(threadMailItem);

                    return new MailItemContainer(threadMailItem, threadExpander)
                    {
                        IsItemVisible = isItemVisible,
                        IsThreadVisible = isThreadVisible
                    };
                }
            }
        }

        // Item not found
        return null;
    }

    private void AddThreadToUI(ThreadMailItemViewModel expander, string groupKey)
    {
        var groupHeader = GetOrCreateGroupHeader(groupKey);
        var headerIndex = _groupHeaderIndexCache.GetValueOrDefault(groupKey, -1);

        if (headerIndex == -1)
        {
            // New group - add header, expander, and thread emails (if expanded)
            var insertPosition = FindGroupInsertionPosition(groupKey);

            Items.Insert(insertPosition, groupHeader);
            Items.Insert(insertPosition + 1, expander);

            var currentIndex = insertPosition + 2;
            var totalInserted = 2 // header + expander
                ;

            // Only add thread emails if the thread is expanded
            if (expander.IsThreadExpanded)
            {
                var sortedThreadEmails = SortDirection == EmailSortDirection.Descending
                    ? expander.ThreadEmails.OrderByDescending(e => e.MailCopy?.CreationDate).ToList()
                    : expander.ThreadEmails.OrderBy(e => e.MailCopy?.CreationDate).ToList();

                foreach (var email in sortedThreadEmails)
                {
                    Items.Insert(currentIndex, email);
                    currentIndex++;
                    totalInserted++;
                }
            }

            UpdateHeaderIndicesAfterInsertion(insertPosition, totalInserted);
            _groupHeaderIndexCache[groupKey] = insertPosition;
        }
        else
        {
            // Existing group - find correct position within group
            var groupEndIndex = FindGroupEndIndex(headerIndex);
            var insertIndex = FindItemInsertionIndexInGroup(expander, headerIndex, groupEndIndex);

            // Insert expander
            Items.Insert(insertIndex, expander);
            var currentIndex = insertIndex + 1;
            var totalInserted = 1 // expander
                ;

            // Only insert thread emails if expanded
            if (expander.IsThreadExpanded)
            {
                var sortedThreadEmails = SortDirection == EmailSortDirection.Descending
                    ? expander.ThreadEmails.OrderByDescending(e => e.MailCopy?.CreationDate).ToList()
                    : expander.ThreadEmails.OrderBy(e => e.MailCopy?.CreationDate).ToList();

                foreach (var email in sortedThreadEmails)
                {
                    Items.Insert(currentIndex, email);
                    currentIndex++;
                    totalInserted++;
                }
            }

            UpdateHeaderIndicesAfterInsertion(insertIndex, totalInserted);
        }

        UpdateGroupHeaderCounts(groupKey, groupHeader);
    }

    private void RemoveThreadFromUI(ThreadMailItemViewModel expander)
    {
        // Remove expander
        var expanderIndex = Items.IndexOf(expander);
        if (expanderIndex >= 0)
        {
            Items.RemoveAt(expanderIndex);
            UpdateHeaderIndicesAfterRemoval(expanderIndex);
        }

        // Remove all thread emails (whether expanded or not)
        foreach (var email in expander.ThreadEmails.ToList())
        {
            var emailIndex = Items.IndexOf(email);
            if (emailIndex >= 0)
            {
                Items.RemoveAt(emailIndex);
                UpdateHeaderIndicesAfterRemoval(emailIndex);
            }
        }
    }

    private void RemoveEmailFromUI(MailItemViewModel email)
    {
        var itemIndex = Items.IndexOf(email);
        if (itemIndex >= 0)
        {
            Items.RemoveAt(itemIndex);
            UpdateHeaderIndicesAfterRemoval(itemIndex);
        }
    }

    private void AddEmailToUI(MailItemViewModel email)
    {
        var groupKey = GetGroupKey(email);
        var groupHeader = GetOrCreateGroupHeader(groupKey);
        var headerIndex = _groupHeaderIndexCache.GetValueOrDefault(groupKey, -1);

        if (headerIndex == -1)
        {
            // New group
            var insertPosition = FindGroupInsertionPosition(groupKey);
            Items.Insert(insertPosition, groupHeader);
            Items.Insert(insertPosition + 1, email);

            UpdateHeaderIndicesAfterInsertion(insertPosition, 2);
            _groupHeaderIndexCache[groupKey] = insertPosition;
        }
        else
        {
            // Existing group
            var groupEndIndex = FindGroupEndIndex(headerIndex);
            var insertIndex = FindItemInsertionIndexInGroup(email, headerIndex, groupEndIndex);
            Items.Insert(insertIndex, email);

            UpdateHeaderIndicesAfterInsertion(insertIndex);
        }

        UpdateGroupHeaderCounts(groupKey, groupHeader);
    }

    #region Helper Methods

    private string GetGroupKeyForItem(object item)
    {
        return item switch
        {
            MailItemViewModel email => GetGroupKey(email),
            ThreadMailItemViewModel expander => GetGroupKey(expander.ThreadEmails.OrderByDescending(e => e.MailCopy?.CreationDate).First()),
            _ => "Default"
        };
    }

    private string GetGroupKey(MailItemViewModel email)
    {
        return GroupingType switch
        {
            EmailGroupingType.ByFromName => email.FromName ?? "Unknown Sender",
            EmailGroupingType.ByDate => email.MailCopy?.CreationDate.ToString("yyyy-MM-dd") ?? DateTime.Today.ToString("yyyy-MM-dd"),
            _ => "Default"
        };
    }

    private DateTime GetEffectiveDate(object item)
    {
        return item switch
        {
            MailItemViewModel email => email.MailCopy?.CreationDate ?? DateTime.MinValue,
            ThreadMailItemViewModel expander => expander.LatestEmailDate,
            _ => DateTime.MinValue
        };
    }

    private int FindInsertionIndex(MailItemViewModel email)
    {
        var createdAt = email.MailCopy!.CreationDate;
        int left = 0, right = _sourceItems.Count;

        while (left < right)
        {
            int mid = (left + right) / 2;
            var comparison = createdAt.CompareTo(_sourceItems[mid].MailCopy?.CreationDate ?? DateTime.MinValue);

            if (SortDirection == EmailSortDirection.Descending)
                comparison = -comparison;

            if (comparison < 0)
                right = mid;
            else
                left = mid + 1;
        }

        return left;
    }

    private GroupHeaderBase GetOrCreateGroupHeader(string groupKey)
    {
        if (!_groupHeaders.TryGetValue(groupKey, out var groupHeader))
        {
            groupHeader = CreateGroupHeader(groupKey);
            _groupHeaders[groupKey] = groupHeader;
            _groupItems[groupKey] = [];
        }
        return groupHeader;
    }

    private GroupHeaderBase CreateGroupHeader(string groupKey)
    {
        return GroupingType switch
        {
            EmailGroupingType.ByFromName => new SenderGroupHeader(groupKey),
            EmailGroupingType.ByDate when DateTime.TryParse(groupKey, out var date) => new DateGroupHeader(date),
            EmailGroupingType.ByDate => new DateGroupHeader(DateTime.Today),
            _ => new SenderGroupHeader(groupKey)
        };
    }

    private IComparer<string> GetGroupComparer()
    {
        return GroupingType switch
        {
            EmailGroupingType.ByFromName => SortDirection == EmailSortDirection.Descending
                ? StringComparer.OrdinalIgnoreCase.Reverse()
                : StringComparer.OrdinalIgnoreCase,
            EmailGroupingType.ByDate => SortDirection == EmailSortDirection.Descending
                ? CreateDateComparer(descending: true)
                : CreateDateComparer(descending: false),
            _ => StringComparer.Ordinal
        };
    }

    private static IComparer<string> CreateDateComparer(bool descending)
    {
        return Comparer<string>.Create((x, y) =>
        {
            var dateX = DateTime.TryParse(x, out var dx) ? dx : DateTime.MinValue;
            var dateY = DateTime.TryParse(y, out var dy) ? dy : DateTime.MinValue;

            var result = dateX.CompareTo(dateY);
            return descending ? -result : result;
        });
    }

    private int FindGroupInsertionPosition(string groupKey)
    {
        var comparer = GetGroupComparer();

        if (_groupHeaderIndexCache.Count == 0)
            return 0;

        var sortedGroups = _groupHeaderIndexCache.Keys.OrderBy(k => k, comparer).ToList();
        var insertPosition = 0;

        for (int i = 0; i < sortedGroups.Count; i++)
        {
            var existingGroupKey = sortedGroups[i];
            var comparison = comparer.Compare(groupKey, existingGroupKey);

            if (comparison < 0)
            {
                insertPosition = _groupHeaderIndexCache[existingGroupKey];
                break;
            }
            else if (i == sortedGroups.Count - 1)
            {
                var lastGroupHeaderIndex = _groupHeaderIndexCache[existingGroupKey];
                var lastGroupItemCount = _groupItems[existingGroupKey].Count;
                insertPosition = lastGroupHeaderIndex + 1 + lastGroupItemCount;
            }
        }

        return insertPosition;
    }

    private int FindGroupEndIndex(int headerIndex)
    {
        var groupKey = string.Empty;
        foreach (var kvp in _groupHeaderIndexCache)
        {
            if (kvp.Value == headerIndex)
            {
                groupKey = kvp.Key;
                break;
            }
        }

        return headerIndex + 1 + _groupItems.GetValueOrDefault(groupKey, []).Count;
    }

    private int FindItemInsertionIndexInGroup(object item, int groupStartIndex, int groupEndIndex)
    {
        var itemDate = GetEffectiveDate(item);

        for (int i = groupStartIndex + 1; i < groupEndIndex; i++)
        {
            var existingItem = Items[i];
            var existingDate = GetEffectiveDate(existingItem);

            var comparison = itemDate.CompareTo(existingDate);
            if (SortDirection == EmailSortDirection.Descending)
                comparison = -comparison;

            if (comparison < 0)
                return i;
        }

        return groupEndIndex;
    }

    private void UpdateHeaderIndicesAfterInsertion(int insertIndex, int itemCount = 1)
    {
        var keysToUpdate = _groupHeaderIndexCache
            .Where(kvp => kvp.Value > insertIndex)
            .Select(kvp => kvp.Key)
            .ToList();

        foreach (var key in keysToUpdate)
        {
            _groupHeaderIndexCache[key] += itemCount;
        }
    }

    private void UpdateHeaderIndicesAfterRemoval(int removeIndex)
    {
        var keysToUpdate = _groupHeaderIndexCache
            .Where(kvp => kvp.Value > removeIndex)
            .Select(kvp => kvp.Key)
            .ToList();

        foreach (var key in keysToUpdate)
        {
            _groupHeaderIndexCache[key]--;
        }
    }

    private void UpdateAllGroupHeaderCounts()
    {
        foreach (var (groupKey, groupHeader) in _groupHeaders)
        {
            UpdateGroupHeaderCounts(groupKey, groupHeader);
        }
    }

    private void UpdateGroupHeaderCounts(string groupKey, GroupHeaderBase groupHeader)
    {
        var emailsInGroup = _sourceItems.Where(e => GetGroupKey(e) == groupKey).ToList();
        var expandersInGroup = _threadExpanders.Values
            .Where(exp => GetGroupKeyForItem(exp) == groupKey)
            .ToList();

        var totalEmailCount = emailsInGroup.Count;
        var unreadCount = emailsInGroup.Count(e => e.MailCopy?.IsRead == false);

        groupHeader.ItemCount = totalEmailCount;
        groupHeader.UnreadCount = unreadCount;
    }

    private void UpdateGroupAfterChanges()
    {
        // Update all group header counts and remove empty groups
        var groupsToRemove = new List<string>();

        foreach (var (groupKey, groupHeader) in _groupHeaders.ToList())
        {
            UpdateGroupHeaderCounts(groupKey, groupHeader);

            if (groupHeader.ItemCount == 0)
            {
                groupsToRemove.Add(groupKey);
            }
        }

        foreach (var groupKey in groupsToRemove)
        {
            RemoveGroupHeader(groupKey);
        }
    }

    private void RemoveGroupHeader(string groupKey)
    {
        if (_groupHeaderIndexCache.TryGetValue(groupKey, out var headerIndex))
        {
            Items.RemoveAt(headerIndex);
            UpdateHeaderIndicesAfterRemoval(headerIndex);

            _groupHeaderIndexCache.Remove(groupKey);
            _groupHeaders.Remove(groupKey);
            _groupItems.Remove(groupKey);
        }
    }

    private void OnSourceItemsChanged(object? sender, NotifyCollectionChangedEventArgs e)
    {
        if (!_isUpdating)
        {
            RefreshGrouping();
        }
    }

    #endregion

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (disposing)
        {
            _sourceItems.CollectionChanged -= OnSourceItemsChanged;

            // Unregister from messenger
            WeakReferenceMessenger.Default.Unregister<PropertyChangedMessage<bool>>(this);

            // Reset IsDisplayedInThread for all emails before disposal
            foreach (var email in _sourceItems)
            {
                email.IsDisplayedInThread = false;
            }

            // Dispose all thread expanders
            foreach (var expander in _threadExpanders.Values)
            {
                expander.Dispose();
            }

            _sourceItems.Clear();
            Items.Clear();
            _groupHeaders.Clear();
            _groupHeaderIndexCache.Clear();
            _groupItems.Clear();
            _threadExpanders.Clear();
        }

        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

/// <summary>
/// Extension method to reverse IComparer for descending sorts
/// </summary>
internal static class ComparerExtensions
{
    public static IComparer<T> Reverse<T>(this IComparer<T> comparer)
    {
        return Comparer<T>.Create((x, y) => comparer.Compare(y, x));
    }
}
