using System.Globalization;
using Connected.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace Connected.Components;

public partial class Collapse : UIComponent
{
	internal enum CollapseState
	{
		Entering, Entered, Exiting, Exited
	}

	internal double _height;
	private bool _expanded, _isRendered, _updateHeight;
	private ElementReference _wrapper;
	internal CollapseState _state = CollapseState.Exited;

	protected string Stylename =>
		new StyleBuilder()
		.AddStyle("max-height", MaxHeight.ToPx(), MaxHeight != null)
		.AddStyle("height", "auto", _state == CollapseState.Entered)
		.AddStyle("height", _height.ToPx(), _state is CollapseState.Entering or CollapseState.Exiting)
		.AddStyle("animation-duration", $"{CalculatedAnimationDuration.ToString("#.##", CultureInfo.InvariantCulture)}s", _state == CollapseState.Entering)
		.Build();

	protected string Classname =>
		new CssBuilder("collapse-container")
		.AddClass($"collapse-entering", _state == CollapseState.Entering)
		.AddClass($"collapse-entered", _state == CollapseState.Entered)
		.AddClass($"collapse-exiting", _state == CollapseState.Exiting)
		.AddClass(AdditionalClassList)
		.Build();

	/// <summary>
	/// If true, expands the panel, otherwise collapse it. Setting this prop enables control over the panel.
	/// </summary>
	[Parameter]
	public bool Expanded
	{
		get => _expanded;
		set
		{
			if (_expanded == value)
				return;
			_expanded = value;

			if (_isRendered)
			{
				_state = _expanded ? CollapseState.Entering : CollapseState.Exiting;
				_ = UpdateHeight();
				_updateHeight = true;
			}
			else if (_expanded)
			{
				_state = CollapseState.Entered;
			}

			_ = ExpandedChanged.InvokeAsync(_expanded);
		}
	}

	/// <summary>
	/// Explicitly sets the height for the Collapse element to override the css default.
	/// </summary>
	[Parameter] public int? MaxHeight { get; set; }

	/// <summary>
	/// Child content of component.
	/// </summary>
	[Parameter] public RenderFragment ChildContent { get; set; }

	[Parameter] public EventCallback OnAnimationEnd { get; set; }

	[Parameter] public EventCallback<bool> ExpandedChanged { get; set; }

	/// <summary>
	/// Modified Animation duration that scales with height parameter.
	/// Basic implementation for now but should be a math formula to allow it to scale between 0.1s and 1s for the effect to be consistently smooth.
	/// </summary>
	private double CalculatedAnimationDuration
	{
		get
		{
			if (MaxHeight != null)
			{
				if (MaxHeight <= 200) return 0.2;
				else if (MaxHeight <= 600) return 0.4;
				else if (MaxHeight <= 1400) return 0.6;
				return 1;
			}
			return Math.Min(_height / 800.0, 1);
		}
		set { }
	}

	internal async Task UpdateHeight()
	{
		try
		{
			_height = (await _wrapper.MudGetBoundingClientRectAsync())?.Height ?? 0;
		}
		catch (Exception ex) when (ex is JSDisconnectedException || ex is TaskCanceledException)
		{
			_height = 0;
		}

		if (MaxHeight != null && _height > MaxHeight)
		{
			_height = MaxHeight.Value;
		}
	}

	protected override async Task OnAfterRenderAsync(bool firstRender)
	{
		if (firstRender)
		{
			_isRendered = true;
			await UpdateHeight();
		}
		else if (_updateHeight && _state is CollapseState.Entering or CollapseState.Exiting)
		{
			_updateHeight = false;
			await UpdateHeight();
			StateHasChanged();
		}
		await base.OnAfterRenderAsync(firstRender);
	}

	public void AnimationEnd()
	{
		if (_state == CollapseState.Entering)
		{
			_state = CollapseState.Entered;
			StateHasChanged();
		}
		else if (_state == CollapseState.Exiting)
		{
			_state = CollapseState.Exited;
			StateHasChanged();
		}
		OnAnimationEnd.InvokeAsync(Expanded);
	}
}
