using System.Diagnostics.CodeAnalysis;
using Connected.Annotations;
using Connected.Extensions;
using Connected.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace Connected.Components;

public partial class Popover : UIComponent, IAsyncDisposable
{
	[Inject] public IPopoverService Service { get; set; }

	protected string PopoverClass =>
		new CssBuilder("popover")
		.AddClass($"popover-fixed", Fixed)
		.AddClass($"popover-open", Open)
		.AddClass($"popover-{TransformOrigin.ToDescription()}")
		.AddClass($"popover-anchor-{AnchorOrigin.ToDescription()}")
		.AddClass($"popover-overflow-{OverflowBehavior.ToDescription()}")
		.AddClass($"popover-relative-width", RelativeWidth)
		.AddClass($"paper", Paper)
		.AddClass($"paper-square", Paper && Square)
		.AddClass($"elevation-{Elevation}", Paper)
		.AddClass($"overflow-y-auto", MaxHeight != null)
		.AddClass(AdditionalClassList)
		.Build();

	protected string PopoverStyles =>
		new StyleBuilder()
		.AddStyle("transition-duration", $"{Duration}ms")
		.AddStyle("transition-delay", $"{Delay}ms")
		.AddStyle("max-height", MaxHeight.ToPx(), MaxHeight != null)
		.Build();

	internal Direction ConvertDirection(Direction direction)
	{
		return direction switch
		{
			Direction.Start => RightToLeft ? Direction.Right : Direction.Left,
			Direction.End => RightToLeft ? Direction.Left : Direction.Right,
			_ => direction
		};
	}

	[CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; }

	/// <summary>
	/// Sets the maxheight the popover can have when open.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.Popover.Appearance)]
	public int? MaxHeight { get; set; } = null;

	/// <summary>
	/// If true, will apply default MudPaper classes.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.Popover.Appearance)]
	public bool Paper { get; set; } = true;

	/// <summary>
	/// The higher the number, the heavier the drop-shadow.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.Popover.Appearance)]
	public int Elevation { set; get; } = 8;

	/// <summary>
	/// If true, border-radius is set to 0.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.Popover.Appearance)]
	public bool Square { get; set; }

	/// <summary>
	/// If true, the popover is visible.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.Popover.Behavior)]
	public bool Open { get; set; }

	/// <summary>
	/// If true the popover will be fixed position instead of absolute.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.Popover.Behavior)]
	public bool Fixed { get; set; }

	/// <summary>
	/// Sets the length of time that the opening transition takes to complete.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.Popover.Appearance)]
	public double Duration { get; set; } = 251;

	/// <summary>
	/// Sets the amount of time in milliseconds to wait from opening the popover before beginning to perform the transition. 
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.Popover.Appearance)]
	public double Delay { get; set; } = 0;

	/// <summary>
	/// Sets the direction the popover will start from relative to its parent.
	/// </summary>
	/// 
	[Obsolete("Use AnchorOrigin and TransformOrigin instead.", true)]
	[Parameter] public Direction Direction { get; set; } = Direction.Bottom;

	/// <summary>
	/// Set the anchor point on the element of the popover.
	/// The anchor point will determinate where the popover will be placed.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.Popover.Appearance)]
	public Origin AnchorOrigin { get; set; } = Origin.TopLeft;

	/// <summary>
	/// Sets the intersection point if the anchor element. At this point the popover will lay above the popover. 
	/// This property in conjunction with AnchorPlacement determinate where the popover will be placed.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.Popover.Appearance)]
	public Origin TransformOrigin { get; set; } = Origin.TopLeft;

	/// <summary>
	/// Set the overflow behavior of a popover and controls how the element should react if there is not enough space for the element to be visible
	/// Defaults to none, which doens't apply any overflow logic
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.Popover.Appearance)]
	public OverflowBehavior OverflowBehavior { get; set; } = OverflowBehavior.FlipOnOpen;

	/// <summary>
	/// If true, the select menu will open either above or bellow the input depending on the direction.
	/// </summary>
	[ExcludeFromCodeCoverage]
	[Obsolete("Use AnchorOrigin and TransformOrigin instead.", true)]
	[Parameter] public bool OffsetX { get; set; }

	/// <summary>
	/// If true, the select menu will open either before or after the input depending on the direction.
	/// </summary>
	[ExcludeFromCodeCoverage]
	[Obsolete("Use AnchorOrigin and TransformOrigin instead.", true)]
	[Parameter] public bool OffsetY { get; set; }

	/// <summary>
	/// If true, the popover will have the same width at its parent element, default to false
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.Popover.Appearance)]
	public bool RelativeWidth { get; set; } = false;

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

	private PopoverHandler _handler;

	protected override void OnInitialized()
	{
		_handler = Service.Register(ChildContent ?? new RenderFragment((x) => { }));
		_handler.SetComponentBaseParameters(this, PopoverClass, PopoverStyles, Open);
		base.OnInitialized();
	}

	protected override void OnParametersSet()
	{
		base.OnParametersSet();

		// henon: this change by PR #3776 caused problems on BSS (#4303)
		//// Only update the fragment if the popover is currently shown or will show
		//// This prevents unnecessary renders and popover handle locking
		//if (!_handler.ShowContent && !Open)
		//    return;

		_handler.UpdateFragment(ChildContent, this, PopoverClass, PopoverStyles, Open);
	}

	protected override async Task OnAfterRenderAsync(bool firstRender)
	{
		if (firstRender == true)
		{
			await _handler.Initialize();
			await Service.InitializeIfNeeded();
		}

		await base.OnAfterRenderAsync(firstRender);
	}

	[ExcludeFromCodeCoverage]
	public async ValueTask DisposeAsync()
	{
		try
		{
			await Service.Unregister(_handler);
		}
		catch (JSDisconnectedException) { }
		catch (TaskCanceledException) { }
	}
}
