using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;

namespace ytLive.Helpers;

/// <summary>
/// A ListBox that keeps selection and keyboard focus coherent when the
/// selected item is deleted. Focus lands on the item now at the deleted
/// position (the previous item when the last one was removed) or leaves
/// the box entirely when it becomes empty. Drag-to-reorder is untouched.
/// </summary>
public class FocusPreservingListBox : ListBox
{
    /// <summary>
    /// Predicate deciding which items the delete-focus fallback may land on
    /// (e.g. skip hidden scenes). Null means every item is eligible.
    /// </summary>
    public Func<object, bool>? IsSelectable { get; set; }

    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
    {
        base.OnItemsChanged(e);

        if (e.Action != NotifyCollectionChangedAction.Remove) return;
        var hadFocus = IsKeyboardFocusWithin || IsKeyboardFocused;
        if (!hadFocus) return;
        if (Mouse.Captured == this) return;

        if (Items.Count == 0)
        {
            SelectedItem = null;
            MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
            return;
        }

        var targetIndex = FindSelectableIndex(Math.Clamp(e.OldStartingIndex, 0, Items.Count - 1));
        if (targetIndex < 0)
        {
            SelectedItem = null;
            return;
        }

        SelectedIndex = targetIndex;
        var item = Items[targetIndex];

        Dispatcher.BeginInvoke(DispatcherPriority.Loaded, () =>
        {
            ScrollIntoView(item);
            if (ItemContainerGenerator.ContainerFromItem(item) is ListBoxItem container)
                container.Focus();
        });
    }

    private int FindSelectableIndex(int start)
    {
        for (var i = start; i >= 0; i--)
            if (IsItemSelectable(i)) return i;
        for (var i = start + 1; i < Items.Count; i++)
            if (IsItemSelectable(i)) return i;
        return -1;
    }

    private bool IsItemSelectable(int index)
    {
        if (index < 0 || index >= Items.Count) return false;
        var item = Items[index];
        return item != null && (IsSelectable?.Invoke(item) ?? true);
    }
}
