using Connected.Annotations;
using Connected.Extensions;
using Connected.Utilities;
using Microsoft.AspNetCore.Components;

namespace Connected.Components;

public partial class DateRangePicker : DatePickerBase
{
   private DateTime? _firstDate = null, _secondDate;
   private DateRange _dateRange;
   private Range<string> _rangeText;

   protected override bool IsRange => true;

   public DateRangePicker()
   {
      DisplayMonths = 2;
      AdornmentAriaLabel = "Open Date Range Picker";
   }

   /// <summary>
   /// Fired when the DateFormat changes.
   /// </summary>
   [Parameter] public EventCallback<DateRange> DateRangeChanged { get; set; }

   /// <summary>
   /// The currently selected range (two-way bindable). If null, then nothing was selected.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Data)]
   public DateRange DateRange
   {
      get => _dateRange;
      set => SetDateRangeAsync(value, true).AndForget();
   }

   protected async Task SetDateRangeAsync(DateRange range, bool updateValue)
   {
      if (_dateRange != range)
      {
         var doesRangeContainDisabledDates = range?.Start != null && range?.End != null && Enumerable
             .Range(0, int.MaxValue)
             .Select(index => range.Start.Value.AddDays(index))
             .TakeWhile(date => date <= range.End.Value)
             .Any(date => IsDateDisabledFunc(date.Date));

         if (doesRangeContainDisabledDates)
         {
            _rangeText = null;
            await SetTextAsync(null, false);
            return;
         }

         _dateRange = range;
         _value = range?.End;

         if (updateValue)
         {
            if (_dateRange == null)
            {
               _rangeText = null;
               await SetTextAsync(null, false);
            }
            else
            {
               _rangeText = new Range<string>(
                   Converter.Convert(_dateRange.Start),
                   Converter.Convert(_dateRange.End));
               await SetTextAsync(_dateRange.ToString(Converter), false);
            }
         }

         await DateRangeChanged.InvokeAsync(_dateRange);
         BeginValidate();
      }
   }

   private Range<string> RangeText
   {
      get => _rangeText;
      set
      {
         if (_rangeText?.Equals(value) ?? value == null)
            return;

         Modified = true;
         _rangeText = value;
         SetDateRangeAsync(ParseDateRangeValue(value?.Start, value?.End), false).AndForget();
      }
   }

   private RangeInput<string> _rangeInput;

   /// <summary>
   /// Focuses the start date of MudDateRangePicker
   /// </summary>
   /// <returns></returns>
   public ValueTask FocusStartAsync() => _rangeInput.FocusStartAsync();

   /// <summary>
   /// Selects the start date of MudDateRangePicker
   /// </summary>
   /// <returns></returns>
   public ValueTask SelectStartAsync() => _rangeInput.SelectStartAsync();

   /// <summary>
   /// Selects the specified range of the start date text
   /// </summary>
   /// <param name="pos1">Start position of the selection</param>
   /// <param name="pos2">End position of the selection</param>
   /// <returns></returns>
   public ValueTask SelectRangeStartAsync(int pos1, int pos2) => _rangeInput.SelectRangeStartAsync(pos1, pos2);

   /// <summary>
   /// Focuses the end date of MudDateRangePicker
   /// </summary>
   /// <returns></returns>
   public ValueTask FocusEndAsync() => _rangeInput.FocusEndAsync();

   /// <summary>
   /// Selects the end date of MudDateRangePicker
   /// </summary>
   /// <returns></returns>
   public ValueTask SelectEndAsync() => _rangeInput.SelectEndAsync();

   /// <summary>
   /// Selects the specified range of the end date text
   /// </summary>
   /// <param name="pos1">Start position of the selection</param>
   /// <param name="pos2">End position of the selection</param>
   /// <returns></returns>
   public ValueTask SelectRangeEndAsync(int pos1, int pos2) => _rangeInput.SelectRangeEndAsync(pos1, pos2);

   protected override Task DateFormatChanged(string newFormat)
   {
      Modified = true;
      return SetTextAsync(_dateRange?.ToString(Converter), false);
   }

   protected override Task StringValueChanged(string value)
   {
      Modified = true;
      // Update the daterange property (without updating back the Value property)
      return SetDateRangeAsync(ParseDateRangeValue(value), false);
   }

   protected override bool HasValue(DateTime? value)
   {
      return null != value && value.HasValue;
   }

   private DateRange ParseDateRangeValue(string value)
   {
      return DateRange.TryParse(value, Converter, out var dateRange) ? dateRange : null;
   }

   private DateRange ParseDateRangeValue(string start, string end)
   {
      return DateRange.TryParse(start, end, Converter, out var dateRange) ? dateRange : null;
   }

   protected override void OnPickerClosed()
   {
      _firstDate = null;
      base.OnPickerClosed();
   }

   protected override string GetDayClasses(int month, DateTime day)
   {
      var b = new CssBuilder("day");
      if (day < GetMonthStart(month) || day > GetMonthEnd(month))
      {
         return b.AddClass("hidden").Build();
      }

      if ((_firstDate != null && _secondDate != null && _firstDate < day && _secondDate > day) ||
          (_firstDate == null && _dateRange != null && _dateRange.Start < day && _dateRange.End > day))
      {
         return b
             .AddClass("range")
             .AddClass("range-between")
             .Build();
      }

      if ((_firstDate != null && day == _firstDate) ||
          (_firstDate == null && _dateRange != null && _dateRange.Start == day && DateRange.End != day))
      {
         return b.AddClass("selected")
             .AddClass("range")
             .AddClass("range-start-selected")
             .AddClass("range-selection", _firstDate != null)
             .AddClass($"theme-{Color.ToDescription()}")
             .Build();
      }

      if ((_firstDate != null && _secondDate != null && day == _secondDate) ||
          (_firstDate == null && _dateRange != null && _dateRange.Start != day && _dateRange.End == day))
      {
         return b.AddClass("selected")
             .AddClass("range")
             .AddClass("range-end-selected")
             .AddClass($"theme-{Color.ToDescription()}")
             .Build();
      }

      if (_firstDate == null && _dateRange != null && _dateRange.Start == _dateRange.End && _dateRange.Start == day)
      {
         return b.AddClass("selected").AddClass($"theme-{Color.ToDescription()}").Build();
      }
      else if (_firstDate != null && day > _firstDate)
      {
         return b.AddClass("range")
             .AddClass("range-selection", _secondDate == null)
             .AddClass($"range-selection-{Color.ToDescription()}", _firstDate != null)
             .Build();
      }

      if (day == DateTime.Today)
      {
         return b.AddClass("current")
             .AddClass("range", _firstDate != null && day > _firstDate)
             .AddClass("range-selection", _firstDate != null && day > _firstDate)
             .AddClass($"range-selection-{Color.ToDescription()}", _firstDate != null && day > _firstDate)
             .AddClass($"{Color.ToDescription()}-text")
             .Build();
      }

      return b.Build();
   }

   protected override async void OnDayClicked(DateTime dateTime)
   {
      if (_firstDate == null || _firstDate > dateTime || _secondDate != null)
      {
         _secondDate = null;
         _firstDate = dateTime;
         return;
      }

      _secondDate = dateTime;
      if (PickerActions == null || AutoClose)
      {
         Submit();

         if (PickerVariant != PickerVariant.Static)
         {
            await Task.Delay(ClosingDelay);
            Close(false);
         }
      }
   }

   protected override void OnOpened()
   {
      _secondDate = null;
      base.OnOpened();
   }

   protected internal override async void Submit()
   {
      if (ReadOnly)
         return;
      if (_firstDate == null || _secondDate == null)
         return;

      await SetDateRangeAsync(new DateRange(_firstDate, _secondDate), true);

      _firstDate = null;
      _secondDate = null;
   }

   public override void Clear(bool close = true)
   {
      DateRange = null;
      _firstDate = _secondDate = null;
      base.Clear();
   }

   protected override string GetTitleDateString()
   {
      if (_firstDate != null)
         return $"{FormatTitleDate(_firstDate)} - {FormatTitleDate(_secondDate)}";

      return DateRange?.Start != null
          ? $"{FormatTitleDate(DateRange.Start)} - {FormatTitleDate(DateRange.End)}"
          : "";
   }

   protected override DateTime GetCalendarStartOfMonth()
   {
      var date = StartMonth ?? DateRange?.Start ?? DateTime.Today;
      return date.StartOfMonth(Culture);
   }

   protected override int GetCalendarYear(int year)
   {
      var date = DateRange?.Start ?? DateTime.Today;
      var diff = date.Year - year;
      var calenderYear = Culture.Calendar.GetYear(date);
      return calenderYear - diff;
   }
}
