using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Radzen.Blazor.Rendering;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;

namespace Radzen.Blazor
{
    /// <summary>
    /// A scheduler component for displaying and managing calendar appointments in multiple view types (day, week, month, year).
    /// RadzenScheduler provides a rich calendar interface with drag-and-drop, inline editing, recurring events, and customizable views.
    /// Displays time-based events in various calendar views, ideal for appointment booking, event calendars, resource scheduling, or any time-based data visualization.
    /// Features multiple views (Day, Week, Month, Year Planner, Year Timeline), drag &amp; drop to move appointments between time slots, resize to adjust appointment duration by dragging edges,
    /// inline editing to create and edit appointments directly in the calendar, tooltips for quick info on hover, customizable appointment templates,
    /// support for all-day and multi-day events, and timezone-aware appointments.
    /// Define data properties using StartProperty, EndProperty, and TextProperty. Add view components (RadzenDayView, RadzenWeekView, RadzenMonthView) as child content.
    /// </summary>
    /// <typeparam name="TItem">The type of appointment data items. Must have DateTime properties for start/end times and a string property for text.</typeparam>
    /// <example>
    /// Basic scheduler with month view:
    /// <code>
    /// &lt;RadzenScheduler Data=@appointments TItem="Appointment" StartProperty="Start" EndProperty="End" TextProperty="Title"&gt;
    ///     &lt;RadzenMonthView /&gt;
    ///     &lt;RadzenWeekView /&gt;
    ///     &lt;RadzenDayView /&gt;
    /// &lt;/RadzenScheduler&gt;
    /// @code {
    ///     class Appointment
    ///     {
    ///         public DateTime Start { get; set; }
    ///         public DateTime End { get; set; }
    ///         public string Title { get; set; }
    ///     }
    ///     List&lt;Appointment&gt; appointments = new();
    /// }
    /// </code>
    /// Scheduler with editing and custom template:
    /// <code>
    /// &lt;RadzenScheduler Data=@appointments TItem="Appointment" 
    ///                  StartProperty="Start" EndProperty="End" TextProperty="Title"
    ///                  SlotSelect=@OnSlotSelect AppointmentSelect=@OnAppointmentSelect&gt;
    ///     &lt;Template Context="appointment"&gt;
    ///         &lt;strong&gt;@appointment.Title&lt;/strong&gt;
    ///         &lt;div&gt;@appointment.Description&lt;/div&gt;
    ///     &lt;/Template&gt;
    ///     &lt;RadzenWeekView /&gt;
    /// &lt;/RadzenScheduler&gt;
    /// </code>
    /// </example>
    [UnconditionalSuppressMessage(TrimMessages.Trimming, TrimMessages.IL2026, Justification = TrimMessages.DataTypePreserved)]
    public partial class RadzenScheduler<
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] TItem> : RadzenComponent, IScheduler
    {
        /// <summary>
        /// Gets or sets the child content of the scheduler. Use to specify what views to render.
        /// </summary>
        /// <value>The child content.</value>
        [Parameter]
        public RenderFragment? ChildContent { get; set; }

        /// <summary>
        /// Gets or sets the template used to render appointments.
        /// </summary>
        /// <example>
        /// <code>
        /// &lt;RadzenScheduler Data="@data" TItem="DataItem" StartProperty="Start" EndProperty="End" TextProperty="Text"&gt;
        ///    &lt;Template Context="data"&gt;
        ///       &lt;strong&gt;@data.Text&lt;/strong&gt;
        ///    &lt;/Template&gt;
        ///    &lt;ChildContent&gt;
        ///       &lt;RadzenMonthView /&gt;
        ///     &lt;/ChildContent&gt;
        /// &lt;/RadzenScheduler&gt;
        /// </code>
        /// </example>
        /// <value>The template.</value>
        [Parameter]
        public RenderFragment<TItem>? Template { get; set; }
        
        /// <summary>
        /// Gets or sets the additional content to be rendered in place of the default navigation buttons in the scheduler.
        /// This property allows for complete customization of the navigation controls, replacing the native date navigation buttons (such as year, month, and day) with user-defined content or buttons.
        /// Use this to add custom controls or interactive elements that better suit your application's requirements.
        /// This requires that the <c>ShowHeader</c> parameter to be set to true (enabled by default).
        /// </summary>
        /// <value>The custom navigation template to replace default navigation buttons.</value>
        [Parameter]
        public RenderFragment? NavigationTemplate { get; set; }


        /// <summary>
        /// Gets or sets the data of RadzenScheduler. It will display an appointment for every item of the collection which is within the current view date range.
        /// </summary>
        /// <value>The data.</value>
        [Parameter]
        public IEnumerable<TItem>? Data { get; set; }

        /// <summary>
        /// Specifies the property of <typeparamref name="TItem" /> which will set <see cref="AppointmentData.Start" />.
        /// </summary>
        /// <value>The name of the property. Must be a <c>DateTime</c> property.</value>
        [Parameter]
        public string? StartProperty { get; set; }

        /// <summary>
        /// Specifies the property of <typeparamref name="TItem" /> which will set <see cref="AppointmentData.End" />.
        /// </summary>
        /// <value>The name of the property. Must be a <c>DateTime</c> property.</value>
        [Parameter]
        public string? EndProperty { get; set; }

        private int selectedIndex { get; set; }

        /// <summary>
        /// Specifies the initially selected view.
        /// </summary>
        /// <value>The index of the selected.</value>
        [Parameter]
        public int SelectedIndex { get; set; }

        private string? todayText;

        /// <summary>
        /// Gets or sets the text of the today button. Set to <c>Today</c> by default.
        /// </summary>
        /// <value>The today text.</value>
        [Parameter]
        public string TodayText { get => todayText ?? Localize(nameof(RadzenStrings.Scheduler_TodayText)); set => todayText = value; }

        private string? nextText;

        /// <summary>
        /// Gets or sets the text of the next button. Set to <c>Next</c> by default.
        /// </summary>
        /// <value>The next text.</value>
        [Parameter]
        public string NextText { get => nextText ?? Localize(nameof(RadzenStrings.Scheduler_NextText)); set => nextText = value; }

        private string? prevText;

        /// <summary>
        /// Gets or sets the text of the previous button. Set to <c>Previous</c> by default.
        /// </summary>
        /// <value>The previous text.</value>
        [Parameter]
        public string PrevText { get => prevText ?? Localize(nameof(RadzenStrings.Scheduler_PrevText)); set => prevText = value; }

        /// <summary>
        /// Gets or sets the initial date displayed by the selected view. Set to <c>DateTime.Today</c> by default.
        /// </summary>
        /// <value>The date.</value>
        [Parameter]
        public DateTime Date { get; set; } = DateTime.Today;

        /// <summary>
        /// Gets or sets the current date displayed by the selected view. Initially set to <see cref="Date" />. Changes during navigation.
        /// </summary>
        /// <value>The current date.</value>
        public DateTime CurrentDate { get; set; }

        /// <summary>
        /// Specifies the property of <typeparamref name="TItem" /> which will set <see cref="AppointmentData.Text" />.
        /// </summary>
        /// <value>The name of the property. Must be a <c>string</c> property.</value>
        [Parameter]
        public string? TextProperty { get; set; }

        /// <summary>
        /// Specifies whether to Show or Hide the Scheduler Header. Defaults to true />.
        /// </summary>
        /// <value>Show / hide header</value>
        [Parameter]
        public bool ShowHeader { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether the previous and next navigation buttons are visible. Set to <c>true</c> by default.
        /// </summary>
        /// <value><c>true</c> if the navigation buttons are visible; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool ShowNavigationButtons { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether the today button is visible. Set to <c>true</c> by default.
        /// </summary>
        /// <value><c>true</c> if the today button is visible; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool ShowTodayButton { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether the date title is visible. Set to <c>true</c> by default.
        /// </summary>
        /// <value><c>true</c> if the date title is visible; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool ShowDateTitle { get; set; } = true;

        /// <summary>
        /// A callback that will be invoked when the user clicks a slot in the current view. Commonly used to add new appointments.
        /// </summary>
        /// <example>
        /// <code>
        /// &lt;RadzenScheduler Data=@appointments SlotSelect=@OnSlotSelect&gt;
        /// &lt;/RadzenScheduler&gt;
        /// @code {
        ///  void OnSlotSelect(SchedulerSlotSelectEventArgs args)
        ///  {
        ///  }
        /// }
        /// </code>
        /// </example>
        [Parameter]
        public EventCallback<SchedulerSlotSelectEventArgs> SlotSelect { get; set; }

        /// <summary>
        /// A callback that will be invoked when the user clicks the Today button.
        /// </summary>
        /// <example>
        /// <code>
        /// &lt;RadzenScheduler Data=@appointments TodaySelect=@OnTodaySelect&gt;
        /// &lt;/RadzenScheduler&gt;
        /// @code {
        /// void OnTodaySelect(SchedulerTodaySelectEventArgs args)
        /// {
        ///     args.Today = DateTime.Today.AddDays(1);
        /// }
        /// }
        /// </code>
        /// </example>
        [Parameter]
        public EventCallback<SchedulerTodaySelectEventArgs> TodaySelect { get; set; }

        /// <summary>
        /// A callback that will be invoked when the user clicks a month header button.
        /// </summary>
        /// <example>
        /// <code>
        /// &lt;RadzenScheduler Data=@appointments MonthSelect=@OnMonthSelect&gt;
        /// &lt;/RadzenScheduler&gt;
        /// @code {
        /// void OnMonthSelect(SchedulerMonthSelectEventArgs args)
        /// {
        ///     var selectedMonth = args.MonthStart.Month;
        /// }
        /// }
        /// </code>
        /// </example>
        [Parameter]
        public EventCallback<SchedulerMonthSelectEventArgs> MonthSelect { get; set; }

        /// <summary>
        /// A callback that will be invoked when the user clicks a day header button or the day number in a MonthView.
        /// </summary>
        /// <example>
        /// <code>
        /// &lt;RadzenScheduler Data=@appointments DaySelect=@OnDaySelect&gt;
        /// &lt;/RadzenScheduler&gt;
        /// @code {
        /// void OnDaySelect(SchedulerDaySelectEventArgs args)
        /// {
        ///     var selectedDay = args.Day;
        /// }
        /// }
        /// </code>
        /// </example>
        [Parameter]
        public EventCallback<SchedulerDaySelectEventArgs> DaySelect { get; set; }

        /// <summary>
        /// A callback that will be invoked when the user clicks an appointment in the current view. Commonly used to edit existing appointments.
        /// </summary>
        /// <example>
        /// <code>
        /// &lt;RadzenScheduler Data=@appointments AppointmentSelect=@OnAppointmentSelect&gt;
        /// &lt;/RadzenScheduler&gt;
        /// @code {
        ///  void OnAppointmentSelect(SchedulerAppointmentSelectEventArgs&lt;TItem&gt; args)
        ///  {
        ///  }
        /// }
        /// </code>
        /// </example>
        [Parameter]
        public EventCallback<SchedulerAppointmentSelectEventArgs<TItem>> AppointmentSelect { get; set; }

        /// <summary>
        /// A callback that will be invoked when the user moves the mouse over an appointment in the current view.
        /// </summary>
        [Parameter]
        public EventCallback<SchedulerAppointmentMouseEventArgs<TItem>> AppointmentMouseEnter { get; set; }

        /// <summary>
        /// A callback that will be invoked when the user moves the mouse out of an appointment in the current view.
        /// </summary>
        [Parameter]
        public EventCallback<SchedulerAppointmentMouseEventArgs<TItem>> AppointmentMouseLeave { get; set; }

        /// <summary>
        /// A callback that will be invoked when the user clicks the more text in the current view. Commonly used to view additional appointments.
        /// Invoke the <see cref="SchedulerMoreSelectEventArgs.PreventDefault"/> method to prevent the default action (showing the additional appointments).
        /// </summary>
        /// <example>
        /// <code>
        /// &lt;RadzenScheduler Data=@appointments MoreSelect=@OnMoreSelect&gt;
        /// &lt;/RadzenScheduler&gt;
        /// @code {
        ///  void OnMoreSelect(SchedulerMoreSelectEventArgs args)
        ///  {
        ///     args.PreventDefault();
        ///  }
        /// }
        /// </code>
        /// </example>
        [Parameter]
        public EventCallback<SchedulerMoreSelectEventArgs> MoreSelect { get; set; }

        /// <summary>
        /// An action that will be invoked when the current view renders an appointment. Never call <c>StateHasChanged</c> when handling AppointmentRender.
        /// </summary>
        /// <example>
        /// <code>
        /// &lt;RadzenScheduler Data=@appointments AppointmentRender=@OnAppointmentRendert&gt;
        /// &lt;/RadzenScheduler&gt;
        /// @code {
        ///   void OnAppintmentRender(SchedulerAppointmentRenderEventArgs&lt;TItem&gt; args)
        ///   {
        ///     if (args.Data.Text == "Birthday")
        ///     {
        ///        args.Attributes["style"] = "color: red;"
        ///     }
        ///.  }
        /// }
        /// </code>
        /// </example>
        [Parameter]
        public Action<SchedulerAppointmentRenderEventArgs<TItem>>? AppointmentRender { get; set; }

        /// <summary>
        /// An action that will be invoked when the current view renders an slot. Never call <c>StateHasChanged</c> when handling SlotRender.
        /// </summary>
        /// <example>
        /// <code>
        /// &lt;RadzenScheduler Data=@appointments SlotRender=@OnSlotRender&gt;
        /// &lt;/RadzenScheduler&gt;
        /// @code {
        ///   void OnSlotRender(SchedulerSlotRenderEventArgs args)
        ///   {
        ///     if (args.View.Text == "Month" &amp;&amp; args.Start.Date == DateTime.Today)
        ///     {
        ///        args.Attributes["style"] = "background: red;";
        ///     }
        ///   }
        /// }
        /// </code>
        /// </example>
        [Parameter]
        public Action<SchedulerSlotRenderEventArgs>? SlotRender { get; set; }

        /// <summary>
        /// A callback that will be invoked when the scheduler needs data for the current view. Commonly used to filter the
        /// data assigned to <see cref="Data" />.
        /// </summary>
        [Parameter]
        public EventCallback<SchedulerLoadDataEventArgs> LoadData { get; set; }

        /// <summary>
        /// A callback that will be invoked when an appointment is being dragged and then dropped on a different slot.
        /// Commonly used to change it to a different timeslot.
        /// </summary>
        /// <example>
        /// <code>
        /// &lt;RadzenScheduler Data=@appointments AppointmentMove=@OnAppointmentMove&gt;
        /// &lt;/RadzenScheduler&gt;
        /// @code {
        ///   async Task OnAppointmentMove(SchedulerAppointmentMoveEventArgs moved)
        ///   {
        ///     var draggedAppointment = appointments.SingleOrDefault(x => x == (Appointment)moved.Appointment.Data);
        ///     if (draggedAppointment != null)
        ///     {
        ///         draggedAppointment.Start = draggedAppointment.Start + moved.TimeSpan;
        ///         draggedAppointment.End = draggedAppointment.End + moved.TimeSpan;
        ///         await scheduler.Reload();
        ///     }
        ///   }
        /// }
        /// </code>
        /// </example>
        /// <value></value>
        [Parameter]
        public EventCallback<SchedulerAppointmentMoveEventArgs> AppointmentMove { get; set; }

        IList<ISchedulerView> Views { get; set; } = new List<ISchedulerView>();

        /// <summary>
        /// Gets the SelectedView.
        /// </summary>
        public ISchedulerView? SelectedView
        {
            get
            {
                return Views.ElementAtOrDefault(selectedIndex);
            }
        }

        /// <inheritdoc />
        public IDictionary<string, object> GetAppointmentAttributes(AppointmentData item)
        {
            ArgumentNullException.ThrowIfNull(item);

            var appointmentData = item.Data is TItem typedData ? typedData : default!;
            var args = new SchedulerAppointmentRenderEventArgs<TItem> { Data = appointmentData, Start = item.Start, End = item.End };

            AppointmentRender?.Invoke(args);

            return args.Attributes;
        }

        /// <inheritdoc />
        public IDictionary<string, object> GetSlotAttributes(DateTime start, DateTime end, Func<IEnumerable<AppointmentData>> getAppointments)
        {
            var args = new SchedulerSlotRenderEventArgs { Start = start, End = end, getAppointments = getAppointments, View = SelectedView };

            SlotRender?.Invoke(args);

            return args.Attributes;
        }

        /// <inheritdoc />
        public RenderFragment RenderAppointment(AppointmentData item)
        {
            ArgumentNullException.ThrowIfNull(item);

            if (Template != null)
            {
                var context = item.Data is TItem templateData ? templateData : default!;
                return Template(context);
            }

            return builder => builder.AddContent(0, item.Text);
        }

        /// <inheritdoc />
        public async Task SelectSlot(DateTime start, DateTime end)
        {
            await SlotSelect.InvokeAsync(new SchedulerSlotSelectEventArgs { Start = start, End = end, Appointments = Array.Empty<AppointmentData>(), View = SelectedView });
        }

        /// <inheritdoc />
        public async Task<bool> SelectSlot(DateTime start, DateTime end, IEnumerable<AppointmentData> appointments)
        {
            var args = new SchedulerSlotSelectEventArgs { Start = start, End = end, Appointments = appointments, View = SelectedView };
            await SlotSelect.InvokeAsync(args);

            return args.IsDefaultPrevented;
        }

        /// <inheritdoc />
        public async Task SelectMonth(DateTime monthStart, IEnumerable<AppointmentData> appointments)
        {
            await MonthSelect.InvokeAsync(new SchedulerMonthSelectEventArgs { MonthStart = monthStart, Appointments = appointments, View = SelectedView });
        }

        /// <inheritdoc />
        public async Task SelectDay(DateTime day, IEnumerable<AppointmentData> appointments)
        {
            await DaySelect.InvokeAsync(new SchedulerDaySelectEventArgs { Day = day, Appointments = appointments, View = SelectedView });
        }

        /// <inheritdoc />
        public async Task<bool> SelectMore(DateTime start, DateTime end, IEnumerable<AppointmentData> appointments)
        {
            var args = new SchedulerMoreSelectEventArgs { Start = start, End = end, Appointments = appointments, View = SelectedView };
            await MoreSelect.InvokeAsync(args);

            return args.IsDefaultPrevented;
        }

        /// <inheritdoc />
        public async Task SelectAppointment(AppointmentData data)
        {
            ArgumentNullException.ThrowIfNull(data);

            var appointmentData = data.Data is TItem typedData ? typedData : default!;
            await AppointmentSelect.InvokeAsync(new SchedulerAppointmentSelectEventArgs<TItem> { Start = data.Start, End = data.End, Data = appointmentData });
        }

        /// <inheritdoc />
        public async Task AddView(ISchedulerView view)
        {
            if (!Views.Contains(view))
            {
                Views.Add(view);

                if (SelectedView == view)
                {
                    await InvokeLoadData();
                }

                StateHasChanged();
            }
        }

        /// <summary>
        /// Selects the specified <see cref="ISchedulerView"/>. The view must already be present in this scheduler.
        /// If the specified view is already selected, no action will be performed.
        /// </summary>
        /// <param name="view">The <see cref="ISchedulerView"/> to select</param>
        public async Task SelectView(ISchedulerView view)
        {
            var viewIndex = Views.IndexOf(view);
            if (viewIndex == -1)
            {
                return;
            }

            if (SelectedView == view)
            {
                return;
            }

            selectedIndex = viewIndex;

            await InvokeLoadData();

            StateHasChanged();
        }

        /// <summary>
        /// Causes the current scheduler view to render. Enumerates the items of <see cref="Data" /> and creates instances of <see cref="AppointmentData" /> to
        /// display in the current view. Use it when <see cref="Data" /> has changed.
        /// </summary>
        public async Task Reload()
        {
            appointments = null;

            await InvokeLoadData();

            StateHasChanged();
        }

        /// <inheritdoc />
        public bool IsSelected(ISchedulerView view)
        {
            return selectedIndex == Views.IndexOf(view);
        }

        async Task OnChangeView(ISchedulerView view)
        {
            selectedIndex = Views.IndexOf(view);

            await InvokeLoadData();
        }

        async Task OnPrev()
        {
            if (SelectedView != null)
            {
                CurrentDate = SelectedView.Prev();
                await InvokeLoadData();
            }
        }

        async Task OnToday()
        {
            var args = new SchedulerTodaySelectEventArgs { Today = DateTime.Now.Date };

            await TodaySelect.InvokeAsync(args);

            CurrentDate = args.Today;

            await InvokeLoadData();
        }

        async Task OnNext()
        {
            if (SelectedView != null)
            {
                CurrentDate = SelectedView.Next();
                await InvokeLoadData();
            }
        }

        /// <inheritdoc />
        public void RemoveView(ISchedulerView view)
        {
            Views.Remove(view);
        }

        /// <inheritdoc />
        protected override void OnInitialized()
        {
            CurrentDate = Date;
            selectedIndex = SelectedIndex;

            double height = 0;

            var style = CurrentStyle;

            if (style.TryGetValue("height", out var pixelHeight))
            {
                if (pixelHeight.EndsWith("px", StringComparison.Ordinal))
                {
                    height = Convert.ToDouble(pixelHeight.TrimEnd("px".ToCharArray()), CultureInfo.InvariantCulture);
                }
            }

            if (height > 0)
            {
                heightIsSet = true;

                Height = height;
            }
        }

        IEnumerable<AppointmentData>? appointments;
        DateTime rangeStart;
        DateTime rangeEnd;
        Func<TItem, DateTime>? startGetter;
        Func<TItem, DateTime>? endGetter;
        Func<TItem, string>? textGetter;

        /// <inheritdoc />
        public override async Task SetParametersAsync(ParameterView parameters)
        {
            var needsReload = false;

            if (parameters.DidParameterChange(nameof(Date), Date))
            {
                CurrentDate = parameters.GetValueOrDefault<DateTime>(nameof(Date));
                needsReload = true;
            }

            if (parameters.DidParameterChange(nameof(SelectedIndex), SelectedIndex))
            {
                selectedIndex = parameters.GetValueOrDefault<int>(nameof(SelectedIndex));
                needsReload = true;
            }

            if (parameters.DidParameterChange(nameof(Data), Data))
            {
                appointments = null;
            }

            if (parameters.DidParameterChange(nameof(StartProperty), StartProperty))
            {
                startGetter = PropertyAccess.Getter<TItem, DateTime>(parameters.GetValueOrDefault<string>(nameof(StartProperty))!);
            }

            if (parameters.DidParameterChange(nameof(EndProperty), EndProperty))
            {
                endGetter = PropertyAccess.Getter<TItem, DateTime>(parameters.GetValueOrDefault<string>(nameof(EndProperty))!);
            }

            if (parameters.DidParameterChange(nameof(TextProperty), TextProperty))
            {
                textGetter = PropertyAccess.Getter<TItem, string>(parameters.GetValueOrDefault<string>(nameof(TextProperty))!);
            }

            await base.SetParametersAsync(parameters);

            if (needsReload)
            {
                await InvokeLoadData();
            }
        }

        private async Task InvokeLoadData()
        {
            if (SelectedView != null)
            {
                await LoadData.InvokeAsync(new SchedulerLoadDataEventArgs { Start = SelectedView.StartDate, End = SelectedView.EndDate, View = SelectedView });
            }
        }

        /// <inheritdoc />
        public bool IsAppointmentInRange(AppointmentData item, DateTime start, DateTime end)
        {
            ArgumentNullException.ThrowIfNull(item);

            if (item.Start == item.End && item.Start >= start && item.End < end)
            {
                return true;
            }

            return item.End > start && item.Start < end;
        }

        /// <inheritdoc />
        public IEnumerable<AppointmentData> GetAppointmentsInRange(DateTime start, DateTime end)
        {
            if (Data == null)
            {
                return [];
            }

            if (start == rangeStart && end == rangeEnd && appointments != null)
            {
                return appointments;
            }

            rangeStart = start;
            rangeEnd = end;

            appointments = Data.AsQueryable()
                               .Where([
                                    new FilterDescriptor { Property = EndProperty, FilterValue = start, FilterOperator = FilterOperator.GreaterThanOrEquals },
                                    new FilterDescriptor { Property = StartProperty, FilterValue = end, FilterOperator = FilterOperator.LessThanOrEquals }
                                ], LogicalFilterOperator.And, FilterCaseSensitivity.Default)
                               .ToList()
                               .Select(item => new AppointmentData { Start = startGetter!(item), End = endGetter!(item), Text = textGetter!(item), Data = item });

            return appointments;
        }

        /// <inheritdoc />
        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            await base.OnAfterRenderAsync(firstRender);

            if (firstRender && JSRuntime != null)
            {
                var rect = await JSRuntime.InvokeAsync<Rect>("Radzen.createResizable", Element, Reference);

                if (!heightIsSet)
                {
                    heightIsSet = true;
                    Resize(rect.Width, rect.Height);
                }
            }
        }

        /// <summary>
        /// Invoked from client-side via interop when the scheduler size changes.
        /// </summary>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        [JSInvokable]
        public void Resize(double width, double height)
        {
            var stateHasChanged = false;

            if (height != Height)
            {
                Height = height;
                stateHasChanged = true;
            }

            if (stateHasChanged)
            {
                StateHasChanged();
            }
        }

        /// <inheritdoc />
        public override void Dispose()
        {
            base.Dispose();

            if (IsJSRuntimeAvailable && JSRuntime != null)
            {
                JSRuntime.InvokeVoid("Radzen.disposeElement", Element);
            }

            GC.SuppressFinalize(this);
        }

        private bool heightIsSet;
        private double Height { get; set; } = 400; // Default height set from theme.
        double IScheduler.Height
        {
            get
            {
                return Height;
            }
        }
        /// <inheritdoc />
        protected override string GetComponentCssClass()
        {
            return $"rz-scheduler";
        }

        async Task IScheduler.MouseEnterAppointment(ElementReference reference, AppointmentData data)
        {
            var argsData = data.Data is TItem typed ? typed : default!;
            await AppointmentMouseEnter.InvokeAsync(new SchedulerAppointmentMouseEventArgs<TItem> { Element = reference, Data = argsData, AppointmentData = data });
        }

        async Task IScheduler.MouseEnterAppointment(ElementReference reference, AppointmentData data, double clientX, double clientY)
        {
            var argsData = data.Data is TItem typed ? typed : default!;
            await AppointmentMouseEnter.InvokeAsync(new SchedulerAppointmentMouseEventArgs<TItem> { Element = reference, Data = argsData, AppointmentData = data, ClientX = clientX, ClientY = clientY });
        }

        async Task IScheduler.MouseLeaveAppointment(ElementReference reference, AppointmentData data)
        {
            var argsData = data.Data is TItem typed ? typed : default!;
            await AppointmentMouseLeave.InvokeAsync(new SchedulerAppointmentMouseEventArgs<TItem> { Element = reference, Data = argsData, AppointmentData = data });
        }

        async Task IScheduler.MouseLeaveAppointment(ElementReference reference, AppointmentData data, double clientX, double clientY)
        {
            var argsData = data.Data is TItem typed ? typed : default!;
            await AppointmentMouseLeave.InvokeAsync(new SchedulerAppointmentMouseEventArgs<TItem> { Element = reference, Data = argsData, AppointmentData = data, ClientX = clientX, ClientY = clientY });
        }

        bool IScheduler.HasMouseEnterAppointmentDelegate()
        {
            return AppointmentMouseEnter.HasDelegate;
        }

        bool IScheduler.HasAppointmentMoveDelegate()
        {
            return AppointmentMove.HasDelegate;
        }
    }
}
