using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;

namespace FasdDesktopUi.Basics.Models
{
    public class HierarchicalSelectionItem : INotifyPropertyChanged
    {
        private bool _isExpanded;

        public string Id { get; set; }
        public string DisplayName { get; set; }
        public string ParentId { get; set; }
        public string ParentDisplayName { get; set; }

        public ObservableCollection<HierarchicalSelectionItem> Children { get; } = new ObservableCollection<HierarchicalSelectionItem>();

        public HierarchicalSelectionItem Parent { get; private set; }

        public bool IsExpanded
        {
            get => _isExpanded;
            set
            {
                if (_isExpanded == value)
                    return;
                _isExpanded = value;
                OnPropertyChanged(nameof(IsExpanded));
            }
        }

        public string FullPath
        {
            get
            {
                if (Parent == null || string.IsNullOrWhiteSpace(Parent.DisplayName))
                    return DisplayName ?? string.Empty;

                if (string.IsNullOrWhiteSpace(DisplayName))
                    return Parent.FullPath;

                return $"{Parent.FullPath} / {DisplayName}";
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        public void AddChild(HierarchicalSelectionItem child)
        {
            if (child == null)
                return;

            child.Parent = this;
            Children.Add(child);
        }

        public void ClearChildren()
        {
            foreach (var child in Children)
                child.Parent = null;

            Children.Clear();
        }

        public void SortChildrenRecursive()
        {
            if (Children == null || Children.Count == 0)
                return;

            var orderedChildren = Children
                .OrderBy(child => child.DisplayName, StringComparer.CurrentCultureIgnoreCase)
                .ToList();

            Children.Clear();

            foreach (var child in orderedChildren)
            {
                Children.Add(child);
                child.SortChildrenRecursive();
            }
        }

        public IEnumerable<HierarchicalSelectionItem> SelfAndDescendants()
        {
            yield return this;
            foreach (var child in Children)
            {
                foreach (var descendant in child.SelfAndDescendants())
                    yield return descendant;
            }
        }

        public void SetExpandedRecursive(bool isExpanded)
        {
            IsExpanded = isExpanded;

            foreach (var child in Children)
                child.SetExpandedRecursive(isExpanded);
        }

        public HierarchicalSelectionItem CloneWithoutChildren()
        {
            return new HierarchicalSelectionItem
            {
                Id = Id,
                DisplayName = DisplayName,
                ParentId = ParentId,
                ParentDisplayName = ParentDisplayName
            };
        }

        public HierarchicalSelectionItem CloneBranch(Func<HierarchicalSelectionItem, bool> predicate)
        {
            bool matches = predicate?.Invoke(this) ?? true;
            var matchingChildren = new List<HierarchicalSelectionItem>();

            foreach (var child in Children)
            {
                var childClone = child.CloneBranch(predicate);
                if (childClone != null)
                    matchingChildren.Add(childClone);
            }

            if (!matches && matchingChildren.Count == 0)
                return null;

            var clone = CloneWithoutChildren();
            foreach (var childClone in matchingChildren)
                clone.AddChild(childClone);

            return clone;
        }

        public static ObservableCollection<HierarchicalSelectionItem> BuildTree(IEnumerable<HierarchicalSelectionItem> items)
        {
            if (items == null)
                return new ObservableCollection<HierarchicalSelectionItem>();

            var lookup = items
                .Where(item => !string.IsNullOrWhiteSpace(item?.Id))
                .GroupBy(item => item.Id)
                .Select(group => group.First())
                .ToDictionary(item => item.Id);

            foreach (var entry in lookup.Values)
                entry.ClearChildren();

            var roots = new List<HierarchicalSelectionItem>();

            foreach (var item in lookup.Values)
            {
                if (!string.IsNullOrWhiteSpace(item.ParentId) && lookup.TryGetValue(item.ParentId, out var parent))
                {
                    parent.AddChild(item);
                }
                else
                {
                    item.Parent = null;
                    roots.Add(item);
                }
            }

            foreach (var root in roots)
                root.SortChildrenRecursive();

            return new ObservableCollection<HierarchicalSelectionItem>(
                roots.OrderBy(root => root.DisplayName, StringComparer.CurrentCultureIgnoreCase));
        }
    }
}
