using Bunit;
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Xunit;

namespace Radzen.Blazor.Tests
{
    public class DatePickerTests
    {
        [Fact]
        public void DatePicker_Renders_CssClass()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            Assert.Contains(@$"rz-datepicker", component.Markup);
            Assert.Contains(@$"rz-calendar", component.Markup);
            Assert.Contains(@$"rz-calendar-header", component.Markup);
            Assert.Contains(@$"rz-calendar-view", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_ShowTimeParameter()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.ShowTime, true));

            Assert.Contains(@$"rz-timepicker", component.Markup);
            Assert.Contains(@$"rz-hour-picker", component.Markup);
            Assert.Contains(@$"rz-minute-picker", component.Markup);
        }

        [Fact]
        public void DatePicker_Updates_MonthNames_When_Culture_Changes()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters => parameters
                .Add(p => p.Inline, true)
                .Add(p => p.Value, new DateTime(2025, 1, 15))
                .Add(p => p.Culture, new CultureInfo("de")));

            Assert.Contains("Januar", component.Markup);

            component.SetParametersAndRender(parameters => parameters
                .Add(p => p.Culture, new CultureInfo("fr")));

            Assert.Contains("janvier", component.Markup);
            Assert.DoesNotContain("Januar", component.Markup);
        }

        [Fact]
        public void DatePicker_Updates_MonthNames_When_DefaultCulture_Cascade_Changes()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            void Build(Bunit.ComponentParameterCollectionBuilder<CascadingValue<CultureInfo>> parameters, CultureInfo culture) => parameters
                .Add(p => p.Name, "DefaultCulture")
                .Add(p => p.Value, culture)
                .AddChildContent<RadzenDatePicker<DateTime>>(picker => picker
                    .Add(p => p.Inline, true)
                    .Add(p => p.Value, new DateTime(2025, 1, 15)));

            var component = ctx.RenderComponent<CascadingValue<CultureInfo>>(parameters => Build(parameters, new CultureInfo("de")));

            Assert.Contains("Januar", component.Markup);

            component.SetParametersAndRender(parameters => Build(parameters, new CultureInfo("ja")));

            Assert.Contains("1月", component.Markup);
            Assert.DoesNotContain("Januar", component.Markup);
        }

        [Fact]
        public void DatePicker_Follows_Nested_Culture_Cascades_Like_The_Demo()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            // Mirror the localization demo exactly: nested DefaultUICulture + DefaultCulture
            // cascades wrapping an inline RadzenDatePicker with ShowTime and no explicit Culture.
            void Build(Bunit.ComponentParameterCollectionBuilder<CascadingValue<CultureInfo>> outer, CultureInfo culture) => outer
                .Add(p => p.Name, "DefaultUICulture")
                .Add(p => p.Value, culture)
                .AddChildContent<CascadingValue<CultureInfo>>(inner => inner
                    .Add(p => p.Name, "DefaultCulture")
                    .Add(p => p.Value, culture)
                    .AddChildContent<RadzenDatePicker<DateTime>>(picker => picker
                        .Add(p => p.Inline, true)
                        .Add(p => p.ShowTime, true)
                        .Add(p => p.Value, new DateTime(2025, 6, 4))));

            var component = ctx.RenderComponent<CascadingValue<CultureInfo>>(o => Build(o, new CultureInfo("de")));
            Assert.Contains("Juni", component.Markup);

            component.SetParametersAndRender(o => Build(o, new CultureInfo("ja")));
            Assert.Contains("6月", component.Markup);
            Assert.DoesNotContain("Juni", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_Japanese_Month_When_Culture_Is_Ja()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters => parameters
                .Add(p => p.Inline, true)
                .Add(p => p.Value, new DateTime(2025, 6, 4))
                .Add(p => p.Culture, new CultureInfo("ja")));

            // Japanese month/day names must appear; English ones must not.
            Assert.Contains("6月", component.Markup);
            Assert.DoesNotContain("June", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_ShowSecondsParameter()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add<bool>(p => p.ShowTime, true);
                parameters.Add<bool>(p => p.ShowSeconds, true);
            });

            Assert.Contains(@$"rz-timepicker", component.Markup);
            Assert.Contains(@$"rz-hour-picker", component.Markup);
            Assert.Contains(@$"rz-minute-picker", component.Markup);
            Assert.Contains(@$"rz-second-picker", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_ShowTimeOkButtonParameter()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add<bool>(p => p.ShowTime, true);
                parameters.Add<bool>(p => p.ShowTimeOkButton, true);
            });

            Assert.Contains(@$"rz-timepicker", component.Markup);
            Assert.Contains(@$"rz-hour-picker", component.Markup);
            Assert.Contains(@$"rz-minute-picker", component.Markup);
            Assert.Contains(@$"<span class=""rz-button-text"">Ok</span>", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_DateFormatParameter()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            var format = "d";

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add(p => p.DateFormat, format);
                parameters.Add<object>(p => p.Value, DateTime.Now);
            });

            Assert.Contains(@$"value=""{string.Format("{0:" + format + "}", DateTime.Now)}""", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_HourFormatParameter()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add<bool>(p => p.ShowTime, true);
                parameters.Add(p => p.HourFormat, "12");
            });

            Assert.Contains(@$"rz-ampm-picker", component.Markup);
            Assert.Contains(@$"rzi-chevron-up", component.Markup);
            Assert.Contains(@$"rzi-chevron-down", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_TimeOnlyParameter()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add<bool>(p => p.ShowTime, true);
                parameters.Add<bool>(p => p.TimeOnly, true);
            });

            Assert.DoesNotContain(@$"rz-calendar-header", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_AllowClearParameter()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add<object>(p => p.Value, DateTime.Now);
                parameters.Add<bool>(p => p.AllowClear, true);
            });

            Assert.Contains(@$"<button type=""button"" class=""notranslate rz-dropdown-clear-icon rzi rzi-times""", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_TabIndexParameter()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            var value = 1;

            component.SetParametersAndRender(parameters => parameters.Add<int>(p => p.TabIndex, value));

            Assert.Contains(@$"tabindex=""{value}""", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_EmptyCssClass_WhenValueIsEmpty()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            component.SetParametersAndRender(parameters => parameters.Add(p => p.Value, null));

            Assert.Contains(@$"rz-state-empty", component.Markup);
        }

        [Fact]
        public void DatePicker_DoesNotRender_EmptyCssClass_WhenValueIsNotEmpty()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            component.SetParametersAndRender(parameters => parameters.Add(p => p.Value, DateTime.Now));

            Assert.DoesNotContain(@$"rz-state-empty", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_DisabledParameter()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.Disabled, true));

            Assert.Contains(@$"disabled", component.Markup);
            Assert.Contains(@$"rz-state-disabled", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_ReadOnlyParameter()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            var value = true;

            component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.ReadOnly, value));

            Assert.Contains(@$"readonly", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_StyleParameter()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            var value = "width:20px";

            component.SetParametersAndRender(parameters => parameters.Add(p => p.Style, value));

            Assert.Contains(@$"style=""{value}""", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_UnmatchedParameter()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            component.SetParametersAndRender(parameters => parameters.AddUnmatched("autofocus", ""));

            Assert.Contains(@$"autofocus", component.Markup);
        }

        [Fact]
        public void DatePicker_NotRaises_ChangeEventOnNextMonth()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            var raised = false;
            object newValue = null;

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add(p => p.Change, args => { raised = true; newValue = args; });
            });

            component.Find(".rz-calendar-next-icon").Click();

            Assert.False(raised);
        }

        [Fact]
        public void DatePicker_NotRaises_ValueChangedEventOnNextMonth()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            var raised = false;
            object newValue = null;

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add(p => p.ValueChanged, args => { raised = true; newValue = args; });
            });

            component.Find(".rz-calendar-next-icon").Click();

            Assert.False(raised);
        }

        [Fact]
        public void DatePicker_NotRaises_ChangeEventOnPrevMonth()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            var raised = false;
            object newValue = null;

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add(p => p.Change, args => { raised = true; newValue = args; });
            });

            component.Find(".rz-calendar-prev-icon").Click();

            Assert.False(raised);
        }

        [Fact]
        public void DatePicker_NotRaises_ValueChangedEventOnPrevMonth()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            var raised = false;
            object newValue = null;

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add(p => p.ValueChanged, args => { raised = true; newValue = args; });
            });

            component.Find(".rz-calendar-prev-icon").Click();

            Assert.False(raised);
        }

        [Fact]
        public void DatePicker_Raises_ValueChangedEvent_Returns_PreviousDateOnInputOnDisabledDates()
        {
            IEnumerable<DateTime> dates = new DateTime[] { DateTime.Today };
            DateTime previousDay = DateTime.Today.AddDays(-1);

            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            var raised = false;
            object newValue = null;

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add(p => p.ValueChanged, args => { raised = true; newValue = args; })
                          .Add(p => p.DateRender, args => { args.Disabled = dates.Contains(args.Date); });
            });

            var inputElement = component.Find(".rz-inputtext");

            // initialize DateTimeValue
            ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult(previousDay.ToShortDateString());
            inputElement.Change(previousDay.AddDays(-1));

            // try to enter disabled date
            ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult(DateTime.Today.ToShortDateString());
            inputElement.Change(DateTime.Today);

            Assert.True(raised);
            Assert.Equal(previousDay, (DateTime)newValue);
        }

        [Fact]
        public void DatePicker_Clears_InputOnDisabledDates()
        {
            IEnumerable<DateTime> dates = new DateTime[] { DateTime.Today };
            DateTime previousDay = DateTime.Today.AddDays(-1);

            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime?>>();

            var raised = false;
            object newValue = null;

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add(p => p.ValueChanged, args => { raised = true; newValue = args; })
                          .Add(p => p.DateRender, args => { args.Disabled = dates.Contains(args.Date); });
            });

            var inputElement = component.Find(".rz-inputtext");

            // initialize DateTimeValue
            ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult(previousDay.ToShortDateString());
            inputElement.Change(previousDay.AddDays(-1));

            // try to enter disabled date
            ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult(DateTime.Today.ToShortDateString());
            inputElement.Change(DateTime.Today);

            Assert.True(raised);
            Assert.Null(newValue);
        }

        [Fact]
        public void DatePicker_Parses_Input_Using_DateFormat()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime?>>();

            var raised = false;
            object newValue = null;

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add(p => p.DateFormat, "ddMM");
                parameters.Add(p => p.ValueChanged, args => { raised = true; newValue = args; });
            });

            var inputElement = component.Find(".rz-inputtext");

            string input = "3012";
            ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult(input);
            inputElement.Change(input);

            Assert.True(raised);
            Assert.Equal(new DateTime(DateTime.Now.Year, 12, 30), newValue);
        }


        [Fact]
        public void DatePicker_Parses_Input_Using_ParseInput()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime?>>();

            Func<string, DateTime?> customParseInput = (input) =>
            {
                if (DateTime.TryParseExact(input, "ddMM", null, DateTimeStyles.None, out var result))
                {
                    return result;
                }

                return null;
            };

            var raised = false;
            object newValue = null;

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add(p => p.ParseInput, customParseInput);
                parameters.Add(p => p.ValueChanged, args => { raised = true; newValue = args; });
            });

            var inputElement = component.Find(".rz-inputtext");

            string input = "3012";
            ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult(input);
            inputElement.Change(input);

            Assert.True(raised);
            Assert.Equal(new DateTime(DateTime.Now.Year, 12, 30), newValue);
        }


        [Fact]
        public void DatePicker_Renders_Blank_WhenValueIsMaxValue()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, DateTime.MaxValue);
            });

            Assert.Contains(@$"rz-state-empty", component.Markup);
            Assert.Contains(@$"value=""""", component.Markup);
        }

        [Fact]
        public void DatePicker_Renders_Blank_WhenValueIsMinValue()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, DateTime.MinValue);
            });

            Assert.Contains(@$"rz-state-empty", component.Markup);
            Assert.Contains(@$"value=""""", component.Markup);
        }

        [Theory]
        [InlineData(DateTimeKind.Local)]
        [InlineData(DateTimeKind.Unspecified)]
        [InlineData(DateTimeKind.Utc)]
        public void DatePicker_Respects_DateTimeKind(DateTimeKind kind)
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(x => x.Kind, kind);
                parameters.Add(x => x.ShowTime, true);
            });

            var raised = false;
            object newValue = null;

            component.SetParametersAndRender(parameters =>
            {
                parameters.Add(p => p.Change, args => { raised = true; newValue = args; });
            });

            component.Find(".rz-calendar-next-icon").Click();
            component.FindAll(".rz-button-text").First(x => x.TextContent == "Ok").Click();

            Assert.True(raised);
            Assert.Equal(kind, ((DateTime)newValue).Kind);
        }

        [Fact]
        public void DatePicker_Renders_FooterTemplate()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            string actionsTemplate = "<input type=\"button\" value=\"Test\" />";

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, DateTime.MinValue);
                parameters.Add(p => p.FooterTemplate, actionsTemplate);
            });

            Assert.Contains(actionsTemplate, component.Markup);
        }

        [Fact]
        public void DatePicker_Converts_DateTimeOffSet_FromUtc_ToLocal()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var valueUtc = DateTimeOffset.UtcNow;
            var kind = DateTimeKind.Local;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTimeOffset>>(parameters =>
            {
                parameters.Add(p => p.Kind, kind);
                parameters.Add(p => p.Value, valueUtc);
            });

            Assert.Equal(kind, (component.Instance.Value as DateTime?)?.Kind);
            Assert.Equal(valueUtc.LocalDateTime.ToString(CultureInfo.InvariantCulture), (component.Instance.Value as DateTime?)?.ToString(CultureInfo.InvariantCulture));
        }

        [Fact]
        public void DatePicker_Converts_DateTimeOffSet_Local_ToUtc()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var valueUtc = DateTimeOffset.Now;
            var kind = DateTimeKind.Utc;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTimeOffset>>(parameters =>
            {
                parameters.Add(p => p.Kind, kind);
                parameters.Add(p => p.Value, valueUtc);
            });

            Assert.Equal(kind, (component.Instance.Value as DateTime?)?.Kind);
            Assert.Equal(valueUtc.UtcDateTime.ToString(CultureInfo.InvariantCulture), (component.Instance.Value as DateTime?)?.ToString(CultureInfo.InvariantCulture));
        }

        [Fact]
        public void DatePicker_Displays_Calender_Icon()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            Assert.Contains(@$"rzi-calendar", component.Markup);
        }

        [Fact]
        public void DatePicker_Displays_Schedule_Icon()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.TimeOnly, true);
            });

            Assert.Contains(@$"rzi-time", component.Markup);
        }

        [Fact]
        public void DatePicker_Supports_DateOnly()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            DateOnly? dateOnly = new DateOnly(2024, 1, 31);
            DateOnly? valueChangedValue = null!;
            var component = ctx.RenderComponent<RadzenDatePicker<DateOnly?>>(parameters =>
            {
                parameters.Add(p => p.Value, dateOnly);
                parameters.Add(p => p.ValueChanged, args => { valueChangedValue = args; });
            });

            Assert.False(component.Instance.ShowTime);
            var input = component.Find("input");
            input.GetAttribute("value").MarkupMatches(dateOnly.ToString());

            // update to new value
            var inputElement = component.Find(".rz-inputtext");
            DateOnly? enteredValue = new DateOnly(2024, 2, 28);
            ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult(enteredValue.Value.ToShortDateString());
            inputElement.Change(enteredValue);

            input.GetAttribute("value").MarkupMatches(enteredValue.ToString());
            Assert.Equal(enteredValue, component.Instance.Value);
            Assert.Equal(enteredValue, valueChangedValue);
        }

        [Fact]
        public void DatePicker_Supports_TimeOnly()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            TimeOnly? timeOnly = new TimeOnly(23, 59, 59);
            TimeOnly? valueChangedValue = null!;
            var component = ctx.RenderComponent<RadzenDatePicker<TimeOnly>>(parameters =>
            {
                parameters.Add(p => p.Value, timeOnly);
                parameters.Add(p => p.ValueChanged, args => { valueChangedValue = args; });
            });

            Assert.True(component.Instance.TimeOnly);
            Assert.True(component.Instance.ShowTime);
            var input = component.Find("input");
            input.GetAttribute("value").MarkupMatches(timeOnly.ToString());

            // update to new value
            var inputElement = component.Find(".rz-inputtext");
            TimeOnly? enteredValue = new TimeOnly(1, 4, 5);
            ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult(enteredValue.Value.ToLongTimeString());
            inputElement.Change(enteredValue);

            input.GetAttribute("value").MarkupMatches(enteredValue.ToString());
            Assert.Equal(enteredValue, component.Instance.Value);
            Assert.Equal(enteredValue, valueChangedValue);
        }

        [Fact]
        public void DatePicker_ShowCalendarWeek_WeekNumberAddedInAdditionalColumn()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameter =>
            {
                parameter.Add(p => p.ShowCalendarWeek, true);
            });

            Assert.Contains(@$"rz-calendar-week-number", component.Markup);
            Assert.Equal(8, component.FindAll(".rz-calendar-view th").Count());
            // check header and week number column
            Assert.Single(component.FindAll("th.rz-datepicker-week-number"));
            Assert.Equal(6, component.FindAll("td.rz-calendar-week-number").Count());
        }

        [Fact]
        public void DatePicker_ShowCalendarWeekFalse_NoAdditionalColumn()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameter =>
            {
                parameter.Add(p => p.ShowCalendarWeek, false);
            });

            Assert.DoesNotContain(@$"rz-calendar-week-number", component.Markup);
            Assert.Equal(7, component.FindAll(".rz-calendar-view th").Count());
        }

        [Fact]
        public void DatePicker_WeekdayHeaders_HaveFullDayNameAriaLabel()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var culture = new CultureInfo("en-US");
            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameter =>
            {
                parameter.Add(p => p.Culture, culture);
            });

            var headers = component.FindAll(".rz-calendar-view thead th[scope='col']");
            Assert.Equal(7, headers.Count);

            var firstDay = (int)culture.DateTimeFormat.FirstDayOfWeek;
            for (int i = 0; i < 7; i++)
            {
                var dayIndex = (firstDay + i) % 7;
                var expectedFull = culture.DateTimeFormat.DayNames[dayIndex];
                var expectedAbbr = culture.DateTimeFormat.AbbreviatedDayNames[dayIndex];

                Assert.Equal(expectedFull, headers[i].GetAttribute("aria-label"));
                Assert.Equal(expectedFull, headers[i].GetAttribute("abbr"));

                var visibleSpan = headers[i].QuerySelector("span[aria-hidden='true']");
                Assert.NotNull(visibleSpan);
                Assert.Equal(expectedAbbr, visibleSpan.TextContent);
            }
        }

        [Fact]
        public void DatePicker_ShowCalendarWeekWithCustomTitle_TitleCorrectlyRendered()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameter =>
            {
                parameter.Add(p => p.ShowCalendarWeek, true);
                parameter.Add(p => p.CalendarWeekTitle, "Wk");
            });

            var weekNumberHeader = component.Find(".rz-calendar-view th.rz-datepicker-week-number");
            Assert.Contains("Wk", weekNumberHeader.InnerHtml);
        }

        [Fact]
        public void DatePicker_Multiple_Selects_IEnumerableDateTime()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            IEnumerable<DateTime> emitted = null;
            var initial = new DateTime(2024, 1, 1);

            var component = ctx.RenderComponent<RadzenDatePicker<IEnumerable<DateTime>>>(parameters =>
            {
                parameters.Add(p => p.Multiple, true);
                parameters.Add(p => p.InitialViewDate, initial);
                parameters.Add(p => p.ValueChanged, args => { emitted = args; });
            });

            component.InvokeAsync(() => component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "10").ParentElement.Click());
            component.InvokeAsync(() => component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "12").ParentElement.Click());

            Assert.NotNull(emitted);
            var list = emitted.ToList();
            Assert.Equal(2, list.Count);
            Assert.Contains(new DateTime(2024, 1, 10), list.Select(d => d.Date));
            Assert.Contains(new DateTime(2024, 1, 12), list.Select(d => d.Date));
        }

        [Fact]
        public void DatePicker_Multiple_Selects_IEnumerableNullableDateTime()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            IEnumerable<DateTime?> emitted = null;
            var initial = new DateTime(2024, 2, 1);

            var component = ctx.RenderComponent<RadzenDatePicker<IEnumerable<DateTime?>>>(parameters =>
            {
                parameters.Add(p => p.Multiple, true);
                parameters.Add(p => p.InitialViewDate, initial);
                parameters.Add(p => p.ValueChanged, args => { emitted = args; });
            });

            component.InvokeAsync(() => component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "3").ParentElement.Click());
            component.InvokeAsync(() => component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "14").ParentElement.Click());

            Assert.NotNull(emitted);
            var list = emitted.ToList();
            Assert.Equal(2, list.Count);
            Assert.Contains(new DateTime(2024, 2, 3), list.Select(d => d.Value.Date));
            Assert.Contains(new DateTime(2024, 2, 14), list.Select(d => d.Value.Date));
        }

        [Fact]
        public void DatePicker_Multiple_Emits_IEnumerableDateTimeOffsetNullable_WithUtcKind()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            IEnumerable<DateTimeOffset?> emitted = null;
            var initial = new DateTime(2024, 3, 1);

            var component = ctx.RenderComponent<RadzenDatePicker<IEnumerable<DateTimeOffset?>>>(parameters =>
            {
                parameters.Add(p => p.Multiple, true);
                parameters.Add(p => p.InitialViewDate, initial);
                parameters.Add(p => p.Kind, DateTimeKind.Utc);
                parameters.Add(p => p.ValueChanged, args => { emitted = args; });
            });

            component.InvokeAsync(() => component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "5").ParentElement.Click());
            component.InvokeAsync(() => component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "20").ParentElement.Click());

            Assert.NotNull(emitted);
            var list = emitted.ToList();
            Assert.Equal(2, list.Count);
            Assert.All(list, dto => Assert.Equal(TimeSpan.Zero, dto.Value.Offset));
        }

        [Fact]
        public void DatePicker_Multiple_Emits_IEnumerableDateOnlyAndTimeOnlyNullable()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            IEnumerable<DateOnly?> emittedDates = null;
            IEnumerable<TimeOnly?> emittedTimes = null;
            var initial = new DateTime(2024, 4, 1);

            var compDates = ctx.RenderComponent<RadzenDatePicker<IEnumerable<DateOnly?>>>(parameters =>
            {
                parameters.Add(p => p.Multiple, true);
                parameters.Add(p => p.InitialViewDate, initial);
                parameters.Add(p => p.ValueChanged, args => { emittedDates = args; });
            });

            compDates.InvokeAsync(() => compDates.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "7").ParentElement.Click());
            compDates.InvokeAsync(() => compDates.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "9").ParentElement.Click());

            Assert.NotNull(emittedDates);
            var dateList = emittedDates.ToList();
            Assert.Equal(2, dateList.Count);
            Assert.Contains(new DateOnly(2024, 4, 7), dateList.Select(d => d.Value));
            Assert.Contains(new DateOnly(2024, 4, 9), dateList.Select(d => d.Value));

            // TimeOnly? emission should produce midnight times for selected dates
            var compTimes = ctx.RenderComponent<RadzenDatePicker<IEnumerable<TimeOnly?>>>(parameters =>
            {
                parameters.Add(p => p.Multiple, true);
                parameters.Add(p => p.InitialViewDate, initial);
                parameters.Add(p => p.ValueChanged, args => { emittedTimes = args; });
            });

            compTimes.InvokeAsync(() => compTimes.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "1").ParentElement.Click());
            compTimes.InvokeAsync(() => compTimes.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "2").ParentElement.Click());

            Assert.NotNull(emittedTimes);
            var timeList = emittedTimes.ToList();
            Assert.Equal(2, timeList.Count);
            Assert.All(timeList, t => Assert.Equal(new TimeOnly(0, 0, 0), t.Value));
        }

        [Fact]
        public void DatePicker_WithoutOkButton_TimeChange_ImmediatelyCommits()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var changeCount = 0;
            DateTime? lastChangeValue = null;
            DateTime? lastValueChanged = null;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.ShowTime, true);
                parameters.Add(p => p.ShowTimeOkButton, false);
                parameters.Add(p => p.Value, new DateTime(2024, 6, 15, 10, 30, 0));
                parameters.Add(p => p.Change, args => { changeCount++; lastChangeValue = args; });
                parameters.Add(p => p.ValueChanged, args => { lastValueChanged = args; });
            });

            component.Find(".rz-hour-picker .rz-numeric-up").Click();

            Assert.Equal(1, changeCount);
            Assert.NotNull(lastChangeValue);
            Assert.Equal(11, lastChangeValue.Value.Hour);
            Assert.NotNull(lastValueChanged);
            Assert.Equal(11, lastValueChanged.Value.Hour);
        }

        [Fact]
        public void DatePicker_Immediate_TimeChange_ImmediatelyCommits()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var changeCount = 0;
            DateTime? lastChangeValue = null;
            DateTime? lastValueChanged = null;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.ShowTime, true);
                parameters.Add(p => p.ShowTimeOkButton, true);
                parameters.Add(p => p.Immediate, true);
                parameters.Add(p => p.Value, new DateTime(2024, 6, 15, 10, 30, 0));
                parameters.Add(p => p.Change, args => { changeCount++; lastChangeValue = args; });
                parameters.Add(p => p.ValueChanged, args => { lastValueChanged = args; });
            });

            component.Find(".rz-hour-picker .rz-numeric-up").Click();

            Assert.Equal(1, changeCount);
            Assert.NotNull(lastChangeValue);
            Assert.Equal(11, lastChangeValue.Value.Hour);
            Assert.NotNull(lastValueChanged);
            Assert.Equal(11, lastValueChanged.Value.Hour);

            component.Find(".rz-minute-picker .rz-numeric-up").Click();

            Assert.Equal(2, changeCount);
            Assert.Equal(31, lastChangeValue.Value.Minute);
            Assert.Equal(31, lastValueChanged.Value.Minute);
        }

        [Fact]
        public void DatePicker_WithoutOkButton_DayClick_ImmediatelyCommits()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var changeRaised = false;
            DateTime? lastValueChanged = null;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.ShowTime, true);
                parameters.Add(p => p.ShowTimeOkButton, false);
                parameters.Add(p => p.Value, new DateTime(2024, 6, 15, 10, 30, 0));
                parameters.Add(p => p.Change, args => { changeRaised = true; });
                parameters.Add(p => p.ValueChanged, args => { lastValueChanged = args; });
            });

            component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "20").ParentElement.Click();

            Assert.True(changeRaised);
            Assert.NotNull(lastValueChanged);
            Assert.Equal(20, lastValueChanged.Value.Day);
            Assert.Equal(10, lastValueChanged.Value.Hour);
        }

        [Fact]
        public void DatePicker_WithOkButton_TimeChange_RevertsOnClose()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var initialDate = new DateTime(2024, 6, 15, 10, 30, 0);
            var valueChangedCount = 0;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.ShowTime, true);
                parameters.Add(p => p.ShowTimeOkButton, true);
                parameters.Add(p => p.Value, initialDate);
                parameters.Add(p => p.ValueChanged, args => { valueChangedCount++; });
            });

            var hourInput = component.Find(".rz-hour-picker input");
            Assert.Equal("10", hourInput.GetAttribute("value"));

            component.Find(".rz-hour-picker .rz-numeric-up").Click();

            hourInput = component.Find(".rz-hour-picker input");
            Assert.Equal("11", hourInput.GetAttribute("value"));
            Assert.Equal(0, valueChangedCount);

            component.InvokeAsync(() => component.Instance.Close());

            hourInput = component.Find(".rz-hour-picker input");
            Assert.Equal("10", hourInput.GetAttribute("value"));

            var input = component.Find(".rz-inputtext");
            Assert.Contains("10", input.GetAttribute("value"));
        }

        [Fact]
        public void DatePicker_WithOkButton_TimeChange_CommitsOnOk()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            DateTime? lastValueChanged = null;
            DateTime? lastChangeValue = null;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.ShowTime, true);
                parameters.Add(p => p.ShowTimeOkButton, true);
                parameters.Add(p => p.Value, new DateTime(2024, 6, 15, 10, 30, 0));
                parameters.Add(p => p.ValueChanged, args => { lastValueChanged = args; });
                parameters.Add(p => p.Change, args => { lastChangeValue = args; });
            });

            component.Find(".rz-hour-picker .rz-numeric-up").Click();
            component.FindAll(".rz-button-text").First(x => x.TextContent == "Ok").Click();

            Assert.NotNull(lastValueChanged);
            Assert.Equal(11, lastValueChanged.Value.Hour);
            Assert.NotNull(lastChangeValue);
            Assert.Equal(11, lastChangeValue.Value.Hour);
        }

        [Fact]
        public void DatePicker_WithOkButton_DayCommits_ThenTimeRevertsOnClose()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            DateTime? lastValueChanged = null;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.ShowTime, true);
                parameters.Add(p => p.ShowTimeOkButton, true);
                parameters.Add(p => p.Value, new DateTime(2024, 6, 15, 10, 30, 0));
                parameters.Add(p => p.ValueChanged, args => { lastValueChanged = args; });
            });

            component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "20").ParentElement.Click();

            Assert.NotNull(lastValueChanged);
            Assert.Equal(20, lastValueChanged.Value.Day);
            Assert.Equal(10, lastValueChanged.Value.Hour);

            component.Find(".rz-hour-picker .rz-numeric-up").Click();

            component.InvokeAsync(() => component.Instance.Close());

            var input = component.Find(".rz-inputtext");
            Assert.Contains("10", input.GetAttribute("value"));
            Assert.Contains("20", input.GetAttribute("value"));
        }

        [Fact]
        public void DatePicker_WithOkButton_OnPopupClose_RevertsTimeChange()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var initialDate = new DateTime(2024, 6, 15, 10, 30, 0);
            DateTime? lastValueChanged = null;
            var valueChangedCount = 0;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.ShowTime, true);
                parameters.Add(p => p.ShowTimeOkButton, true);
                parameters.Add(p => p.Value, initialDate);
                parameters.Add(p => p.ValueChanged, args => { valueChangedCount++; lastValueChanged = args; });
            });

            component.Find(".rz-hour-picker .rz-numeric-up").Click();

            var countBeforeClose = valueChangedCount;

            component.InvokeAsync(() => component.Instance.OnPopupClose());

            Assert.Equal(countBeforeClose, valueChangedCount);

            var input = component.Find("input");
            Assert.Contains("10", input.GetAttribute("value"));
        }

        [Fact]
        public void DatePicker_OkClick_Fires_OnOkButtonClick()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var okClickRaised = false;
            DateTime? okClickValue = null;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.ShowTime, true);
                parameters.Add(p => p.ShowTimeOkButton, true);
                parameters.Add(p => p.OkClick, args => { okClickRaised = true; okClickValue = args; });
            });

            component.Find(".rz-calendar-next-icon").Click();
            component.FindAll(".rz-button-text").First(x => x.TextContent == "Ok").Click();

            Assert.True(okClickRaised);
            Assert.NotNull(okClickValue);
        }

        [Fact]
        public void DatePicker_OkClick_DoesNotFire_OnDaySelection()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var okClickRaised = false;
            var changeRaised = false;

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.ShowTime, true);
                parameters.Add(p => p.ShowTimeOkButton, true);
                parameters.Add(p => p.Change, args => { changeRaised = true; });
                parameters.Add(p => p.OkClick, args => { okClickRaised = true; });
            });

            component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "15").ParentElement.Click();

            Assert.True(changeRaised);
            Assert.False(okClickRaised);
        }
        [Fact]
        public void DatePicker_Renders_ImmediateParameter()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
                parameters.Add(p => p.Immediate, true));

            var inputElement = component.Find(".rz-inputtext");

            Assert.Contains("oninput", inputElement.ToMarkup());
        }

        [Fact]
        public void DatePicker_DoesNotRender_OninputWhenNotImmediate()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            var inputElement = component.Find(".rz-inputtext");

            Assert.DoesNotContain("oninput", inputElement.ToMarkup());
        }

        [Fact]
        public void DatePicker_Immediate_Raises_ChangeOnValidInput()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var raised = false;
            object newValue = null;
            var testDate = new DateTime(2025, 6, 15);

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime?>>(parameters =>
            {
                parameters.Add(p => p.Immediate, true);
                parameters.Add(p => p.Change, args => { raised = true; newValue = args; });
            });

            var inputElement = component.Find(".rz-inputtext");

            ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult(testDate.ToShortDateString());
            inputElement.Input(testDate.ToShortDateString());

            Assert.True(raised);
            Assert.Equal(testDate, ((DateTime?)newValue)?.Date);
        }

        [Fact]
        public void DatePicker_Immediate_DoesNotUpdateOnInvalidInput()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var initialDate = new DateTime(2025, 6, 15);
            var changeCount = 0;

            // Use non-nullable DateTime so ParseDate does not clear the value on invalid input
            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Immediate, true);
                parameters.Add(p => p.Value, initialDate);
                parameters.Add(p => p.Change, args => { changeCount++; });
            });

            var inputElement = component.Find(".rz-inputtext");

            // Simulate partial/invalid input
            ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult("abc");
            inputElement.Input("abc");

            // ParseDateImmediate ignores invalid input; ParseDate also reverts to FormattedValue for non-nullable.
            // Value should remain unchanged.
            Assert.Equal(initialDate, component.Instance.Value);
        }

        [Fact]
        public void DatePicker_Immediate_Raises_ValueChangedOnValidInput()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var raised = false;
            DateTime? newValue = null;
            var testDate = new DateTime(2025, 3, 20);

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime?>>(parameters =>
            {
                parameters.Add(p => p.Immediate, true);
                parameters.Add(p => p.ValueChanged, args => { raised = true; newValue = args; });
            });

            var inputElement = component.Find(".rz-inputtext");

            ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult(testDate.ToShortDateString());
            inputElement.Input(testDate.ToShortDateString());

            Assert.True(raised);
            Assert.Equal(testDate, newValue?.Date);
        }
        [Fact]
        public void DatePicker_NonGregorianCalendar_Renders_CorrectDayNumbers()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            // Thai Buddhist calendar: same months/days as Gregorian, year offset by +543
            var thaiCulture = new CultureInfo("th-TH");
            var testDate = new DateTime(2024, 4, 3);

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, testDate);
                parameters.Add(p => p.Culture, thaiCulture);
                parameters.Add(p => p.Inline, true);
            });

            var dayCells = component.FindAll("td span.rz-state-default");
            var dayNumbers = dayCells.Select(c => c.TextContent.Trim()).Where(t => !string.IsNullOrEmpty(t)).ToList();

            // April has 30 days — day numbers 1-30 should appear
            Assert.Contains("1", dayNumbers);
            Assert.Contains("3", dayNumbers);
            Assert.Contains("30", dayNumbers);
        }

        [Fact]
        public void DatePicker_NonGregorianCalendar_ActiveDay_IsCorrect()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var thaiCulture = new CultureInfo("th-TH");
            // April 3, 2024 Gregorian = day 3 in Thai Buddhist calendar (same day)
            var testDate = new DateTime(2024, 4, 3);

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, testDate);
                parameters.Add(p => p.Culture, thaiCulture);
                parameters.Add(p => p.Inline, true);
            });

            var activeDay = component.Find("span.rz-state-active");
            Assert.Equal("3", activeDay.TextContent.Trim());
        }

        [Fact]
        public void DatePicker_NonGregorianCalendar_MonthDropdown_ShowsCorrectMonth()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var thaiCulture = new CultureInfo("th-TH");
            var testDate = new DateTime(2024, 4, 3);

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, testDate);
                parameters.Add(p => p.Culture, thaiCulture);
                parameters.Add(p => p.Inline, true);
            });

            // Month dropdown should show the Thai name for April (month 4)
            var monthDropdown = component.Find(".rz-calendar-month-dropdown");
            var monthName = thaiCulture.DateTimeFormat.GetMonthName(4);
            Assert.Contains(monthName, monthDropdown.InnerHtml);
        }

        [Fact]
        public void DatePicker_NonGregorianCalendar_YearDropdown_ShowsCalendarYear()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var thaiCulture = new CultureInfo("th-TH");
            // 2024 Gregorian = 2567 Thai Buddhist
            var testDate = new DateTime(2024, 4, 3);

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, testDate);
                parameters.Add(p => p.Culture, thaiCulture);
                parameters.Add(p => p.Inline, true);
            });

            var yearDropdown = component.Find(".rz-calendar-year-dropdown");
            Assert.Contains("2567", yearDropdown.InnerHtml);
        }

        [Fact]
        public void DatePicker_NonGregorianCalendar_OtherMonthDays_MarkedCorrectly()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var thaiCulture = new CultureInfo("th-TH");
            var testDate = new DateTime(2024, 4, 3);

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, testDate);
                parameters.Add(p => p.Culture, thaiCulture);
                parameters.Add(p => p.Inline, true);
            });

            var currentMonthCells = component.FindAll("td:not(.rz-calendar-other-month):not(.rz-calendar-week-number)");
            Assert.True(currentMonthCells.Count > 0);
        }

        [Fact]
        public void DatePicker_NonGregorianCalendar_DayClick_SetsCorrectValue()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var thaiCulture = new CultureInfo("th-TH");
            var testDate = new DateTime(2024, 4, 1);

            DateTime? changedValue = null;
            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, testDate);
                parameters.Add(p => p.Culture, thaiCulture);
                parameters.Add(p => p.Inline, true);
                parameters.Add(p => p.ValueChanged, (DateTime v) => { changedValue = v; });
            });

            // Click on day "10"
            var dayCells = component.FindAll("td[role='button'] span.rz-state-default");
            var day10 = dayCells.FirstOrDefault(c => c.TextContent.Trim() == "10" && !c.ClassList.Contains("rz-calendar-other-month"));

            if (day10 != null)
            {
                day10.ParentElement!.Click();

                Assert.NotNull(changedValue);
                Assert.Equal(new DateTime(2024, 4, 10).Date, changedValue!.Value.Date);
            }
        }

        [Fact]
        public void DatePicker_NonGregorianCalendar_FormattedValue_UsesCalendarYear()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var thaiCulture = new CultureInfo("th-TH");
            var testDate = new DateTime(2024, 4, 3);

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, testDate);
                parameters.Add(p => p.Culture, thaiCulture);
            });

            // The formatted value should use Thai Buddhist year 2567
            var formattedValue = component.Instance.FormattedValue;
            Assert.False(string.IsNullOrEmpty(formattedValue));
            Assert.Contains("2567", formattedValue);
        }

        [Fact]
        public void DatePicker_ShowTime_MinToday_DoesNotDisableToday()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var today = DateTime.Now;
            var min = new DateTime(today.Year, today.Month, today.Day, 14, 0, 0);

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, today);
                parameters.Add(p => p.ShowTime, true);
                parameters.Add(p => p.Min, min);
            });

            // Find today's cell - it should not have rz-state-disabled
            var todayCell = component.FindAll("td span.rz-calendar-today");
            Assert.NotEmpty(todayCell);
            Assert.DoesNotContain("rz-state-disabled", todayCell.First().ClassName);
        }

        [Fact]
        public void DatePicker_NoShowTime_MinToday_DisablesToday()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var today = DateTime.Now;
            var min = new DateTime(today.Year, today.Month, today.Day, 14, 0, 0);

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, today);
                parameters.Add(p => p.Min, min);
            });

            // Without ShowTime, today should be disabled since midnight < 14:00
            var todayCell = component.FindAll("td span.rz-calendar-today");
            Assert.NotEmpty(todayCell);
            Assert.Contains("rz-state-disabled", todayCell.First().ClassName);
        }

        [Fact]
        public void DatePicker_Calendar_HasGridRoles_AndAriaLabel()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, new DateTime(2024, 6, 12));
            });

            var grid = component.Find("table.rz-calendar-view");
            Assert.Equal("grid", grid.GetAttribute("role"));
            Assert.False(string.IsNullOrEmpty(grid.GetAttribute("aria-label")));

            Assert.NotEmpty(component.FindAll("table.rz-calendar-view tr[role='row']"));
            Assert.NotEmpty(component.FindAll("table.rz-calendar-view td[role='gridcell']"));
            Assert.Empty(component.FindAll("table.rz-calendar-view td[role='button']"));
        }

        [Fact]
        public void DatePicker_DayCell_HasAriaLabel_WithFullDate_AndSelectedState()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var selected = new DateTime(2024, 6, 12);
            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, selected);
            });

            var selectedCells = component.FindAll("td[role='gridcell'][aria-selected='true']");
            Assert.Single(selectedCells);

            var cell = selectedCells.First();
            var ariaLabel = cell.GetAttribute("aria-label");
            Assert.False(string.IsNullOrEmpty(ariaLabel));
            // long date pattern — weekday name should appear
            Assert.Contains(selected.ToString("dddd", CultureInfo.CurrentCulture), ariaLabel);
            Assert.Contains("selected", ariaLabel);
        }

        [Fact]
        public void DatePicker_FocusedDay_HasRovingTabindexZero()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
            {
                parameters.Add(p => p.Value, new DateTime(2024, 6, 12));
            });

            var tabbableCells = component.FindAll("td[role='gridcell'][tabindex='0']");
            Assert.Single(tabbableCells);

            var notTabbable = component.FindAll("td[role='gridcell'][tabindex='-1']");
            Assert.NotEmpty(notTabbable);
        }

        [Fact]
        public void DatePicker_CalendarViewContainer_IsNot_FocusTarget()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");

            var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();

            var wrapper = component.Find("div.rz-calendar-view-container");
            Assert.Null(wrapper.GetAttribute("tabindex"));
        }
    }
}
