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

namespace Connected.Components;

public class DatePicker : DatePickerBase
{
	private DateTime? _selectedDate;

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

	/// <summary>
	/// The currently selected date (two-way bindable). If null, then nothing was selected.
	/// </summary>
	[Parameter]
	[Category(CategoryTypes.FormComponent.Data)]
	public DateTime? Date
	{
		get => _value;
		set => SetDateAsync(value, true).AndForget();
	}

	protected async Task SetDateAsync(DateTime? date, bool updateValue)
	{
		if (_value != date)
		{
			Modified = true;

			if (date is not null && IsDateDisabledFunc(date.Value.Date))
			{
				await SetTextAsync(null, false);
				return;
			}

			_value = date;
			if (updateValue)
				await SetTextAsync(Converter.Convert(_value), false);
			
			await DateChanged.InvokeAsync(_value);
			BeginValidate();
			FieldChanged(_value);
		}
	}

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

	protected override Task StringValueChanged(string value)
	{
		Modified = true;
		// Update the date property (without updating back the Value property)
		return SetDateAsync(Converter.ConvertBack(value), false);
	}

	protected override string GetDayClasses(int month, DateTime day)
	{
		var b = new CssBuilder("day");
		b.AddClass(AdditionalDateClassesFunc?.Invoke(day) ?? string.Empty);
		if (day < GetMonthStart(month) || day > GetMonthEnd(month))
			return b.AddClass("hidden").Build();
		if ((Date?.Date == day && _selectedDate == null) || _selectedDate?.Date == day)
			return b.AddClass("selected").AddClass($"theme-{Color.ToDescription()}").Build();
		if (day == DateTime.Today)
			return b.AddClass("current mud-button-outlined").AddClass($"button-outlined-{Color.ToDescription()} mud-{Color.ToDescription()}-text").Build();
		return b.Build();
	}

	protected override async void OnDayClicked(DateTime dateTime)
	{
		_selectedDate = dateTime;
		if (PickerActions == null || AutoClose || PickerVariant == PickerVariant.Static)
		{
			Submit();

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

	/// <summary>
	/// user clicked on a month
	/// </summary>
	/// <param name="month"></param>
	protected override void OnMonthSelected(DateTime month)
	{
		PickerMonth = month;
		var nextView = GetNextView();
		if (nextView == null)
		{
			_selectedDate = _selectedDate.HasValue ?
				//everything has to be set because a value could already defined -> fix values can be ignored as they are set in submit anyway
				new DateTime(month.Year, month.Month, _selectedDate.Value.Day, _selectedDate.Value.Hour, _selectedDate.Value.Minute, _selectedDate.Value.Second, _selectedDate.Value.Millisecond, _selectedDate.Value.Kind)
				//We can assume day here, as it was not set yet. If a fix value is set, it will be overriden in Submit
				: new DateTime(month.Year, month.Month, 1);
			SubmitAndClose();
		}
		else
		{
			CurrentView = (OpenTo)nextView;
		}
	}

	/// <summary>
	/// user clicked on a year
	/// </summary>
	/// <param name="year"></param>
	protected override void OnYearClicked(int year)
	{
		var current = GetMonthStart(0);
		PickerMonth = new DateTime(year, current.Month, 1);
		var nextView = GetNextView();
		if (nextView == null)
		{
			_selectedDate = _selectedDate.HasValue ?
				//everything has to be set because a value could already defined -> fix values can be ignored as they are set in submit anyway
				new DateTime(_selectedDate.Value.Year, _selectedDate.Value.Month, _selectedDate.Value.Day, _selectedDate.Value.Hour, _selectedDate.Value.Minute, _selectedDate.Value.Second, _selectedDate.Value.Millisecond, _selectedDate.Value.Kind)
				//We can assume month and day here, as they were not set yet
				: new DateTime(year, 1, 1);
			SubmitAndClose();
		}
		else
		{
			CurrentView = (OpenTo)nextView;
		}
	}

	protected override void OnOpened()
	{
		_selectedDate = null;

		base.OnOpened();
	}

	protected internal override async void Submit()
	{
		if (ReadOnly)
			return;
		if (_selectedDate == null)
			return;

		if (FixYear.HasValue || FixMonth.HasValue || FixDay.HasValue)
			_selectedDate = new DateTime(FixYear ?? _selectedDate.Value.Year,
				FixMonth ?? _selectedDate.Value.Month,
				FixDay ?? _selectedDate.Value.Day,
				_selectedDate.Value.Hour,
				_selectedDate.Value.Minute,
				_selectedDate.Value.Second,
				_selectedDate.Value.Millisecond);

		await SetDateAsync(_selectedDate, true);
		_selectedDate = null;
	}

	public override async void Clear(bool close = true)
	{
		_selectedDate = null;
		await SetDateAsync(null, true);

		if (AutoClose == true)
		{
			Close(false);
		}
	}

	protected override string GetTitleDateString()
	{
		return FormatTitleDate(_selectedDate ?? Date);
	}

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

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

	//To be completed on next PR
	protected internal override void HandleKeyDown(KeyboardEventArgs obj)
	{
		if (Disabled || ReadOnly)
			return;
		base.HandleKeyDown(obj);
		switch (obj.Key)
		{
			case "ArrowRight":
				if (IsOpen)
				{

				}
				break;
			case "ArrowLeft":
				if (IsOpen)
				{

				}
				break;
			case "ArrowUp":
				if (IsOpen == false && Editable == false)
				{
					IsOpen = true;
				}
				else if (obj.AltKey == true)
				{
					IsOpen = false;
				}
				else if (obj.ShiftKey == true)
				{

				}
				else
				{

				}
				break;
			case "ArrowDown":
				if (IsOpen == false && Editable == false)
				{
					IsOpen = true;
				}
				else if (obj.ShiftKey == true)
				{

				}
				else
				{

				}
				break;
			case "Escape":
				ReturnDateBackUp();
				break;
			case "Enter":
			case "NumpadEnter":
				if (!IsOpen)
				{
					Open();
				}
				else
				{
					Submit();
					Close();
					_inputReference?.SetText(Text);
				}
				break;
			case " ":
				if (!Editable)
				{
					if (!IsOpen)
					{
						Open();
					}
					else
					{
						Submit();
						Close();
						_inputReference?.SetText(Text);
					}
				}
				break;
		}
	}

	private void ReturnDateBackUp()
	{
		Close();
	}

	/// <summary>
	/// Scrolls to the date.
	/// </summary>
	public void GoToDate()
	{
		if (Date.HasValue)
		{
			PickerMonth = new DateTime(Date.Value.Year, Date.Value.Month, 1);
			ScrollToYear();
		}
	}

	/// <summary>
	/// Scrolls to the defined date.
	/// </summary>
	public async Task GoToDate(DateTime date, bool submitDate = true)
	{
		PickerMonth = new DateTime(date.Year, date.Month, 1);
		if (submitDate)
		{
			await SetDateAsync(date, true);
			ScrollToYear();
		}
	}
}
