// Copyright (c) MudBlazor 2021
// MudBlazor licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

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

namespace Connected.Components;

public partial class Pagination : UIComponent
{
   #region Css Classes

   private string Classname =>
      new CssBuilder("pagination")
         .AddClass($"pagination-{Variant.ToDescription()}")
         .AddClass($"pagination-{Size.ToDescription()}")
         .AddClass("pagination-disable-elevation", DisableElevation)
         .AddClass("pagination-rtl", RightToLeft)
         .AddClass(AdditionalClassList)
         .Build();

   private string ItemClassname =>
      new CssBuilder("pagination-item")
         .AddClass("pagination-item-rectangular", Rectangular)
         .Build();

   private string SelectedItemClassname =>
      new CssBuilder(ItemClassname)
         .AddClass("pagination-item-selected")
         .Build();

   #endregion

   #region Parameter

   private int _count = 1;

   /// <summary>
   /// The number of pages.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Behavior)]
   public int Count
   {
      get => _count;
      set
      {
         _count = Math.Max(1, value);
         Selected = Math.Min(Selected, _count);
      }
   }

   private int _boundaryCount = 2;

   /// <summary>
   /// The number of items at the start and end of the pagination.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Appearance)]
   public int BoundaryCount
   {
      get => _boundaryCount;
      set
      {
         _boundaryCount = Math.Max(1, value);
      }
   }

   private int _middleCount = 3;

   /// <summary>
   /// The number of items in the middle of the pagination.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Appearance)]
   public int MiddleCount
   {
      get => _middleCount;
      set
      {
         _middleCount = Math.Max(1, value);
      }
   }

   private bool _selectedFirstSet;
   private int _selected = 1;

   /// <summary>
   /// The selected page number.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Behavior)]
   public int Selected
   {
      get => _selected;
      set
      {
         if (_selected == value)
            return;

         //this is required because _selected will stay 1 when Count is not yet set
         if (!_selectedFirstSet)
         {
            _selected = value;
            _selectedFirstSet = true;
         }
         else
            _selected = Math.Max(1, Math.Min(value, Count));

         SelectedChanged.InvokeAsync(_selected);
      }
   }

   /// <summary>
   /// The variant to use.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Appearance)]
   public Variant Variant { get; set; } = Variant.Text;

   /// <summary>
   /// The color of the component. It supports the theme colors.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Appearance)]
   public ThemeColor Color { get; set; } = ThemeColor.Primary;

   /// <summary>
   /// If true, the pagination buttons are displayed rectangular.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Appearance)]
   public bool Rectangular { get; set; }

   /// <summary>
   /// The size of the component..
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Appearance)]
   public Size Size { get; set; } = Size.Medium;

   /// <summary>
   /// If true, no drop-shadow will be used.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Appearance)]
   public bool DisableElevation { get; set; }

   /// <summary>
   /// If true, the pagination will be disabled.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Behavior)]
   public bool Disabled { get; set; }

   /// <summary>
   /// If true, the navigate-to-first-page button is shown.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Behavior)]
   public bool ShowFirstButton { get; set; }

   /// <summary>
   /// If true, the navigate-to-last-page button is shown.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Behavior)]
   public bool ShowLastButton { get; set; }

   /// <summary>
   /// If true, the navigate-to-previous-page button is shown.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Behavior)]
   public bool ShowPreviousButton { get; set; } = true;

   /// <summary>
   /// If true, the navigate-to-next-page button is shown.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Behavior)]
   public bool ShowNextButton { get; set; } = true;

   /// <summary>
   /// Invokes the callback when a control button is clicked.
   /// </summary>
   [Parameter]
   public EventCallback<Page> ControlButtonClicked { get; set; }

   /// <summary>
   /// Invokes the callback when selected page changes.
   /// </summary>
   [Parameter]
   public EventCallback<int> SelectedChanged { get; set; }

   /// <summary>
   /// Custom first icon.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Appearance)]
   public string FirstIcon { get; set; } = Icons.Material.Filled.FirstPage;

   /// <summary>
   /// Custom before icon.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Appearance)]
   public string BeforeIcon { get; set; } = Icons.Material.Filled.NavigateBefore;

   /// <summary>
   /// Custom next icon.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Appearance)]
   public string NextIcon { get; set; } = Icons.Material.Filled.NavigateNext;

   /// <summary>
   /// Custom last icon.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.Pagination.Appearance)]
   public string LastIcon { get; set; } = Icons.Material.Filled.LastPage;

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

   #endregion

   #region Methods

   /*generates an array representing the pagination numbers, e.g. for Count==11, MiddleCount==3, BoundaryCount==1,
	 Selected==6 the output will be the int array [1, 2, -1, 5, 6, 7, -1, 10, 11]
	 -1 is displayed as "..." in the ui*/
   private IEnumerable<int> GeneratePagination()
   {
      //return array {1, ..., Count} if Count is small 
      if (Count <= 4 || Count <= 2 * BoundaryCount + MiddleCount + 2)
         return Enumerable.Range(1, Count).ToArray();

      var length = 2 * BoundaryCount + MiddleCount + 2;
      var pages = new int[length];

      //set start boundary items, e.g. if BoundaryCount == 3 => [1, 2, 3, ...]
      for (var i = 0; i < BoundaryCount; i++)
      {
         pages[i] = i + 1;
      }

      //set end boundary items, e.g. if BoundaryCount == 3 and Count == 11 => [..., 9, 10, 11]
      for (var i = 0; i < BoundaryCount; i++)
      {
         pages[length - i - 1] = Count - i;
      }

      //calculate start value for middle items
      var startValue = 0;
      if (Selected <= BoundaryCount + MiddleCount / 2 + 1)
         startValue = BoundaryCount + 2;
      else if (Selected >= Count - BoundaryCount - MiddleCount / 2)
         startValue = Count - BoundaryCount - MiddleCount;
      else
         startValue = Selected - MiddleCount / 2;

      //set middle items, e.g. if MiddleCount == 3 and Selected == 5 and Count == 11 => [..., 4, 5, 6, ...] 
      for (var i = 0; i < MiddleCount; i++)
      {
         pages[BoundaryCount + 1 + i] = startValue + i;
      }

      //set start delimiter "..." when selected page is far enough to the end, dots are represented as -1
      pages[BoundaryCount] = (BoundaryCount + MiddleCount / 2 + 1 < Selected) ? -1 : BoundaryCount + 1;

      //set end delimiter "..." when selected page is far enough to the start, dots are represented as -1
      pages[length - BoundaryCount - 1] = (Count - BoundaryCount - MiddleCount / 2 > Selected) ? -1 : Count - BoundaryCount;

      //remove ellipsis if difference is small enough, e.g convert [..., 5 , -1 , 7, ...] to [..., 5, 6, 7, ...]
      for (var i = 0; i < length - 2; i++)
      {
         if (pages[i] + 2 == pages[i + 2])
            pages[i + 1] = pages[i] + 1;
      }

      return pages;
   }

   //triggered when the user clicks on a control button, e.g. the navigate-to-next-page-button
   private void OnClickControlButton(Page page)
   {
      ControlButtonClicked.InvokeAsync(page);
      NavigateTo(page);
   }

   //Last line cannot be tested because Page enum has 4 items
   /// <summary>
   /// Navigates to the specified page.
   /// </summary>
   /// <param name="page">The target page. page=Page.Next navigates to the next page.</param>
   [ExcludeFromCodeCoverage]
   public void NavigateTo(Page page)
   {
      Selected = page switch
      {
         Page.First => 1,
         Page.Last => Math.Max(1, Count),
         Page.Next => Math.Min(Selected + 1, Count),
         Page.Previous => Math.Max(1, Selected - 1),
         _ => Selected
      };
   }

   /// <summary>
   /// Navigates to the specified page.
   /// </summary>
   /// <param name="pageIndex"></param>The target page. pageIndex=2 navigates to the 3. page.
   public void NavigateTo(int pageIndex)
   {
      Selected = pageIndex + 1;
   }

   #endregion
}
