@namespace Lumeo

@if (Label is not null && !InsideFormField)
{
    <label class="text-sm font-medium leading-none">
        @Label
        @if (Required)
        {
            <span class="text-destructive ms-0.5">*</span>
        }
    </label>
}

@if (Variant == DatePickerVariant.Wheel)
{
    @* iOS-style 3-column wheel picker. v1 limitations:
       - Only honours single-date Mode (Range/Month/Year fall back to a day/month/year wheel).
       - No Presets sidebar.
       - ShowTimePicker is supported via a stacked TimeWheelPicker below the date wheel.
    *@
    @if (Inline)
    {
        <div>
            <DateWheelPicker Value="@Value" ValueChanged="HandleDateSelected" Min="@MinDate" Max="@MaxDate" Culture="@Culture" />
            @if (ShowTimePicker)
            {
                <div class="border-t border-border/40 p-3">
                    <TimePicker Value="@Time" ValueChanged="HandleTimeChanged" Culture="@Culture" Variant="TimePicker.TimePickerVariant.Wheel" />
                </div>
            }
        </div>
    }
    else
    {
        <Popover @bind-Open="_isOpen">
            <PopoverTrigger>
                <button type="button"
                        name="@Name"
                        aria-required="@Required.ToString().ToLower()"
                        aria-invalid="@EffectiveInvalid.ToString().ToLower()"
                        class="@CssClass"
                        disabled="@Disabled"
                        @attributes="AdditionalAttributes">
                    <Blazicon Svg="Lucide.Calendar" class="me-2 h-4 w-4 shrink-0" />
                    @if (TriggerContent != null)
                    {
                        @TriggerContent
                    }
                    else if (Value.HasValue)
                    {
                        <span>@Value.Value.ToString(EffectiveFormat, EffectiveCulture)</span>
                    }
                    else
                    {
                        <span class="text-muted-foreground group-hover:text-accent-foreground">@EffectivePlaceholder</span>
                    }
                    @if (Clearable && HasValue)
                    {
                        <button type="button" class="ms-auto ps-1 hover:text-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1 rounded" aria-label="@L["Common.Clear"]" @onclick="HandleClear" @onclick:stopPropagation="true">
                            <Blazicon Svg="Lucide.X" class="h-3.5 w-3.5" />
                        </button>
                    }
                </button>
            </PopoverTrigger>
            <PopoverContent Align="PopoverContent.PopoverAlign.Start" Class="w-[260px] p-3">
                <DateWheelPicker Value="@Value" ValueChanged="HandleDateSelected" Min="@MinDate" Max="@MaxDate" Culture="@Culture" />
                @if (ShowTimePicker)
                {
                    <div class="border-t border-border/40 mt-3 pt-3">
                        <TimePicker Value="@Time" ValueChanged="HandleTimeChanged" Culture="@Culture" Variant="TimePicker.TimePickerVariant.Wheel" />
                    </div>
                }
            </PopoverContent>
        </Popover>
    }
}
else if (Inline)
{
        @if (Mode == DatePickerMode.Month)
        {
            <Calendar Value="@Value" ValueChanged="HandleDateSelected" MinDate="@MinDate" MaxDate="@MaxDate" IsDateDisabled="@IsDateDisabled" DateClass="@DateClass" DateTooltip="@DateTooltip" DateBadge="@DateBadge" InitialView="@Calendar.CalendarView.Months" FirstDayOfWeek="@FirstDayOfWeek" ShowWeekNumbers="@ShowWeekNumbers" />
        }
        else if (Mode == DatePickerMode.Year)
        {
            <Calendar Value="@Value" ValueChanged="HandleDateSelected" MinDate="@MinDate" MaxDate="@MaxDate" IsDateDisabled="@IsDateDisabled" DateClass="@DateClass" DateTooltip="@DateTooltip" DateBadge="@DateBadge" InitialView="@Calendar.CalendarView.Years" FirstDayOfWeek="@FirstDayOfWeek" ShowWeekNumbers="@ShowWeekNumbers" />
        }
        else if (Mode == DatePickerMode.Range)
        {
            @if (Presets != null && Presets.Count > 0)
            {
                <div class="flex">
                    <div class="flex flex-col gap-1 border-e border-border/40 p-2 min-w-[120px]">
                        @foreach (var preset in Presets)
                        {
                            var p = preset;
                            <button type="button" class="text-sm px-3 py-1.5 rounded-md text-left hover:bg-accent hover:text-accent-foreground transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1" @onclick="() => HandlePresetSelected(p)">
                                @p.Label
                            </button>
                        }
                    </div>
                    <Calendar IsRange="true" RangeStart="@RangeStart" RangeStartChanged="HandleRangeStartChanged"
                              RangeEnd="@RangeEnd" RangeEndChanged="HandleRangeEndChanged"
                              MinDate="@MinDate" MaxDate="@MaxDate" IsDateDisabled="@IsDateDisabled" DateClass="@DateClass" DateTooltip="@DateTooltip" DateBadge="@DateBadge" NumberOfMonths="@EffectiveNumberOfMonths" FirstDayOfWeek="@FirstDayOfWeek" ShowWeekNumbers="@ShowWeekNumbers" />
                </div>
            }
            else
            {
                <Calendar IsRange="true" RangeStart="@RangeStart" RangeStartChanged="HandleRangeStartChanged"
                          RangeEnd="@RangeEnd" RangeEndChanged="HandleRangeEndChanged"
                          MinDate="@MinDate" MaxDate="@MaxDate" IsDateDisabled="@IsDateDisabled" DateClass="@DateClass" DateTooltip="@DateTooltip" DateBadge="@DateBadge" NumberOfMonths="@EffectiveNumberOfMonths" FirstDayOfWeek="@FirstDayOfWeek" ShowWeekNumbers="@ShowWeekNumbers" />
            }
        }
        else
        {
            <div>
                @if (Presets != null && Presets.Count > 0)
                {
                    <div class="flex">
                        <div class="flex flex-col gap-1 border-e border-border/40 p-2 min-w-[120px]">
                            @foreach (var preset in Presets)
                            {
                                var p = preset;
                                <button type="button" class="text-sm px-3 py-1.5 rounded-md text-left hover:bg-accent hover:text-accent-foreground transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1" @onclick="() => HandlePresetSelected(p)">
                                    @p.Label
                                </button>
                            }
                        </div>
                        <Calendar Value="@Value" ValueChanged="HandleDateSelected" MinDate="@MinDate" MaxDate="@MaxDate" IsDateDisabled="@IsDateDisabled" DateClass="@DateClass" DateTooltip="@DateTooltip" DateBadge="@DateBadge" NumberOfMonths="@EffectiveNumberOfMonths" FirstDayOfWeek="@FirstDayOfWeek" ShowWeekNumbers="@ShowWeekNumbers" />
                    </div>
                }
                else
                {
                    <Calendar Value="@Value" ValueChanged="HandleDateSelected" MinDate="@MinDate" MaxDate="@MaxDate" IsDateDisabled="@IsDateDisabled" DateClass="@DateClass" DateTooltip="@DateTooltip" DateBadge="@DateBadge" NumberOfMonths="@EffectiveNumberOfMonths" FirstDayOfWeek="@FirstDayOfWeek" ShowWeekNumbers="@ShowWeekNumbers" />
                }

                @if (ShowTimePicker)
                {
                    <div class="border-t border-border/40 p-3">
                        <TimePicker Value="@Time" ValueChanged="HandleTimeChanged" Culture="@Culture" />
                    </div>
                }
            </div>
        }
}
else
{
    <Popover @bind-Open="_isOpen">
        <PopoverTrigger>
            <button type="button"
                    name="@Name"
                    aria-required="@Required.ToString().ToLower()"
                    aria-invalid="@EffectiveInvalid.ToString().ToLower()"
                    class="@CssClass"
                    disabled="@Disabled"
                    @attributes="AdditionalAttributes">
                <Blazicon Svg="Lucide.Calendar" class="me-2 h-4 w-4 shrink-0" />
                @if (TriggerContent != null)
                {
                    @TriggerContent
                }
                else if (Mode == DatePickerMode.Range)
                {
                    @if (RangeStart.HasValue && RangeEnd.HasValue)
                    {
                        <span>@RangeStart.Value.ToString(EffectiveFormat, EffectiveCulture) - @RangeEnd.Value.ToString(EffectiveFormat, EffectiveCulture)</span>
                    }
                    else if (RangeStart.HasValue)
                    {
                        <span>@RangeStart.Value.ToString(EffectiveFormat, EffectiveCulture) - ...</span>
                    }
                    else
                    {
                        <span class="text-muted-foreground group-hover:text-accent-foreground">@EffectivePlaceholder</span>
                    }
                }
                else if (Mode == DatePickerMode.Month)
                {
                    @if (Value.HasValue)
                    {
                        <span>@Value.Value.ToString("MMMM yyyy", EffectiveCulture)</span>
                    }
                    else
                    {
                        <span class="text-muted-foreground group-hover:text-accent-foreground">@EffectivePlaceholder</span>
                    }
                }
                else if (Mode == DatePickerMode.Year)
                {
                    @if (Value.HasValue)
                    {
                        <span>@Value.Value.Year</span>
                    }
                    else
                    {
                        <span class="text-muted-foreground group-hover:text-accent-foreground">@EffectivePlaceholder</span>
                    }
                }
                else
                {
                    @if (Value.HasValue)
                    {
                        <span>@Value.Value.ToString(EffectiveFormat, EffectiveCulture)</span>
                    }
                    else
                    {
                        <span class="text-muted-foreground group-hover:text-accent-foreground">@EffectivePlaceholder</span>
                    }
                }
    
                @if (Clearable && HasValue)
                {
                    <button type="button" class="ms-auto ps-1 hover:text-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1 rounded" aria-label="@L["Common.Clear"]" @onclick="HandleClear" @onclick:stopPropagation="true">
                        <Blazicon Svg="Lucide.X" class="h-3.5 w-3.5" />
                    </button>
                }
            </button>
        </PopoverTrigger>
        <PopoverContent Align="PopoverContent.PopoverAlign.Start" Class="w-auto p-0">
            @if (Mode == DatePickerMode.Month)
            {
                <Calendar Value="@Value" ValueChanged="HandleDateSelected" MinDate="@MinDate" MaxDate="@MaxDate" IsDateDisabled="@IsDateDisabled" DateClass="@DateClass" DateTooltip="@DateTooltip" DateBadge="@DateBadge" InitialView="@Calendar.CalendarView.Months" FirstDayOfWeek="@FirstDayOfWeek" ShowWeekNumbers="@ShowWeekNumbers" />
            }
            else if (Mode == DatePickerMode.Year)
            {
                <Calendar Value="@Value" ValueChanged="HandleDateSelected" MinDate="@MinDate" MaxDate="@MaxDate" IsDateDisabled="@IsDateDisabled" DateClass="@DateClass" DateTooltip="@DateTooltip" DateBadge="@DateBadge" InitialView="@Calendar.CalendarView.Years" FirstDayOfWeek="@FirstDayOfWeek" ShowWeekNumbers="@ShowWeekNumbers" />
            }
            else if (Mode == DatePickerMode.Range)
            {
                @if (Presets != null && Presets.Count > 0)
                {
                    <div class="flex">
                        <div class="flex flex-col gap-1 border-e border-border/40 p-2 min-w-[120px]">
                            @foreach (var preset in Presets)
                            {
                                var p = preset;
                                <button type="button" class="text-sm px-3 py-1.5 rounded-md text-left hover:bg-accent hover:text-accent-foreground transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1" @onclick="() => HandlePresetSelected(p)">
                                    @p.Label
                                </button>
                            }
                        </div>
                        <Calendar IsRange="true" RangeStart="@RangeStart" RangeStartChanged="HandleRangeStartChanged"
                                  RangeEnd="@RangeEnd" RangeEndChanged="HandleRangeEndChanged"
                                  MinDate="@MinDate" MaxDate="@MaxDate" IsDateDisabled="@IsDateDisabled" DateClass="@DateClass" DateTooltip="@DateTooltip" DateBadge="@DateBadge" NumberOfMonths="@EffectiveNumberOfMonths" FirstDayOfWeek="@FirstDayOfWeek" ShowWeekNumbers="@ShowWeekNumbers" />
                    </div>
                }
                else
                {
                    <Calendar IsRange="true" RangeStart="@RangeStart" RangeStartChanged="HandleRangeStartChanged"
                              RangeEnd="@RangeEnd" RangeEndChanged="HandleRangeEndChanged"
                              MinDate="@MinDate" MaxDate="@MaxDate" IsDateDisabled="@IsDateDisabled" DateClass="@DateClass" DateTooltip="@DateTooltip" DateBadge="@DateBadge" NumberOfMonths="@EffectiveNumberOfMonths" FirstDayOfWeek="@FirstDayOfWeek" ShowWeekNumbers="@ShowWeekNumbers" />
                }
            }
            else
            {
                <div>
                    @if (Presets != null && Presets.Count > 0)
                    {
                        <div class="flex">
                            <div class="flex flex-col gap-1 border-e border-border/40 p-2 min-w-[120px]">
                                @foreach (var preset in Presets)
                                {
                                    var p = preset;
                                    <button type="button" class="text-sm px-3 py-1.5 rounded-md text-left hover:bg-accent hover:text-accent-foreground transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1" @onclick="() => HandlePresetSelected(p)">
                                        @p.Label
                                    </button>
                                }
                            </div>
                            <Calendar Value="@Value" ValueChanged="HandleDateSelected" MinDate="@MinDate" MaxDate="@MaxDate" IsDateDisabled="@IsDateDisabled" DateClass="@DateClass" DateTooltip="@DateTooltip" DateBadge="@DateBadge" NumberOfMonths="@EffectiveNumberOfMonths" FirstDayOfWeek="@FirstDayOfWeek" ShowWeekNumbers="@ShowWeekNumbers" />
                        </div>
                    }
                    else
                    {
                        <Calendar Value="@Value" ValueChanged="HandleDateSelected" MinDate="@MinDate" MaxDate="@MaxDate" IsDateDisabled="@IsDateDisabled" DateClass="@DateClass" DateTooltip="@DateTooltip" DateBadge="@DateBadge" NumberOfMonths="@EffectiveNumberOfMonths" FirstDayOfWeek="@FirstDayOfWeek" ShowWeekNumbers="@ShowWeekNumbers" />
                    }
    
                    @if (ShowTimePicker)
                    {
                        <div class="border-t border-border/40 p-3">
                            <TimePicker Value="@Time" ValueChanged="HandleTimeChanged" Culture="@Culture" />
                        </div>
                    }
                </div>
            }
        </PopoverContent>
    </Popover>
}

@if (!InsideFormField)
{
    @if (EffectiveInvalid && ErrorText is not null)
    {
        <p class="text-xs text-destructive">@ErrorText</p>
    }
    else if (HelperText is not null)
    {
        <p class="text-xs text-muted-foreground">@HelperText</p>
    }
}

@inject Lumeo.Services.Localization.ILumeoLocalizer L

@code {
    public enum DatePickerMode { Single, Range, Month, Year }

    /// <summary>
    /// Visual style of the picker.
    /// <see cref="DatePickerVariant.Calendar"/> renders the existing month-grid Calendar (default).
    /// <see cref="DatePickerVariant.Wheel"/> renders three iOS-style snap-scrolling
    /// columns (day / month / year) — designed for mobile / touch UIs.
    /// </summary>
    public enum DatePickerVariant { Calendar, Wheel }

    /// <summary>Picker visual style. Defaults to <see cref="DatePickerVariant.Calendar"/>.</summary>
    [Parameter] public DatePickerVariant Variant { get; set; } = DatePickerVariant.Calendar;

    [CascadingParameter] private FormField.FormFieldContext? FormField { get; set; }

    [Parameter] public bool Required { get; set; }
    [Parameter] public bool Invalid { get; set; }
    [Parameter] public string? ErrorText { get; set; }
    [Parameter] public string? HelperText { get; set; }
    [Parameter] public string? Label { get; set; }
    [Parameter] public string? Name { get; set; }

    private bool EffectiveInvalid => Invalid || FormField?.HasError == true;
    private bool InsideFormField => FormField is not null;

    [Parameter] public DateOnly? Value { get; set; }
    [Parameter] public EventCallback<DateOnly?> ValueChanged { get; set; }

    /// <summary>
    /// Optional DateTime? binding. When set, takes precedence over <see cref="Value"/>.
    /// Changes fire both <see cref="ValueChanged"/> (DateOnly?) and <see cref="DateTimeValueChanged"/> (DateTime? at midnight).
    /// </summary>
    [Parameter] public DateTime? DateTimeValue { get; set; }
    [Parameter] public EventCallback<DateTime?> DateTimeValueChanged { get; set; }

    [Parameter] public string? Placeholder { get; set; }

    private string EffectivePlaceholder => Placeholder ?? (Mode == DatePickerMode.Range ? L["DateRange.Placeholder"] : L["DatePicker.Placeholder"]);
    /// <summary>
    /// Date format string. When null (default), the display falls back to the current
    /// culture's <c>ShortDatePattern</c> — so a <c>de-DE</c> user sees <c>15.03.2026</c>
    /// and an <c>en-US</c> user sees <c>3/15/2026</c> automatically.
    /// </summary>
    [Parameter] public string? Format { get; set; }

    /// <summary>
    /// Overrides <see cref="System.Globalization.CultureInfo.CurrentCulture"/> for
    /// parsing and formatting within this picker. Leave null to honour ASP.NET
    /// request localization.
    /// </summary>
    [Parameter] public System.Globalization.CultureInfo? Culture { get; set; }

    private System.Globalization.CultureInfo EffectiveCulture => Culture ?? System.Globalization.CultureInfo.CurrentCulture;
    private string EffectiveFormat => Format ?? EffectiveCulture.DateTimeFormat.ShortDatePattern;
    [Parameter] public DateOnly? MinDate { get; set; }
    [Parameter] public DateOnly? MaxDate { get; set; }
    /// <summary>Custom predicate to disable specific dates (e.g. booked rooms,
    /// unavailable slots). Returns <c>true</c> for dates that should not be
    /// selectable.</summary>
    [Parameter] public Func<DateTime, bool>? IsDateDisabled { get; set; }
    /// <summary>Custom CSS classes per day cell (e.g. status backgrounds for
    /// booking calendars).</summary>
    [Parameter] public Func<DateTime, string>? DateClass { get; set; }
    /// <summary>Per-date tooltip text rendered as the day cell's <c>title</c>
    /// attribute (native browser tooltip).</summary>
    [Parameter] public Func<DateTime, string?>? DateTooltip { get; set; }
    /// <summary>Per-date indicator slot (status dots, count badges, icons)
    /// rendered absolutely positioned in the bottom-right of the day cell.</summary>
    [Parameter] public Func<DateTime, RenderFragment?>? DateBadge { get; set; }
    [Parameter] public List<DatePickerPreset>? Presets { get; set; }
    [Parameter] public DatePickerMode Mode { get; set; } = DatePickerMode.Single;
    [Parameter] public DateOnly? RangeStart { get; set; }
    [Parameter] public EventCallback<DateOnly?> RangeStartChanged { get; set; }
    [Parameter] public DateOnly? RangeEnd { get; set; }
    [Parameter] public EventCallback<DateOnly?> RangeEndChanged { get; set; }
    [Parameter] public bool ShowTimePicker { get; set; }
    [Parameter] public TimeSpan? Time { get; set; }
    [Parameter] public EventCallback<TimeSpan?> TimeChanged { get; set; }
    [Parameter] public bool Disabled { get; set; }
    [Parameter] public bool Clearable { get; set; }
    [Parameter] public RenderFragment? TriggerContent { get; set; }
    [Parameter] public int NumberOfMonths { get; set; } = 1;

    /// <summary>
    /// Optional controlled-mode override for the popover open state. When non-null,
    /// the consumer holds the source of truth — the picker no longer mutates its
    /// internal state and instead raises <see cref="OpenChanged"/>. Use this when
    /// the picker lives inside a parent that re-renders heavily (e.g. an
    /// <c>EditForm</c> with active <c>DataAnnotationsValidator</c>) and the
    /// popover would otherwise close mid-click during a parameter cascade.
    /// When null (default), the picker falls back to its internal open state and
    /// behaves exactly as before.
    /// </summary>
    /// <summary>When true, renders the calendar inline (always visible) without a popover wrapper.</summary>
    [Parameter] public bool Inline { get; set; }

    /// <summary>Leftmost column day of week. Null means culture default.</summary>
    [Parameter] public DayOfWeek? FirstDayOfWeek { get; set; }

    /// <summary>When true, renders an ISO week-number column.</summary>
    [Parameter] public bool ShowWeekNumbers { get; set; }

    [Parameter] public bool? Open { get; set; }

    /// <summary>
    /// Fires whenever the popover would open or close. Pair with
    /// <see cref="Open"/> via <c>@bind-Open</c> for the controlled-mode pattern
    /// described above.
    /// </summary>
    [Parameter] public EventCallback<bool> OpenChanged { get; set; }

    [Parameter] public string? Class { get; set; }
    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }

    // Controlled-mode bridge: when Open is non-null, the parent owns the state and
    // we never write to _internalOpen — every "set" just raises OpenChanged so the
    // parent can decide. When Open is null, _internalOpen is the source of truth.
    private bool _internalOpen;
    private bool _isOpen
    {
        get => Open ?? _internalOpen;
        set => SetIsOpen(value);
    }

    private void SetIsOpen(bool value)
    {
        if (Open is null)
        {
            _internalOpen = value;
        }
        if (OpenChanged.HasDelegate)
        {
            _ = OpenChanged.InvokeAsync(value);
        }
    }

    private DateTime? _prevDateTimeValue;

    protected override void OnParametersSet()
    {
        // DateTimeValue wins when it changes — sync Value from it.
        if (DateTimeValue != _prevDateTimeValue)
        {
            _prevDateTimeValue = DateTimeValue;
            Value = DateTimeValue.HasValue ? DateOnly.FromDateTime(DateTimeValue.Value) : null;
        }
    }

    private int EffectiveNumberOfMonths => NumberOfMonths > 1 ? NumberOfMonths : (Mode == DatePickerMode.Range ? 2 : 1);

    private bool HasValue => Mode == DatePickerMode.Range ? RangeStart.HasValue : Value.HasValue;

    private string CssClass => $"group flex h-9 w-full items-center rounded-md border {(EffectiveInvalid ? "border-destructive ring-1 ring-destructive/20" : "border-input")} bg-transparent px-3 py-2 text-sm shadow-sm hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-50 {Class}".Trim();

    private async Task HandleDateSelected(DateOnly? date)
    {
        if (Mode == DatePickerMode.Month && date.HasValue)
        {
            Value = new DateOnly(date.Value.Year, date.Value.Month, 1);
            await ValueChanged.InvokeAsync(Value);
            await RaiseDateTimeValueChanged(Value);
            _isOpen = false;
        }
        else if (Mode == DatePickerMode.Year && date.HasValue)
        {
            Value = new DateOnly(date.Value.Year, 1, 1);
            await ValueChanged.InvokeAsync(Value);
            await RaiseDateTimeValueChanged(Value);
            _isOpen = false;
        }
        else
        {
            Value = date;
            await ValueChanged.InvokeAsync(date);
            await RaiseDateTimeValueChanged(date);
            if (!ShowTimePicker)
            {
                _isOpen = false;
            }
        }
    }

    private async Task HandleRangeStartChanged(DateOnly? date)
    {
        RangeStart = date;
        await RangeStartChanged.InvokeAsync(date);
    }

    private async Task HandleRangeEndChanged(DateOnly? date)
    {
        RangeEnd = date;
        await RangeEndChanged.InvokeAsync(date);
        if (date.HasValue)
        {
            _isOpen = false;
        }
    }

    private async Task HandleTimeChanged(TimeSpan? time)
    {
        Time = time;
        await TimeChanged.InvokeAsync(time);
    }

    private async Task HandlePresetSelected(DatePickerPreset preset)
    {
        Value = preset.Date;
        await ValueChanged.InvokeAsync(preset.Date);
        await RaiseDateTimeValueChanged(preset.Date);
        _isOpen = false;
    }

    private async Task HandleClear()
    {
        if (Mode == DatePickerMode.Range)
        {
            RangeStart = null;
            RangeEnd = null;
            await RangeStartChanged.InvokeAsync(null);
            await RangeEndChanged.InvokeAsync(null);
        }
        else
        {
            Value = null;
            await ValueChanged.InvokeAsync(null);
            await RaiseDateTimeValueChanged(null);
        }
        if (ShowTimePicker)
        {
            Time = null;
            await TimeChanged.InvokeAsync(null);
        }
    }

    private async Task RaiseDateTimeValueChanged(DateOnly? date)
    {
        if (DateTimeValueChanged.HasDelegate)
        {
            var dt = date.HasValue ? (DateTime?)date.Value.ToDateTime(TimeOnly.MinValue) : null;
            _prevDateTimeValue = dt;
            await DateTimeValueChanged.InvokeAsync(dt);
        }
    }

    public record DatePickerPreset(string Label, DateOnly Date);
}
