using PCUT.Extensions;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;

namespace PCUT.Models
{
    public abstract class SelectableCollection : NotificationBase
    {
        protected readonly bool _withDefault;

        protected int _selectedIndex;
        public int SelectedIndex
        {
            get => _selectedIndex;
            set
            {
                if (_withDefault && value == 0)
                {
                    _selectedIndex = 0;
                }
                else
                {
                    SetProperty(ref _selectedIndex, value);
                }
            }
        }

        protected SelectableCollection(bool withDefault)
        {
            _withDefault = withDefault;
            _selectedIndex = -1;
        }

        private void Selector_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var selector = sender as Selector;
            if (_withDefault && selector.SelectedIndex == 0)
            {
                selector.SelectedIndex = -1;
            }
        }

        public void Bind(Selector selector)
        {
            if (selector != null)
                selector.SelectionChanged += Selector_SelectionChanged;
        }

        public virtual void Bind(AutoSuggestBox autoSuggestBox, bool validateMemberPath = true)
        {
            if (validateMemberPath && string.IsNullOrEmpty(autoSuggestBox.TextMemberPath))
                throw new ArgumentException("Cannot bind auto suggestion box with default binding strategy. TextMemberPath is null");
            autoSuggestBox.TextChanged += AutoSuggestBoxTextChanged;
            autoSuggestBox.SuggestionChosen += AutoSuggestBoxSuggestionChosen;
            autoSuggestBox.QuerySubmitted += AutoSuggestBoxQuerySubmitted;
        }

        protected virtual void AutoSuggestBoxTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) { }
        protected virtual void AutoSuggestBoxSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) { }
        protected virtual void AutoSuggestBoxQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) { }
    }

    public class SelectableCollection<T> : SelectableCollection
    {
        private readonly T DefaultItem;

        private readonly List<T> _items;
        public ObservableCollection<T> Items { get; }
        public T SelectedItem => _selectedIndex >= 0 ? Items[_selectedIndex] : default;

        private Func<T, string> _propertySelector;

        public SelectableCollection(T defaultItem) : base(true)
        {
            DefaultItem = defaultItem;
            _selectedIndex = -1;
            _items = new List<T>();
            Items = new ObservableCollection<T>();
        }

        public SelectableCollection(bool withDefault = false) : base(withDefault)
        {
            if (withDefault)
                DefaultItem = default;
            _selectedIndex = -1;
            _items = new List<T>();
            Items = new ObservableCollection<T>();
        }

        public SelectableCollection(params T[] items) : this()
        {
            AddRange(items);
        }

        public SelectableCollection(bool withDefault = false, params T[] items) : this(withDefault)
        {
            AddRange(items);
        }

        public SelectableCollection(T defaultItem, params T[] items) : this(defaultItem)
        {
            AddRange(items);
        }

        public void SelectBy(Func<T, bool> predicate)
        {
            var item = Items.FirstOrDefault(predicate);
            var index = (item == null) ? -1 : Items.IndexOf(item);
            SelectedIndex = index;
        }

        public void Load(IEnumerable<T> items)
        {
            SelectedIndex = -1;
            AddRange(items);
        }

        private void AddRange(IEnumerable<T> items)
        {
            _items.Clear();
            Items.Clear();
            if (_withDefault)
            {
                _items.Add(DefaultItem);
                Items.Add(DefaultItem);
            }
            foreach (var item in items)
            {
                _items.Add(item);
                Items.Add(item);
            }
        }

        public void Filter(Predicate<T> filter)
        {
            var start = _withDefault ? 1 : 0;
            Items.Filter(_items, filter, start);
            _selectedIndex = -2; // set to -2 so that the next select will trigger property changed event
        }

        public void Bind(AutoSuggestBox autoSuggestBox, Func<T, string> propertySelector)
        {
            _propertySelector = propertySelector;
            base.Bind(autoSuggestBox, validateMemberPath: propertySelector is null);
        }

        protected override void AutoSuggestBoxTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
        {
            if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
            {
                Predicate<T> filter;
                if (string.IsNullOrEmpty(sender.Text))
                {
                    filter = x => true;
                }
                else if (_propertySelector != null)
                {
                    filter = x => _propertySelector.Invoke(x)?.StartsWith(sender.Text) ?? false;
                }
                else
                {
                    var propertyInfo = typeof(T).GetProperty(sender.TextMemberPath, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
                    filter = x => propertyInfo.GetValue(x)?.ToString()?.StartsWith(sender.Text) ?? false;
                }
                Filter(filter);
            }
        }

        protected override void AutoSuggestBoxSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
        {
            
        }

        protected override void AutoSuggestBoxQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
        {
            if (string.IsNullOrEmpty(args.QueryText))
            {
                SelectedIndex = -1;
            }
            else if (args.ChosenSuggestion != null)
            {
                SelectedIndex = Items.IndexOf((T)args.ChosenSuggestion);
            }
            else
            {
                Func<T, bool> filter;
                if (_propertySelector != null)
                {
                    filter = x => _propertySelector.Invoke(x) == sender.Text;
                }
                else
                {
                    var propertyInfo = typeof(T).GetProperty(sender.TextMemberPath, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
                    filter = x => propertyInfo.GetValue(x)?.ToString() == sender.Text;
                }
                var item = Items.FirstOrDefault(filter);
                if (item != null)
                {
                    SelectedIndex = Items.IndexOf(item);
                }
            }
        }
    }
}
