using FasdDesktopUi.Basics.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

namespace FasdDesktopUi.Basics.UserControls
{
    public partial class HierarchicalSelectionControl : UserControl
    {
        private readonly ObservableCollection<HierarchicalSelectionItem> visibleItems = new ObservableCollection<HierarchicalSelectionItem>();
        private readonly Dictionary<string, HierarchicalSelectionItem> itemLookup = new Dictionary<string, HierarchicalSelectionItem>();
        private readonly DispatcherTimer searchDelayTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) };
        private string lastSearchText = string.Empty;
        private bool suppressTreeSelectionChanged;

        private TextBox searchTextBox;
        private TreeView treeViewControl;
        private ScrollViewer itemsScrollViewer;

        public ObservableCollection<HierarchicalSelectionItem> VisibleItems => visibleItems;

        public event EventHandler DropDownOpened;
        public event EventHandler DropDownClosed;

        public HierarchicalSelectionControl()
        {
            InitializeComponent();
            searchDelayTimer.Tick += SearchDelayTimer_Tick;
        }

        public bool CloseDropDownIfOpen()
        {
            if (ComboBoxControl?.IsDropDownOpen != true)
                return false;

            ComboBoxControl.IsDropDownOpen = false;
            return true;
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            EnsureTemplateParts();
            if (treeViewControl != null)
                treeViewControl.ItemsSource = VisibleItems;
        }

        #region DependencyProperties

        public ObservableCollection<HierarchicalSelectionItem> ItemsSource
        {
            get => (ObservableCollection<HierarchicalSelectionItem>)GetValue(ItemsSourceProperty);
            set => SetValue(ItemsSourceProperty, value);
        }

        public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
            nameof(ItemsSource),
            typeof(ObservableCollection<HierarchicalSelectionItem>),
            typeof(HierarchicalSelectionControl),
            new PropertyMetadata(null, OnItemsSourceChanged));

        private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is HierarchicalSelectionControl control)
            {
                control.RebuildLookup();
                control.ApplyFilter(control.lastSearchText);
            }
        }

        public HierarchicalSelectionItem SelectedItem
        {
            get => (HierarchicalSelectionItem)GetValue(SelectedItemProperty);
            set => SetValue(SelectedItemProperty, value);
        }

        public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
            nameof(SelectedItem),
            typeof(HierarchicalSelectionItem),
            typeof(HierarchicalSelectionControl),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemChanged));

        private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is HierarchicalSelectionControl control)
            {
                control.TryExpandToSelectedItem();
                control.SyncTreeSelectionWithSelectedItem(bringIntoView: false);
            }
        }

        public Brush ComboBoxBackground
        {
            get => (Brush)GetValue(ComboBoxBackgroundProperty);
            set => SetValue(ComboBoxBackgroundProperty, value);
        }

        public static readonly DependencyProperty ComboBoxBackgroundProperty =
            DependencyProperty.Register(nameof(ComboBoxBackground), typeof(Brush), typeof(HierarchicalSelectionControl), new PropertyMetadata(Brushes.Transparent));

        public string SearchPlaceholderText
        {
            get => (string)GetValue(SearchPlaceholderTextProperty);
            set => SetValue(SearchPlaceholderTextProperty, value);
        }

        public static readonly DependencyProperty SearchPlaceholderTextProperty =
            DependencyProperty.Register(nameof(SearchPlaceholderText), typeof(string), typeof(HierarchicalSelectionControl), new PropertyMetadata(string.Empty));

        #endregion

        #region UI Event Handling

        private void ComboBoxControl_DropDownOpened(object sender, EventArgs e)
        {
            EnsureTemplateParts();
            searchTextBox?.Focus();
            searchTextBox?.SelectAll();
            suppressTreeSelectionChanged = false;
            SyncTreeSelectionWithSelectedItem(bringIntoView: true);
            DropDownOpened?.Invoke(this, e);
        }

        private void ComboBoxControl_DropDownClosed(object sender, EventArgs e)
        {
            searchDelayTimer.Stop();
            suppressTreeSelectionChanged = false;
            DropDownClosed?.Invoke(this, e);
        }

        private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            searchDelayTimer.Stop();
            searchDelayTimer.Start();
        }

        private void SearchDelayTimer_Tick(object sender, EventArgs e)
        {
            searchDelayTimer.Stop();
            lastSearchText = searchTextBox?.Text ?? string.Empty;
            ApplyFilter(lastSearchText);
        }

        private void TreeViewControl_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            if (suppressTreeSelectionChanged)
                return;

            if (e.NewValue is HierarchicalSelectionItem selected)
            {
                var original = ResolveOriginalItem(selected);
                if (original != null && !Equals(SelectedItem, original))
                {
                    SelectedItem = original;
                }

                suppressTreeSelectionChanged = true;
                ComboBoxControl.IsDropDownOpen = false;
            }
        }

        #endregion

        #region Data Preparation and Filtering

        private HierarchicalSelectionItem ResolveOriginalItem(HierarchicalSelectionItem item)
        {
            if (item == null)
                return null;

            if (!string.IsNullOrWhiteSpace(item.Id) && itemLookup.TryGetValue(item.Id, out var original))
                return original;

            return null;
        }

        private void RebuildLookup()
        {
            itemLookup.Clear();

            if (ItemsSource == null)
                return;

            foreach (var entry in ItemsSource.SelectMany(item => item.SelfAndDescendants()))
            {
                if (string.IsNullOrWhiteSpace(entry.Id))
                    continue;

                itemLookup[entry.Id] = entry;
            }
        }

        private void ApplyFilter(string searchText)
        {
            visibleItems.Clear();

            if (ItemsSource == null)
                return;

            if (string.IsNullOrWhiteSpace(searchText))
            {
                foreach (var item in ItemsSource)
                {
                    item.SetExpandedRecursive(false);
                    visibleItems.Add(item);
                }

                TryExpandToSelectedItem();
                SyncTreeSelectionWithSelectedItem(bringIntoView: false);
                return;
            }

            var comparison = StringComparison.CurrentCultureIgnoreCase;

            foreach (var root in ItemsSource)
            {
                var clone = root.CloneBranch(node =>
                    node.FullPath?.IndexOf(searchText, comparison) >= 0 ||
                    node.DisplayName?.IndexOf(searchText, comparison) >= 0);

                if (clone == null)
                    continue;

                clone.SetExpandedRecursive(true);
                visibleItems.Add(clone);
            }

            // If the selected item is part of current results, keep it visually selected.
            SyncTreeSelectionWithSelectedItem(bringIntoView: false);
        }

        private void TryExpandToSelectedItem()
        {
            if (SelectedItem == null || string.IsNullOrWhiteSpace(SelectedItem.Id))
                return;

            if (!string.IsNullOrWhiteSpace(lastSearchText))
                return;

            var chain = SelectedItem;
            while (chain != null)
            {
                chain.IsExpanded = true;
                chain = chain.Parent;
            }
        }

        #endregion

        #region Tree Selection Sync

        private void SyncTreeSelectionWithSelectedItem(bool bringIntoView)
        {
            if (SelectedItem == null || string.IsNullOrWhiteSpace(SelectedItem.Id))
                return;

            EnsureTemplateParts();
            if (treeViewControl == null)
                return;

            // Wait for popup/template layout to finish so item containers are generated.
            Dispatcher.BeginInvoke(new Action(() =>
            {
                var target = FindVisibleItemById(VisibleItems, SelectedItem.Id);
                if (target == null)
                    return;

                var ancestor = target.Parent;
                while (ancestor != null)
                {
                    ancestor.IsExpanded = true;
                    ancestor = ancestor.Parent;
                }

                treeViewControl.UpdateLayout();
                var targetContainer = GetTreeViewItemContainer(treeViewControl, target);
                if (targetContainer == null)
                    return;

                suppressTreeSelectionChanged = true;
                try
                {
                    targetContainer.IsSelected = true;
                    if (bringIntoView)
                        targetContainer.BringIntoView();
                }
                finally
                {
                    suppressTreeSelectionChanged = false;
                }
            }), DispatcherPriority.Loaded);
        }

        private static HierarchicalSelectionItem FindVisibleItemById(IEnumerable<HierarchicalSelectionItem> items, string id)
        {
            if (items == null || string.IsNullOrWhiteSpace(id))
                return null;

            foreach (var item in items)
            {
                if (item == null)
                    continue;

                if (string.Equals(item.Id, id, StringComparison.OrdinalIgnoreCase))
                    return item;

                var childMatch = FindVisibleItemById(item.Children, id);
                if (childMatch != null)
                    return childMatch;
            }

            return null;
        }

        private static TreeViewItem GetTreeViewItemContainer(ItemsControl root, object targetItem)
        {
            if (root == null || targetItem == null)
                return null;

            var directContainer = root.ItemContainerGenerator.ContainerFromItem(targetItem) as TreeViewItem;
            if (directContainer != null)
                return directContainer;

            foreach (var child in root.Items)
            {
                var childContainer = root.ItemContainerGenerator.ContainerFromItem(child) as TreeViewItem;
                if (childContainer == null)
                    continue;

                childContainer.UpdateLayout();
                var result = GetTreeViewItemContainer(childContainer, targetItem);
                if (result != null)
                    return result;
            }

            return null;
        }

        #endregion

        #region Template and Scroll Handling

        private void EnsureTemplateParts()
        {
            if (treeViewControl == null)
            {
                treeViewControl = ComboBoxControl.Template.FindName("PART_TreeView", ComboBoxControl) as TreeView;
                if (treeViewControl != null)
                {
                    treeViewControl.SelectedItemChanged += TreeViewControl_SelectedItemChanged;
                    treeViewControl.PreviewKeyDown += DropDownContent_PreviewKeyDown;
                    treeViewControl.ItemsSource = VisibleItems;
                }
            }

            if (searchTextBox == null)
            {
                searchTextBox = ComboBoxControl.Template.FindName("PART_SearchTextBox", ComboBoxControl) as TextBox;
                if (searchTextBox != null)
                {
                    searchTextBox.TextChanged += SearchTextBox_TextChanged;
                    searchTextBox.PreviewKeyDown += DropDownContent_PreviewKeyDown;
                }
            }

            if (itemsScrollViewer == null)
            {
                itemsScrollViewer = ComboBoxControl.Template.FindName("PART_ItemsScrollViewer", ComboBoxControl) as ScrollViewer;
            }
        }

        private void ItemsScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            var scroller = itemsScrollViewer ?? sender as ScrollViewer;
            if (scroller == null || scroller.ScrollableHeight <= 0)
                return;

            var lines = SystemParameters.WheelScrollLines;
            if (lines < 0)
            {
                if (e.Delta < 0)
                    scroller.PageDown();
                else
                    scroller.PageUp();
                e.Handled = true;
                return;
            }

            if (lines == 0)
            {
                scroller.ScrollToVerticalOffset(scroller.VerticalOffset - e.Delta);
                e.Handled = true;
                return;
            }

            var direction = e.Delta < 0 ? 1 : -1;
            var stepCount = Math.Max(1, Math.Abs(e.Delta) / 120) * lines;
            for (var i = 0; i < stepCount; i++)
            {
                if (direction > 0)
                    scroller.LineDown();
                else
                    scroller.LineUp();
            }

            e.Handled = true;
        }

        private void DropDownContent_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key != Key.Escape)
                return;

            if (CloseDropDownIfOpen())
                e.Handled = true;
        }

        #endregion

        #region Keyboard

        protected override void OnPreviewKeyDown(KeyEventArgs e)
        {
            base.OnPreviewKeyDown(e);

            if (!IsEnabled)
                return;

            if (!ComboBoxControl.IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Down || e.Key == Key.Space))
            {
                ComboBoxControl.IsDropDownOpen = true;
                e.Handled = true;
                return;
            }

            if (ComboBoxControl.IsDropDownOpen && e.Key == Key.Escape)
            {
                ComboBoxControl.IsDropDownOpen = false;
                e.Handled = true;
            }
        }

        #endregion
    }
}
