using System.Globalization;
using Connected.Extensions;
using Connected.Interop;
using Connected.Services;
using Connected.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

namespace Connected.Components;

public partial class Tabs : UIComponent, IAsyncDisposable
{
	
	#region Event callbacks

	/// <summary>
	/// Fired when ActivePanelIndex changes.
	/// </summary>
	[Parameter]
	public EventCallback<int> ActivePanelIndexChanged { get; set; }

	#endregion

	#region Content placeholders

	private bool _isDisposed;

	private int _activePanelIndex = 0;
	private int _scrollIndex = 0;

	private bool _isRendered = false;
	private bool _prevButtonDisabled;
	private bool _nextButtonDisabled;
	private bool _showScrollButtons;
	private bool _disableSliderAnimation;
	private ElementReference _tabsContentSize;
	private double _size;
	private double _position;
	private double _toolbarContentSize;
	private double _allTabsSize;
	private double _scrollPosition;

	private IResizeObserver _resizeObserver;

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

	/// <summary>
	/// A readonly list of the current panels. Panels should be added or removed through the RenderTree use this collection to get informations about the current panels
	/// </summary>
	public IReadOnlyList<TabPanel> Panels { get; private set; }

	private List<TabPanel> _panels;

	/// <summary>
	/// A render fragment that is added before or after (based on the value of HeaderPosition) the tabs inside the header panel of the tab control
	/// </summary>
	[Parameter]
	public RenderFragment<Tabs> Header { get; set; }

	/// <summary>
	/// A render fragment that is added before or after (based on the value of HeaderPosition) inside each tab panel
	/// </summary>
	[Parameter]
	public RenderFragment<TabPanel> TabPanelHeader { get; set; }

	/// <summary>
	/// Additional content specified by Header is placed either before the tabs, after or not at all
	/// </summary>
	[Parameter]
	public TabHeaderPosition HeaderPosition { get; set; } = TabHeaderPosition.After;

	/// <summary>
	/// Additional content specified by Header is placed either before the tabs, after or not at all
	/// </summary>
	[Parameter]
	public TabHeaderPosition TabPanelHeaderPosition { get; set; } = TabHeaderPosition.After;

	/// <summary>
	/// Can be used in derived class to add a class to the main container. If not overwritten return an empty string
	/// </summary>
	protected virtual string InternalClassName { get; } = string.Empty;

	private string _prevIcon;

	private string _nextIcon;

	/// <summary>
	/// This fragment is placed between toolbar and panels. 
	/// It can be used to display additional content like an address line in a browser.
	/// The active tab will be the content of this RenderFragement
	/// </summary>
	[Parameter]
	public RenderFragment<TabPanel> PrePanelContent { get; set; }



	public TabPanel ActivePanel { get; private set; }

	/// <summary>
	/// The current active panel index. Also with Bidirectional Binding
	/// </summary>
	[Parameter]
	public int ActivePanelIndex
	{
		get => _activePanelIndex;
		set
		{
			if (_activePanelIndex != value)
			{
				_activePanelIndex = value;
				if (_isRendered)
					ActivePanel = _panels[_activePanelIndex];
				ActivePanelIndexChanged.InvokeAsync(value);
			}
		}
	}

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

	[Inject] private IResizeObserverFactory _resizeObserverFactory { get; set; }

	internal void AddPanel(TabPanel tabPanel)
	{
		_panels.Add(tabPanel);
		if (_panels.Count == 1)
			ActivePanel = tabPanel;

		StateHasChanged();
	}

	internal async Task SetPanelRef(ElementReference reference)
	{
		if (_isRendered == true && _resizeObserver.IsElementObserved(reference) == false)
		{
			await _resizeObserver.Observe(reference);
			Rerender();
			StateHasChanged();
		}
	}

	internal async Task RemovePanel(TabPanel tabPanel)
	{
		if (_isDisposed)
			return;

		var index = _panels.IndexOf(tabPanel);
		var newIndex = index;
		if (ActivePanelIndex == index && index == _panels.Count - 1)
		{
			newIndex = index > 0 ? index - 1 : 0;
			if (_panels.Count == 1)
			{
				ActivePanel = null;
			}
		}
		else if (_activePanelIndex > index)
		{
			_activePanelIndex--;
			await ActivePanelIndexChanged.InvokeAsync(_activePanelIndex);
		}

		if (index != newIndex)
		{
			ActivePanelIndex = newIndex;
		}

		_panels.Remove(tabPanel);
		await _resizeObserver.Unobserve(tabPanel.PanelRef);
		Rerender();
		StateHasChanged();
	}

	public void ActivatePanel(TabPanel panel, bool ignoreDisabledState = false)
	{
		ActivatePanel(panel, null, ignoreDisabledState);
	}

	public void ActivatePanel(int index, bool ignoreDisabledState = false)
	{
		var panel = _panels[index];
		ActivatePanel(panel, null, ignoreDisabledState);
	}

	public void ActivatePanel(object id, bool ignoreDisabledState = false)
	{
		var panel = _panels.Where((p) => Equals(p.ID, id)).FirstOrDefault();
		if (panel != null)
			ActivatePanel(panel, null, ignoreDisabledState);
	}

	private void ActivatePanel(TabPanel panel, MouseEventArgs ev, bool ignoreDisabledState = false)
	{
		if (!panel.Disabled || ignoreDisabledState)
		{
			ActivePanelIndex = _panels.IndexOf(panel);

			if (ev != null)
				ActivePanel.OnClick.InvokeAsync(ev);

			CenterScrollPositionAroundSelectedItem();
			SetSliderState();
			SetScrollabilityStates();
			StateHasChanged();
		}
	}

	#endregion

	#region Styling properties

	protected string TabsClassnames =>
		 new CssBuilder("tabs")
		 .AddClass(InternalClassName)
		 .Build();

	protected string ToolbarClassnames =>
		 new CssBuilder("tabs-toolbar")
		 .AddClass($"tabs-vertical", IsVerticalTabs())
		 .Build();

	protected string WrapperClassnames =>
		 new CssBuilder("tabs-toolbar-wrapper")
		 .AddClass($"tabs-vertical", IsVerticalTabs())
		 .Build();

	protected string WrapperScrollStyle =>
	new StyleBuilder()
		 .AddStyle("transform", $"translateY({(-1 * _scrollPosition).ToString(CultureInfo.InvariantCulture)}px)", IsVerticalTabs())
		 .Build();

	protected string PanelsClassnames =>
		 new CssBuilder("tabs-panels")
		 .AddClass($"tabs-vertical", IsVerticalTabs())
		 .AddClass(PanelClass)
		 .Build();

	protected string SliderClass =>
		 new CssBuilder("tab-slider")
		 .AddClass($"tab-slider-vertical", IsVerticalTabs())
		 .Build();

	protected string MaxHeightStyles =>
		 new StyleBuilder()
		 .Build();

	protected string SliderStyle => RightToLeft ?
		 new StyleBuilder()
		 .AddStyle("transition", _disableSliderAnimation ? "none" : "top .3s cubic-bezier(.64,.09,.08,1);", IsVerticalTabs())
		 .AddStyle("height", _size.ToPx(), IsVerticalTabs())
		 .AddStyle("top", _position.ToPx(), IsVerticalTabs())
		 .Build() : new StyleBuilder()
		 .AddStyle("transition", _disableSliderAnimation ? "none" : "top .3s cubic-bezier(.64,.09,.08,1);", IsVerticalTabs())
		 .AddStyle("height", _size.ToPx(), IsVerticalTabs())
		 .AddStyle("top", _position.ToPx(), IsVerticalTabs())
		 .Build();

	private bool IsVerticalTabs()
	{
		return Position is Position.Left or Position.Right or Position.Start or Position.End;
	}

	private Position ConvertPosition(Position position)
	{
		return position switch
		{
			Position.Start => RightToLeft ? Position.Right : Position.Left,
			Position.End => RightToLeft ? Position.Left : Position.Right,
			_ => position
		};
	}

	string GetTabClass(TabPanel panel)
	{
		var tabClass = new CssBuilder("tab")
		  .AddClass($"tab-active", when: () => panel == ActivePanel)
		  .AddClass($"disabled", panel.Disabled)
		  .AddClass($"ripple", !DisableRipple)
		  .AddClass(TabPanelClass)
		  .Build();

		return tabClass;
	}

	private Placement GetTooltipPlacement()
	{
		if (Position == Position.Right)
			return Placement.Left;
		else if (Position == Position.Left)
			return Placement.Right;
		else if (Position == Position.Bottom)
			return Placement.Top;
		else
			return Placement.Bottom;
	}

	string GetTabStyle(TabPanel panel)
	{
		var tabStyle = new StyleBuilder()
		.Build();

		return tabStyle;
	}

	/// <summary>
	/// Hides the active tab slider.
	/// </summary>
	[Parameter]
	public bool HideSlider { get; set; }

	/// <summary>
	/// The color of the component. It supports the theme colors.
	/// </summary>
	[Parameter]
	public ThemeColor Color { get; set; } = ThemeColor.Default;

	/// <summary>
	/// The color of the tab slider. It supports the theme colors.
	/// </summary>
	[Parameter]
	public ThemeColor SliderColor { get; set; } = ThemeColor.Inherit;

	/// <summary>
	/// The color of the icon. It supports the theme colors.
	/// </summary>
	[Parameter]
	public ThemeColor IconColor { get; set; } = ThemeColor.Inherit;

	/// <summary>
	/// The color of the next/prev icons. It supports the theme colors.
	/// </summary>
	[Parameter]
	public ThemeColor ScrollIconColor { get; set; } = ThemeColor.Inherit;

	/// <summary>
	/// If true, always display the scroll buttons even if the tabs are smaller than the required with, buttons will be disabled if there is nothing to scroll.
	/// </summary>
	[Parameter]
	public bool AlwaysShowScrollButtons { get; set; }

	/// <summary>
	/// Glyph to use for left pagination.
	/// </summary>
	[Parameter]
	public string PrevIcon { get; set; } = Icons.Filled.ChevronLeft;

	/// <summary>
	/// Glyph to use for right pagination.
	/// </summary>
	[Parameter]
	public string NextIcon { get; set; } = Icons.Filled.ChevronRight;

	/// <summary>
	/// Sets the position of the tabs itself.
	/// </summary>
	[Parameter]
	public Position Position { get; set; } = Position.Top;

	/// <summary>
	/// If true, will apply elevation, rounded, outlined effects to the whole tab component instead of just toolbar.
	/// </summary>
	[Parameter]
	public bool ApplyEffectsToContainer { get; set; }

	/// <summary>
	/// If true, disables ripple effect.
	/// </summary>
	[Parameter]
	public bool DisableRipple { get; set; }

	/// <summary>
	/// If true, disables slider animation
	/// </summary>
	[Parameter]
	public bool DisableSliderAnimation { get => _disableSliderAnimation; set => _disableSliderAnimation = value; }

	/// <summary>
	/// Custom class/classes for TabPanel
	/// </summary>
	[Parameter]
	public string TabPanelClass { get; set; }

	/// <summary>
	/// Custom class/classes for Selected Content Panel
	/// </summary>
	[Parameter]
	public string PanelClass { get; set; }

	/// <summary>
	/// If true, render all tabs and hide (display:none) every non-active.
	/// </summary>
	[Parameter]
	public bool KeepPanelsAlive { get; set; } = false;

	#endregion

	#region Lifecycle

	public Tabs()
	{
		_panels = new List<TabPanel>();
		Panels = _panels.AsReadOnly();
	}

	protected override void OnInitialized()
	{
		_resizeObserver = _resizeObserverFactory.Create();
		base.OnInitialized();
	}

	protected override void OnParametersSet()
	{
		if (_resizeObserver == null)
		{
			_resizeObserver = _resizeObserverFactory.Create();
		}

		Rerender();
	}

	protected override async Task OnAfterRenderAsync(bool firstRender)
	{
		if (firstRender)
		{
			var items = _panels.Select(x => x.PanelRef).ToList();
			items.Add(_tabsContentSize);

			if (_panels.Count > 0)
				ActivePanel = _panels[_activePanelIndex];

			await _resizeObserver.Observe(items);

			_resizeObserver.OnResized += OnResized;

			Rerender();
			StateHasChanged();

			_isRendered = true;
		}
	}

	public async ValueTask DisposeAsync()
	{
		if (_isDisposed == true)
			return;
		_isDisposed = true;
		_resizeObserver.OnResized -= OnResized;
		await _resizeObserver.DisposeAsync();
	}

	private void Rerender()
	{
		_nextIcon = RightToLeft ? PrevIcon : NextIcon;
		_prevIcon = RightToLeft ? NextIcon : PrevIcon;

		GetToolbarContentSize();
		GetAllTabsSize();
		SetScrollButtonVisibility();
		SetSliderState();
		SetScrollabilityStates();
	}

	private async void OnResized(IDictionary<ElementReference, BoundingClientRect> changes)
	{
		Rerender();
		await InvokeAsync(StateHasChanged);
	}

	private void SetSliderState()
	{
		if (ActivePanel == null) { return; }

		_position = GetLengthOfPanelItems(ActivePanel);
		_size = GetRelevantSize(ActivePanel.PanelRef);
	}

	private void GetToolbarContentSize() => _toolbarContentSize = GetRelevantSize(_tabsContentSize);

	private void GetAllTabsSize()
	{
		double totalTabsSize = 0;

		foreach (var panel in _panels)
		{
			totalTabsSize += GetRelevantSize(panel.PanelRef);
		}

		_allTabsSize = totalTabsSize;
	}

	private double GetRelevantSize(ElementReference reference) => Position switch
	{
		Position.Top or Position.Bottom => _resizeObserver.GetWidth(reference),
		_ => _resizeObserver.GetHeight(reference)
	};

	private double GetLengthOfPanelItems(TabPanel panel, bool inclusive = false)
	{
		var value = 0.0;
		foreach (var item in _panels)
		{
			if (item == panel)
			{
				if (inclusive)
				{
					value += GetRelevantSize(item.PanelRef);
				}

				break;
			}

			value += GetRelevantSize(item.PanelRef);
		}

		return value;
	}

	private double GetPanelLength(TabPanel panel) => panel == null ? 0.0 : GetRelevantSize(panel.PanelRef);

	private void SetScrollButtonVisibility()
	{
		_showScrollButtons = AlwaysShowScrollButtons || _allTabsSize > _toolbarContentSize;
	}

	private void ScrollPrev()
	{
		_scrollIndex = Math.Max(_scrollIndex - GetVisiblePanels(), 0);
		ScrollToItem(_panels[_scrollIndex]);
		SetScrollabilityStates();
	}

	private void ScrollNext()
	{
		_scrollIndex = Math.Min(_scrollIndex + GetVisiblePanels(), _panels.Count - 1);
		ScrollToItem(_panels[_scrollIndex]);
		SetScrollabilityStates();
	}

	/// <summary>
	/// Calculates the amount of panels that are completely visible inside the toolbar content area. Panels that are just partially visible are not considered here!
	/// </summary>
	/// <returns>The amount of panels visible inside the toolbar area. CAUTION: Might return 0!</returns>
	private int GetVisiblePanels()
	{
		var x = 0D;
		var count = 0;

		var toolbarContentSize = GetRelevantSize(_tabsContentSize);

		foreach (var panel in _panels)
		{
			x += GetRelevantSize(panel.PanelRef);

			if (x < toolbarContentSize)
			{
				count++;
			}
			else
			{
				break;
			}
		}

		return count;
	}

	private void ScrollToItem(TabPanel panel)
	{
		var position = GetLengthOfPanelItems(panel);
		_scrollPosition = RightToLeft ? -position : position;
	}

	private bool IsAfterLastPanelIndex(int index) => index >= _panels.Count;
	private bool IsBeforeFirstPanelIndex(int index) => index < 0;

	private void CenterScrollPositionAroundSelectedItem()
	{
		TabPanel panelToStart = ActivePanel;
		var length = GetPanelLength(panelToStart);
		if (length >= _toolbarContentSize)
		{
			ScrollToItem(panelToStart);
			return;
		}

		var indexCorrection = 1;
		while (true)
		{
			var panelAfterIndex = _activePanelIndex + indexCorrection;
			if (!IsAfterLastPanelIndex(panelAfterIndex))
			{
				length += GetPanelLength(_panels[panelAfterIndex]);
			}

			if (length >= _toolbarContentSize)
			{
				_scrollIndex = _panels.IndexOf(panelToStart);
				ScrollToItem(panelToStart);
				break;
			}

			length = _toolbarContentSize - length;

			var panelBeforeindex = _activePanelIndex - indexCorrection;
			if (!IsBeforeFirstPanelIndex(panelBeforeindex))
			{
				length -= GetPanelLength(_panels[panelBeforeindex]);
			}
			else
			{
				break;
			}

			if (length < 0)
			{
				_scrollIndex = _panels.IndexOf(panelToStart);
				ScrollToItem(panelToStart);
				break;
			}

			length = _toolbarContentSize - length;
			panelToStart = _panels[_activePanelIndex - indexCorrection];

			indexCorrection++;
		}

		_scrollIndex = _panels.IndexOf(panelToStart);
		ScrollToItem(panelToStart);

		SetScrollabilityStates();
	}

	private void SetScrollabilityStates()
	{
		var isEnoughSpace = _allTabsSize <= _toolbarContentSize;

		if (isEnoughSpace)
		{
			_nextButtonDisabled = true;
			_prevButtonDisabled = true;
		}
		else
		{
			// Disable next button if the last panel is completely visible
			_nextButtonDisabled = Math.Abs(_scrollPosition) >= GetLengthOfPanelItems(_panels.Last(), true) - _toolbarContentSize;
			_prevButtonDisabled = _scrollIndex == 0;
		}
	}

	#endregion

}