using Connected.Annotations;
using Connected.Utilities;
using Microsoft.AspNetCore.Components;

namespace Connected.Components;

public partial class ChipSet : UIComponent, IDisposable
{

   protected string Classname =>
   new CssBuilder("chipset")
     .AddClass(AdditionalClassList)
   .Build();

   /// <summary>
   /// Child content of component.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.ChipSet.Behavior)]
   public RenderFragment ChildContent { get; set; }

   /// <summary>
   /// Allows to select more than one chip.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.ChipSet.Behavior)]
   public bool MultiSelection { get; set; } = false;

   /// <summary>
   /// Will not allow to deselect the selected chip in single selection mode.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.ChipSet.Behavior)]
   public bool Mandatory { get; set; } = false;

   /// <summary>
   /// Will make all chips closable.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.ChipSet.Behavior)]
   public bool AllClosable { get; set; } = false;

   /// <summary>
   ///  Will show a check-mark for the selected components.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.ChipSet.Appearance)]
   public bool Filter
   {
      get => _filter;
      set
      {
         if (_filter == value)
            return;
         _filter = value;
         StateHasChanged();
         foreach (var chip in _chips)
            chip.ForceRerender();
      }
   }

   /// <summary>
   ///  Will make all chips read only.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.ChipSet.Behavior)]
   public bool ReadOnly { get; set; } = false;

   /// <summary>
   /// The currently selected chip in Choice mode
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.ChipSet.Behavior)]
   public Chip SelectedChip
   {
      get { return _chips.OfType<Chip>().FirstOrDefault(x => x.IsSelected); }
      set
      {
         if (value == null)
         {
            foreach (var chip in _chips)
            {
               chip.IsSelected = false;
            }
         }
         else
         {
            foreach (var chip in _chips)
            {
               chip.IsSelected = (chip == value);
            }
         }
         this.InvokeAsync(StateHasChanged);
      }
   }

   /// <summary>
   /// Called when the selected chip changes, in Choice mode
   /// </summary>
   [Parameter]
   public EventCallback<Chip> SelectedChipChanged { get; set; }

   /// <summary>
   /// The currently selected chips in Filter mode
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.ChipSet.Behavior)]
   public Chip[] SelectedChips
   {
      get { return _chips.OfType<Chip>().Where(x => x.IsSelected).ToArray(); }
      set
      {
         if (value == null || value.Length == 0)
         {
            foreach (var chip in _chips)
            {
               chip.IsSelected = false;
            }
         }
         else
         {
            var selected = new HashSet<Chip>(value);
            foreach (var chip in _chips)
            {
               chip.IsSelected = selected.Contains(chip);
            }
         }
         StateHasChanged();
      }
   }

   protected override void OnInitialized()
   {
      base.OnInitialized();
      if (_selectedValues == null)
         _selectedValues = new HashSet<object>(_comparer);
      _initialSelectedValues = new HashSet<object>(_selectedValues, _comparer);
   }

   private IEqualityComparer<object> _comparer;
   private HashSet<object> _selectedValues;
   private HashSet<object> _initialSelectedValues;

   /// <summary>
   /// The Comparer to use for comparing selected values internally.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.ChipSet.Behavior)]
   public IEqualityComparer<object> Comparer
   {
      get => _comparer;
      set
      {
         _comparer = value;
         // Apply comparer and refresh selected values
         _selectedValues = new HashSet<object>(_selectedValues, _comparer);
         SelectedValues = _selectedValues;
      }
   }

   /// <summary>
   /// Called when the selection changed, in Filter mode
   /// </summary>
   [Parameter]
   public EventCallback<Chip[]> SelectedChipsChanged { get; set; }

   /// <summary>
   /// The current selected value.
   /// Note: make the list Clickable for item selection to work.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.ChipSet.Behavior)]
   public ICollection<object> SelectedValues
   {
      get => _selectedValues;
      set
      {
         if (value == null)
            SetSelectedValues(new object[0]);
         else
            SetSelectedValues(value.ToArray()).AndForget();
      }
   }

   /// <summary>
   /// Called whenever the selection changed
   /// </summary>
   [Parameter] public EventCallback<ICollection<object>> SelectedValuesChanged { get; set; }

   internal Task SetSelectedValues(object[] values)
   {
      HashSet<object> newValues = null;
      if (values == null)
         values = new object[0];
      if (MultiSelection)
         newValues = new HashSet<object>(values, _comparer);
      else
      {
         newValues = new HashSet<object>(_comparer);
         if (values.Length > 0)
            newValues.Add(values.First());
      }
      // avoid update with same values
      if (_selectedValues.IsEqualTo(newValues))
         return Task.CompletedTask;
      _selectedValues = newValues;
      foreach (var chip in _chips.ToArray())
      {
         var isSelected = _selectedValues.Contains(chip.Value);
         chip.IsSelected = isSelected;
      }
      return NotifySelection();
   }

   /// <summary>
   /// Called when a Chip was deleted (by click on the close icon)
   /// </summary>
   [Parameter]
   public EventCallback<Chip> OnClose { get; set; }

   internal Task Add(Chip chip)
   {
      _chips.Add(chip);
      if (_selectedValues.Contains(chip.Value))
         chip.IsSelected = true;
      return CheckDefault(chip);
   }

   internal void Remove(Chip chip)
   {
      _chips.Remove(chip);
      if (chip.IsSelected)
      {
         _selectedValues.Remove(chip.Value);
         NotifySelection().AndForget();
      }
   }

   private async Task CheckDefault(Chip chip)
   {
      if (!MultiSelection)
         return;
      if (chip.DefaultProcessed)
         return;
      chip.DefaultProcessed = true;
      if (chip.Default == null)
         return;
      var oldSelected = chip.IsSelected;
      chip.IsSelected = chip.Default == true;
      if (chip.IsSelected != oldSelected)
      {
         if (chip.IsSelected)
            _selectedValues.Add(chip.Value);
         else
            _selectedValues.Remove(chip.Value);
         await NotifySelection();
      }
   }

   private HashSet<Chip> _chips = new();
   private bool _filter;

   internal Task OnChipClicked(Chip chip)
   {
      var wasSelected = chip.IsSelected;
      if (MultiSelection)
      {
         chip.IsSelected = !chip.IsSelected;
      }
      else
      {
         foreach (var ch in _chips)
         {
            ch.IsSelected = (ch == chip); // <-- exclusively select the one chip only, thus all others must be deselected
         }
         if (!Mandatory)
            chip.IsSelected = !wasSelected;
      }
      UpdateSelectedValues();
      return NotifySelection();
   }

   private void UpdateSelectedValues()
   {
      _selectedValues = new HashSet<object>(_chips.Where(x => x.IsSelected).Select(x => x.Value), _comparer);
   }

   private object[] _lastSelectedValues = null;

   private async Task NotifySelection()
   {
      if (_disposed)
         return;
      // to avoid endless notification loops we check if selection has really changed
      if (_selectedValues.IsEqualTo(_lastSelectedValues))
         return;
      _lastSelectedValues = _selectedValues.ToArray();
      await SelectedChipChanged.InvokeAsync(SelectedChip);
      await SelectedChipsChanged.InvokeAsync(SelectedChips);
      await SelectedValuesChanged.InvokeAsync(SelectedValues);
      StateHasChanged();
   }

   public void OnChipDeleted(Chip chip)
   {
      Remove(chip);
      OnClose.InvokeAsync(chip);
   }

   protected override async void OnAfterRender(bool firstRender)
   {
      if (firstRender)
         await SelectDefaultChips();
      base.OnAfterRender(firstRender);
   }

   private async Task SelectDefaultChips()
   {
      if (!MultiSelection)
      {
         var anySelected = false;
         var defaultChip = _chips.LastOrDefault(chip => chip.Default == true);
         if (defaultChip != null)
         {
            defaultChip.IsSelected = true;
            anySelected = true;
         }
         if (anySelected)
         {
            UpdateSelectedValues();
            await NotifySelection();
         }
      }
   }

   private bool _disposed;

   public void Dispose()
   {
      _disposed = true;
   }
}
