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

namespace Connected.Components;

public partial class FocusTrap : IDisposable
{
	protected string Classname =>
		 new CssBuilder("outline-none")
			  .AddClass(AdditionalClassList)
			  .Build();

	protected ElementReference _firstBumper;
	protected ElementReference _lastBumper;
	protected ElementReference _fallback;
	protected ElementReference _root;

	private bool _shiftDown;
	private bool _disabled;
	private bool _initialized;

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

	/// <summary>
	/// If true, the focus will no longer loop inside the component.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.FocusTrap.Behavior)]
	public bool Disabled
	{
		get => _disabled;
		set
		{
			if (_disabled != value)
			{
				_disabled = value;
				_initialized = false;
			}
		}
	}

	/// <summary>
	/// Defines on which element to set the focus when the component is created or enabled.
	/// When DefaultFocus.Element is used, the focus will be set to the FocusTrap itself, so the user will have to press TAB key once to focus the first tabbable element.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.FocusTrap.Behavior)]
	public DefaultFocus DefaultFocus { get; set; } = DefaultFocus.FirstChild;

	protected override async Task OnAfterRenderAsync(bool firstRender)
	{
		await base.OnAfterRenderAsync(firstRender);

		if (firstRender)
			await SaveFocusAsync();

		if (!_initialized)
			await InitializeFocusAsync();
	}

	private Task OnBottomFocusAsync(FocusEventArgs args)
	{
		return FocusLastAsync();
	}

	private Task OnBumperFocusAsync(FocusEventArgs args)
	{
		return _shiftDown ? FocusLastAsync() : FocusFirstAsync();
	}

	private Task OnRootFocusAsync(FocusEventArgs args)
	{
		return FocusFallbackAsync();
	}

	private void OnRootKeyDown(KeyboardEventArgs args)
	{
		HandleKeyEvent(args);
	}

	private void OnRootKeyUp(KeyboardEventArgs args)
	{
		HandleKeyEvent(args);
	}

	private Task OnTopFocusAsync(FocusEventArgs args)
	{
		return FocusFirstAsync();
	}

	private Task InitializeFocusAsync()
	{
		_initialized = true;

		if (!_disabled)
		{
			switch (DefaultFocus)
			{
				case DefaultFocus.Element: return FocusFallbackAsync();
				case DefaultFocus.FirstChild: return FocusFirstAsync();
				case DefaultFocus.LastChild: return FocusLastAsync();
			}
		}
		return Task.CompletedTask;
	}

	private Task FocusFallbackAsync()
	{
		return _fallback.FocusAsync().AsTask();
	}

	private Task FocusFirstAsync()
	{
		return _root.FocusFirstAsync(2, 4).AsTask();
	}

	private Task FocusLastAsync()
	{
		return _root.FocusLastAsync(2, 4).AsTask();
	}

	private void HandleKeyEvent(KeyboardEventArgs args)
	{
		_shouldRender = false;
		if (args.Key == "Tab")
			_shiftDown = args.ShiftKey;
	}

	private Task RestoreFocusAsync()
	{
		return _root.RestoreFocusAsync().AsTask();
	}

	private Task SaveFocusAsync()
	{
		return _root.SaveFocusAsync().AsTask();
	}

	bool _shouldRender = true;

	protected override bool ShouldRender()
	{
		if (_shouldRender)
			return true;
		_shouldRender = true; // auto-reset _shouldRender to true
		return false;
	}

	public void Dispose()
	{
		if (!_disabled)
			RestoreFocusAsync().AndForget(TaskOption.Safe);
	}
}
