using System;
using System.Windows.Input;
using CommunityToolkit.Diagnostics;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Wino.Core.Domain.Models.Calendar;
using Wino.Helpers;

namespace Wino.Calendar.Controls
{
    public class WinoCalendarView : Control
    {
        private const string PART_DayViewItemBorder = nameof(PART_DayViewItemBorder);
        private const string PART_CalendarView = nameof(PART_CalendarView);

        public static readonly DependencyProperty HighlightedDateRangeProperty = DependencyProperty.Register(nameof(HighlightedDateRange), typeof(DateRange), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnHighlightedDateRangeChanged)));
        public static readonly DependencyProperty VisibleDateBackgroundProperty = DependencyProperty.Register(nameof(VisibleDateBackground), typeof(Brush), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertiesChanged)));
        public static readonly DependencyProperty DateClickedCommandProperty = DependencyProperty.Register(nameof(DateClickedCommand), typeof(ICommand), typeof(WinoCalendarView), new PropertyMetadata(null));
        public static readonly DependencyProperty TodayBackgroundColorProperty = DependencyProperty.Register(nameof(TodayBackgroundColor), typeof(Color), typeof(WinoCalendarView), new PropertyMetadata(null));

        public Color TodayBackgroundColor
        {
            get { return (Color)GetValue(TodayBackgroundColorProperty); }
            set { SetValue(TodayBackgroundColorProperty, value); }
        }

        /// <summary>
        /// Gets or sets the command to execute when a date is picked.
        /// Unused.
        /// </summary>
        public ICommand DateClickedCommand
        {
            get { return (ICommand)GetValue(DateClickedCommandProperty); }
            set { SetValue(DateClickedCommandProperty, value); }
        }

        /// <summary>
        /// Gets or sets the highlighted range of dates.
        /// </summary>
        public DateRange HighlightedDateRange
        {
            get { return (DateRange)GetValue(HighlightedDateRangeProperty); }
            set { SetValue(HighlightedDateRangeProperty, value); }
        }

        public Brush VisibleDateBackground
        {
            get { return (Brush)GetValue(VisibleDateBackgroundProperty); }
            set { SetValue(VisibleDateBackgroundProperty, value); }
        }



        private CalendarView CalendarView;

        public WinoCalendarView()
        {
            DefaultStyleKey = typeof(WinoCalendarView);
        }

        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            CalendarView = GetTemplateChild(PART_CalendarView) as CalendarView;

            Guard.IsNotNull(CalendarView, nameof(CalendarView));

            CalendarView.SelectedDatesChanged -= InternalCalendarViewSelectionChanged;
            CalendarView.SelectedDatesChanged += InternalCalendarViewSelectionChanged;

            // TODO: Should come from settings.
            CalendarView.FirstDayOfWeek = Windows.Globalization.DayOfWeek.Monday;

            // Everytime display mode changes, update the visible date range backgrounds.
            // If users go back from year -> month -> day, we need to update the visible date range backgrounds.

            CalendarView.RegisterPropertyChangedCallback(CalendarView.DisplayModeProperty, (s, e) => UpdateVisibleDateRangeBackgrounds());
        }

        private void InternalCalendarViewSelectionChanged(CalendarView sender, CalendarViewSelectedDatesChangedEventArgs args)
        {
            if (args.AddedDates?.Count > 0)
            {
                var clickedDate = args.AddedDates[0].Date;
                SetInnerDisplayDate(clickedDate);

                var clickArgs = new CalendarViewDayClickedEventArgs(clickedDate);
                DateClickedCommand?.Execute(clickArgs);
            }

            // Reset selection, we don't show selected dates but react to them.
            CalendarView.SelectedDates.Clear();
        }

        private static void OnPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is WinoCalendarView control)
            {
                control.UpdateVisibleDateRangeBackgrounds();
            }
        }

        private void SetInnerDisplayDate(DateTime dateTime) => CalendarView?.SetDisplayDate(dateTime);

        // Changing selected dates will trigger the selection changed event.
        // It will behave like user clicked the date.
        public void GoToDay(DateTime dateTime) => CalendarView.SelectedDates.Add(dateTime);

        private static void OnHighlightedDateRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is WinoCalendarView control)
            {
                control.SetInnerDisplayDate(control.HighlightedDateRange.StartDate);
                control.UpdateVisibleDateRangeBackgrounds();
            }
        }

        public void UpdateVisibleDateRangeBackgrounds()
        {
            if (HighlightedDateRange == null || VisibleDateBackground == null || TodayBackgroundColor == null || CalendarView == null) return;

            var markDateCalendarDayItems = WinoVisualTreeHelper.FindDescendants<CalendarViewDayItem>(CalendarView);

            foreach (var calendarDayItem in markDateCalendarDayItems)
            {
                var border = WinoVisualTreeHelper.GetChildObject<Border>(calendarDayItem, PART_DayViewItemBorder);

                if (border == null) return;

                if (calendarDayItem.Date.Date == DateTime.Today.Date)
                {
                    border.Background = new SolidColorBrush(TodayBackgroundColor);
                }
                else if (calendarDayItem.Date.Date >= HighlightedDateRange.StartDate.Date && calendarDayItem.Date.Date < HighlightedDateRange.EndDate.Date)
                {
                    border.Background = VisibleDateBackground;
                }
                else
                {
                    border.Background = null;
                }
            }
        }
    }
}
