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

namespace Connected.Components;

public partial class List : UIComponent, IDisposable
{
	protected string Classname =>
	new CssBuilder("list")
		.AddClass("list-padding", !DisablePadding)
	  .AddClass(AdditionalClassList)
	.Build();

	[CascadingParameter] protected List ParentList { get; set; }

	/// <summary>
	/// The color of the selected List Item.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.List.Appearance)]
	public ThemeColor Color { get; set; } = ThemeColor.Primary;

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

	/// <summary>
	/// Set true to make the list items clickable. This is also the precondition for list selection to work.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.List.Selecting)]
	public bool Clickable { get; set; }

	/// <summary>
	/// If true, vertical padding will be removed from the list.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.List.Appearance)]
	public bool DisablePadding { get; set; }

	/// <summary>
	/// If true, compact vertical padding will be applied to all list items.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.List.Appearance)]
	public bool Dense { get; set; }

	/// <summary>
	/// If true, the left and right padding is removed on all list items.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.List.Appearance)]
	public bool DisableGutters { get; set; }

	/// <summary>
	/// If true, will disable the list item if it has onclick.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.List.Behavior)]
	public bool Disabled { get; set; }

	/// <summary>
	/// The current selected list item.
	/// Note: make the list Clickable for item selection to work.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.List.Selecting)]
	public ListItem SelectedItem
	{
		get => _selectedItem;
		set
		{
			if (_selectedItem == value)
				return;
			SetSelectedValue(_selectedItem?.Value, force: true);
		}
	}

	/// <summary>
	/// Called whenever the selection changed
	/// </summary>
	[Parameter] public EventCallback<ListItem> SelectedItemChanged { get; set; }

	/// <summary>
	/// The current selected value.
	/// Note: make the list Clickable for item selection to work.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.List.Selecting)]
	public object SelectedValue
	{
		get => _selectedValue;
		set
		{
			SetSelectedValue(value, force: true);
		}
	}

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

	protected override void OnInitialized()
	{
		if (ParentList != null)
		{
			ParentList.Register(this);
			CanSelect = ParentList.CanSelect;
		}
		else
		{
			CanSelect = SelectedItemChanged.HasDelegate || SelectedValueChanged.HasDelegate || SelectedValue != null;
		}
	}

	internal event Action ParametersChanged;

	protected override void OnParametersSet()
	{
		base.OnParametersSet();
		ParametersChanged?.Invoke();
	}

	private HashSet<ListItem> _items = new();
	private HashSet<List> _childLists = new();
	private ListItem _selectedItem;
	private object _selectedValue;

	internal void Register(ListItem item)
	{
		_items.Add(item);
		if (CanSelect && SelectedValue != null && object.Equals(item.Value, SelectedValue))
		{
			item.SetSelected(true);
			_selectedItem = item;
			SelectedItemChanged.InvokeAsync(item);
		}
	}

	internal void Unregister(ListItem item)
	{
		_items.Remove(item);
	}

	internal void Register(List child)
	{
		_childLists.Add(child);
	}

	internal void Unregister(List child)
	{
		_childLists.Remove(child);
	}

	internal void SetSelectedValue(object value, bool force = false)
	{
		if ((!CanSelect || !Clickable) && !force)
			return;
		if (object.Equals(_selectedValue, value))
			return;
		_selectedValue = value;
		SelectedValueChanged.InvokeAsync(value).AndForget();
		_selectedItem = null; // <-- for now, we'll see which item matches the value below
		foreach (var listItem in _items.ToArray())
		{
			var isSelected = value != null && object.Equals(value, listItem.Value);
			listItem.SetSelected(isSelected);
			if (isSelected)
				_selectedItem = listItem;
		}
		foreach (var childList in _childLists.ToArray())
		{
			childList.SetSelectedValue(value);
			if (childList.SelectedItem != null)
				_selectedItem = childList.SelectedItem;
		}
		SelectedItemChanged.InvokeAsync(_selectedItem).AndForget();
		ParentList?.SetSelectedValue(value);
	}

	internal bool CanSelect { get; private set; }

	public void Dispose()
	{
		ParametersChanged = null;
		ParentList?.Unregister(this);
	}

}
