// 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 Connected.Utilities;
using Microsoft.AspNetCore.Components;

namespace Connected.Components;

public partial class PageContentNavigation : IAsyncDisposable
{
   private List<PageContentSection> _sections = new();
   private IScrollSpy _scrollSpy;

   [Inject] IScrollSpyFactory ScrollSpyFactory { get; set; }

   /// <summary>
   /// The displayed section within the MudPageContentNavigation
   /// </summary>
   public IEnumerable<PageContentSection> Sections => _sections.AsEnumerable();

   /// <summary>
   /// The currently active session. null if there is no section selected
   /// </summary>
   public PageContentSection ActiveSection => _sections.FirstOrDefault(x => x.IsActive == true);

   /// <summary>
   /// The text displayed about the section links. Defaults to "Conents"
   /// </summary>
   [Parameter] public string Headline { get; set; } = "Contents";

   /// <summary>
   /// The css selector used to identifify the HTML elements that should be observed for viewport changes
   /// </summary>
   [Parameter] public string SectionClassSelector { get; set; } = string.Empty;

   /// <summary>
   /// If there are mutliple levels, this can specified to make a mapping between a level class likw "second-level" and the level in the hierarchy
   /// </summary>
   [Parameter] public IDictionary<string, int> HierarchyMapper { get; set; } = new Dictionary<string, int>();

   /// <summary>
   /// If there are multiple levels, this property controls they visibility of them.
   /// </summary>
   [Parameter] public ContentNavigationExpandBehaviour ExpandBehaviour { get; set; } = ContentNavigationExpandBehaviour.Always;

   /// <summary>
   /// If this option is true the first added section will become active when there is no other indication of an active session. Default value is false  
   /// </summary>
   [Parameter] public bool ActivateFirstSectionAsDefault { get; set; } = false;

   private Task OnNavLinkClick(string id)
   {
      SelectActiveSection(id);
      return _scrollSpy.ScrollToSection(id);
   }

   private void ScrollSpy_ScrollSectionSectionCentered(object sender, ScrollSectionCenteredEventArgs e) =>
       SelectActiveSection(e.Id);

   private void SelectActiveSection(string id)
   {
      if (string.IsNullOrEmpty(id))
      {
         return;
      }

      var activelink = _sections.FirstOrDefault(x => x.Id == id);
      if (activelink == null)
      {
         return;
      }

      _sections.ToList().ForEach(item => item.Deactive());
      activelink.Activate();

      StateHasChanged();
   }

   private string GetNavLinkClass(PageContentSection section) => new CssBuilder("page-content-navigation-navlink")
      .AddClass("active", section.IsActive)
      .AddClass($"navigation-level-{section.Level}")
      .Build();

   private string GetPanelClass() => new CssBuilder("page-content-navigation").AddClass(AdditionalClassList).Build();

   /// <summary>
   /// Scrolls to a section based on the fragment of the uri. If there is no fragment, no scroll will occured
   /// </summary>
   /// <param name="uri">The uri containing the fragment to scroll</param>
   /// <returns>A task that completes when the viewport has scrolled</returns>
   public Task ScrollToSection(Uri uri) => _scrollSpy.ScrollToSection(uri);

   /// <summary>
   /// Add a section to the content navigation
   /// </summary>
   /// <param name="sectionName">name of the section will be displayed in the navigation</param>
   /// <param name="sectionId">id of the section. It will be appending to the current url, if the section becomes active</param>
   /// <param name="forceUpdate">If true, StateHasChanged is called, forcing a rerender of the component</param>
   public void AddSection(string sectionName, string sectionId, bool forceUpdate) => AddSection(new(sectionName, sectionId), forceUpdate);

   private Dictionary<PageContentSection, PageContentSection> _parentMapper = new();

   /// <summary>
   /// Add a section to the content navigation
   /// </summary>
   /// <param name="section">The section that needs to be added</param>
   /// <param name="forceUpdate">If true, StateHasChanged is called, forcing a rerender of the component</param>
   public void AddSection(PageContentSection section, bool forceUpdate)
   {
      _sections.Add(section);

      int diffRootLevel = 1_000_000;
      int counter = 0;
      foreach (var item in _sections.Where(x => x.Parent == null))
      {
         item.SetLevelStructure(counter, diffRootLevel);
         counter += diffRootLevel;
      }

      if (section.Id == _scrollSpy.CenteredSection)
      {
         section.Activate();
      }
      else if (_sections.Count == 1 && ActivateFirstSectionAsDefault == true)
      {
         section.Activate();
         _scrollSpy.SetSectionAsActive(section.Id).AndForget();
      }

      if (forceUpdate == true)
      {
         StateHasChanged();
      }
   }


   /// <summary>
   /// Rerender the component
   /// </summary>
   public void Update() => StateHasChanged();

   protected override void OnInitialized()
   {
      _scrollSpy = ScrollSpyFactory.Create();
   }


   protected override async Task OnAfterRenderAsync(bool firstRender)
   {
      if (firstRender)
      {

         _scrollSpy.ScrollSectionSectionCentered += ScrollSpy_ScrollSectionSectionCentered;

         if (string.IsNullOrEmpty(SectionClassSelector) == false)
         {
            await _scrollSpy.StartSpying(SectionClassSelector);
         }

         SelectActiveSection(_scrollSpy.CenteredSection);
      }
   }

   public ValueTask DisposeAsync()
   {
      if (_scrollSpy == null) { return ValueTask.CompletedTask; }

      _scrollSpy.ScrollSectionSectionCentered -= ScrollSpy_ScrollSectionSectionCentered;
      return _scrollSpy.DisposeAsync();
   }
}
