using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Threading;
using System.Diagnostics;
namespace SearchableComboBox
{
    /// 
    /// Represents a ComboBox with search functionality.
    /// 
    public class SearchableComboBox : ComboBox
    {
        #region Fields and Constructors
        private readonly DispatcherTimer _debounceTimer;
        private bool debug = true;
        private TextBox _searchTermTextBox;
        private CollectionViewSource _collectionViewSource;
        private object _lastSelectedItem;
        private bool _isDropDownOpen = false;
        private TextBlock _placeholderTextBlock;
        /// 
        /// Initializes a new instance of the  class.
        /// 
        public SearchableComboBox()
        {
            Debug.WriteLineIf(debug, "Constructor");
            _debounceTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(300) };
            _debounceTimer.Tick += DebounceTimer_Tick;
            this.DropDownClosed += OnDropDownClosed;
        }
        static SearchableComboBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchableComboBox), new FrameworkPropertyMetadata(typeof(SearchableComboBox)));
        }
        public override void OnApplyTemplate()
        {
            Debug.WriteLineIf(debug, "OnApplyTemplate");
            base.OnApplyTemplate();
            _searchTermTextBox = Template.FindName("SearchTermTextBox", this) as TextBox;
            _placeholderTextBlock = Template.FindName("PlaceholderTextBlock", this) as TextBlock;
            UpdatePlaceholderVisibility();
        }
        #endregion
        #region CustomSort Support
        public static readonly DependencyProperty CustomSortProperty =
            DependencyProperty.Register(
                "CustomSort",
                typeof(IComparer),
                typeof(SearchableComboBox),
                new PropertyMetadata(null, OnCustomSortChanged));
        /// 
        /// Gets or sets the custom sort comparer.
        /// 
        public IComparer CustomSort
        {
            get => (IComparer)GetValue(CustomSortProperty);
            set => SetValue(CustomSortProperty, value);
        }
        private static void OnCustomSortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var comboBox = d as SearchableComboBox;
            if (comboBox._collectionViewSource != null && comboBox._collectionViewSource.View is ListCollectionView lcv)
            {
                lcv.CustomSort = e.NewValue as IComparer;
            }
        }
        #endregion
        #region IsSearchEnabled
        public static readonly DependencyProperty IsSearchEnabledProperty =
            DependencyProperty.Register(
                "IsSearchEnabled",
                typeof(bool),
                typeof(SearchableComboBox),
                new PropertyMetadata(true));
        /// 
        /// Gets or sets a value indicating whether search is enabled.
        /// 
        public bool IsSearchEnabled
        {
            get => (bool)GetValue(IsSearchEnabledProperty);
            set => SetValue(IsSearchEnabledProperty, value);
        }
        #endregion
        #region Placeholder Text
        public static readonly DependencyProperty PlaceholderStyleProperty =
            DependencyProperty.Register("PlaceholderStyle", typeof(Style), typeof(SearchableComboBox), new PropertyMetadata(null));
        /// 
        /// Gets or sets the style for the placeholder text block.
        /// 
        public Style PlaceholderStyle
        {
            get => (Style)GetValue(PlaceholderStyleProperty);
            set => SetValue(PlaceholderStyleProperty, value);
        }
        public static readonly DependencyProperty PlaceholderProperty =
            DependencyProperty.Register("Placeholder", typeof(string), typeof(SearchableComboBox), new PropertyMetadata(string.Empty));
        /// 
        /// Gets or sets the placeholder text.
        /// 
        public string Placeholder
        {
            get => (string)GetValue(PlaceholderProperty);
            set => SetValue(PlaceholderProperty, value);
        }
        private void UpdatePlaceholderVisibility()
        {
            if (_placeholderTextBlock == null) return;
            if (SelectedItem == null || SelectedItem.Equals(string.Empty))
            {
                _placeholderTextBlock.Visibility = Visibility.Visible;
            }
            else
            {
                _placeholderTextBlock.Visibility = Visibility.Collapsed;
            }
        }
        #endregion
        #region OnItemsSourceChanged event
        protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
        {
            Debug.WriteLineIf(debug, "OnItemsSourceChanged");
            base.OnItemsSourceChanged(oldValue, newValue);
            if (_collectionViewSource == null)
            {
                _collectionViewSource = new CollectionViewSource { Source = newValue };
                _collectionViewSource.View.Filter = FilterPredicate;
                if (_collectionViewSource.View is ListCollectionView lcv && CustomSort != null)
                {
                    lcv.CustomSort = CustomSort;
                }
                ItemsSource = _collectionViewSource.View;
            }
        }
        #endregion
        #region Changes only when selected
        protected override void OnSelectionChanged(SelectionChangedEventArgs e)
        {
            Debug.WriteLineIf(debug, "OnSelectionChanged");
            if (_isDropDownOpen && e.AddedItems.Count == 0 && e.RemovedItems.Count > 0)
            {
                // If the dropdown is open and the selection was removed due to filtering, revert the selection to the last selected item
                SelectedItem = _lastSelectedItem;
                return; // Exit early to avoid additional processing and raising events
            }
            if (e.AddedItems.Count > 0)
            {
                _lastSelectedItem = e.AddedItems[0];
            }
            UpdatePlaceholderVisibility();
            base.OnSelectionChanged(e);
        }
        #endregion
        #region Error and border visibility
        public static readonly DependencyProperty NotFoundLabelVisibilityProperty =
            DependencyProperty.Register("NotFoundLabelVisibility", typeof(Visibility), typeof(SearchableComboBox), new PropertyMetadata(Visibility.Collapsed));
        public static readonly DependencyProperty DropDownBorderVisibilityProperty =
            DependencyProperty.Register("DropDownBorderVisibility", typeof(Visibility), typeof(SearchableComboBox), new PropertyMetadata(Visibility.Visible));
        /// 
        /// Gets or sets the visibility of the not found label.
        /// 
        public Visibility NotFoundLabelVisibility
        {
            get => (Visibility)GetValue(NotFoundLabelVisibilityProperty);
            set => SetValue(NotFoundLabelVisibilityProperty, value);
        }
        /// 
        /// Gets or sets the visibility of the dropdown border.
        /// 
        public Visibility DropDownBorderVisibility
        {
            get => (Visibility)GetValue(DropDownBorderVisibilityProperty);
            set => SetValue(DropDownBorderVisibilityProperty, value);
        }
        private void UpdateVisibility()
        {
            Debug.WriteLineIf(debug, "UpdateVisibility");
            if (_collectionViewSource.View.IsEmpty)
            {
                NotFoundLabelVisibility = Visibility.Visible;
                DropDownBorderVisibility = Visibility.Collapsed;
            }
            else
            {
                NotFoundLabelVisibility = Visibility.Collapsed;
                DropDownBorderVisibility = Visibility.Visible;
            }
        }
        #endregion
        #region Filtering logic
        public static readonly DependencyProperty SearchTermProperty =
            DependencyProperty.Register("SearchTerm", typeof(string), typeof(SearchableComboBox), new PropertyMetadata(string.Empty, OnSearchTermChanged));
        /// 
        /// Gets or sets the search term.
        /// 
        public string SearchTerm
        {
            get => (string)GetValue(SearchTermProperty);
            set => SetValue(SearchTermProperty, value);
        }
        private static void OnSearchTermChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is SearchableComboBox searchableComboBox)
            {
                searchableComboBox.DebounceFilter();
            }
        }
        private void DebounceFilter()
        {
            _debounceTimer.Stop();
            _debounceTimer.Start();
        }
        private void DebounceTimer_Tick(object sender, EventArgs e)
        {
            _debounceTimer.Stop();
            _collectionViewSource.View.Refresh();
            UpdateVisibility();
        }
        private bool FilterPredicate(object item)
        {
            if (string.IsNullOrEmpty(SearchTerm))
            {
                return true;
            }
            string strItem = item.ToString();
            if (strItem.IndexOf(SearchTerm, StringComparison.OrdinalIgnoreCase) >= 0)
            {
                return true;
            }
            return false;
        }
        protected override void OnDropDownOpened(EventArgs e)
        {
            Debug.WriteLineIf(debug, "OnDropDownOpened");
            _isDropDownOpen = true;
            _lastSelectedItem = SelectedItem;  // Store the current selected item.
            this.Dispatcher.BeginInvoke((Action)(() =>
            {
                _searchTermTextBox?.Focus();
            }), DispatcherPriority.ContextIdle);
            base.OnDropDownOpened(e);
        }
        #endregion
        #region Clear Search
        private void OnDropDownClosed(object sender, EventArgs e)
        {
            Debug.WriteLineIf(debug, "OnDropDownClosed");
            SearchTerm = string.Empty;
            _collectionViewSource.View.Refresh();
            if (_lastSelectedItem != null)
            {
                SelectedItem = _lastSelectedItem;
            }
            _isDropDownOpen = false;
        }
        #endregion
    }
}