@namespace Lumeo
@inject Lumeo.Services.Localization.ILumeoLocalizer L
@inject Lumeo.Services.IComponentInteropService Interop
@implements IAsyncDisposable

<div class="@CssClass" @attributes="AdditionalAttributes">
    @if (NumberOfMonths == 1)
    {
        @RenderMonth(DisplayDate)
    }
    else
    {
        <div class="flex gap-4">
            @for (var i = 0; i < NumberOfMonths; i++)
            {
                var monthDate = DisplayDate.AddMonths(i);
                <div>
                    @RenderMonth(monthDate, showPrev: i == 0, showNext: i == NumberOfMonths - 1)
                </div>
            }
        </div>
    }
</div>

@code {
    [Parameter] public DateOnly? Value { get; set; }
    [Parameter] public EventCallback<DateOnly?> ValueChanged { get; set; }
    [Parameter] public DateOnly? MinDate { get; set; }
    [Parameter] public DateOnly? MaxDate { get; set; }
    [Parameter] public bool IsRange { get; set; }
    [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 int NumberOfMonths { get; set; } = 1;
    [Parameter] public bool ShowWeekNumbers { get; set; }
    [Parameter] public bool ShowYearPicker { get; set; }
    [Parameter] public CalendarView InitialView { get; set; } = CalendarView.Days;
    [Parameter] public Func<DateTime, bool>? IsDateDisabled { get; set; }
    [Parameter] public Func<DateTime, string>? DateClass { get; set; }
    /// <summary>Optional per-date tooltip text. Returned string becomes the
    /// day cell's <c>title</c> attribute (native browser tooltip on hover).
    /// Return <c>null</c> or empty for no tooltip on that date. Useful for
    /// booking calendars: "Booked by John, 14:00–17:00" or "Sold out".</summary>
    [Parameter] public Func<DateTime, string?>? DateTooltip { get; set; }
    /// <summary>Optional per-date indicator slot. Returns a <c>RenderFragment</c>
    /// rendered absolutely positioned in the bottom-right of the day cell —
    /// good for status dots, count badges, icons. Return <c>null</c> for no
    /// badge on that date. The slot is non-interactive
    /// (<c>pointer-events-none</c>) so clicks still hit the day button.</summary>
    [Parameter] public Func<DateTime, RenderFragment?>? DateBadge { get; set; }
    /// <summary>The day of the week to render in the leftmost column.
    /// When <c>null</c> (default), uses
    /// <c>CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek</c> —
    /// so de-DE / fr-FR / etc. start on Monday automatically, en-US on
    /// Sunday. Pass an explicit <see cref="DayOfWeek"/> to override.</summary>
    [Parameter] public DayOfWeek? FirstDayOfWeek { get; set; }
    /// <summary>When <c>true</c> (default), users can swipe left/right on the
    /// day grid to navigate to the next or previous month on touch devices.
    /// Vertical page scroll is unaffected (<c>touch-action: pan-y</c>).
    /// Set to <c>false</c> to disable swipe navigation.</summary>
    [Parameter] public bool SwipeEnabled { get; set; } = true;

    private readonly string _elementId = $"cal-grid-{Guid.NewGuid():N}";

    private DayOfWeek EffectiveFirstDayOfWeek =>
        FirstDayOfWeek ?? System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
    [Parameter] public string? Class { get; set; }
    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }

    private DateOnly DisplayDate { get; set; }
    private DateOnly? _hoverDate;
    private CalendarView _currentView = CalendarView.Days;
    private bool _swipeAttached;

    private string CssClass => $"p-3 {Class}".Trim();

    protected override void OnInitialized()
    {
        _currentView = InitialView;
        DisplayDate = (IsRange ? RangeStart : Value) ?? DateOnly.FromDateTime(DateTime.Today);
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!SwipeEnabled || NumberOfMonths != 1) return;

        // Attach swipe only in Days view — the day grid element only exists in
        // that view. When the user switches to Months/Years and back, the grid
        // div is recreated so we must re-register on each Days → hidden → Days
        // transition rather than once on first render.
        var shouldAttach = _currentView == CalendarView.Days;
        if (shouldAttach && !_swipeAttached)
        {
            _swipeAttached = true;
            try { await Interop.RegisterHorizontalSwipe(_elementId, OnSwipeAsync); }
            catch (JSDisconnectedException) { _swipeAttached = false; }
            catch (InvalidOperationException) { _swipeAttached = false; } // JS unavailable during prerender
        }
        else if (!shouldAttach && _swipeAttached)
        {
            _swipeAttached = false;
            try { await Interop.UnregisterHorizontalSwipe(_elementId); }
            catch (JSDisconnectedException) { }
        }
    }

    private Task OnSwipeAsync(string direction)
    {
        if (_currentView != CalendarView.Days) return Task.CompletedTask;
        if (direction == "next" && !IsNextMonthDisabled)
            NextMonth();
        else if (direction == "prev" && !IsPrevMonthDisabled)
            PreviousMonth();
        StateHasChanged();
        return Task.CompletedTask;
    }

    public async ValueTask DisposeAsync()
    {
        if (!_swipeAttached) return;
        try { await Interop.UnregisterHorizontalSwipe(_elementId); }
        catch (Microsoft.JSInterop.JSDisconnectedException) { }
    }

    private bool IsPrevMonthDisabled =>
        MinDate.HasValue && new DateOnly(DisplayDate.Year, DisplayDate.Month, 1) <= new DateOnly(MinDate.Value.Year, MinDate.Value.Month, 1);

    private bool IsNextMonthDisabled
    {
        get
        {
            var lastPanel = DisplayDate.AddMonths(NumberOfMonths - 1);
            return MaxDate.HasValue && new DateOnly(lastPanel.Year, lastPanel.Month, 1) >= new DateOnly(MaxDate.Value.Year, MaxDate.Value.Month, 1);
        }
    }

    private bool IsDisabled(DateOnly day) =>
        (MinDate.HasValue && day < MinDate.Value) || (MaxDate.HasValue && day > MaxDate.Value)
        || (IsDateDisabled != null && IsDateDisabled(day.ToDateTime(TimeOnly.MinValue)));

    private string[] GetDayHeaders()
    {
        // .NET's ShortestDayNames collapses to 1 letter per culture
        // ("S,M,T,W,T,F,S"), which is ambiguous in any language. Use
        // AbbreviatedDayNames and take the first two letters of each so we
        // get culture-correct, distinguishable headers:
        //   en-US → Su/Mo/Tu/We/Th/Fr/Sa
        //   de-DE → So/Mo/Di/Mi/Do/Fr/Sa
        //   fr-FR → di/lu/ma/me/je/ve/sa
        var abbr = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.AbbreviatedDayNames;
        var startIndex = (int)EffectiveFirstDayOfWeek;
        var result = new string[7];
        for (var i = 0; i < 7; i++)
        {
            var raw = abbr[(startIndex + i) % 7];
            // Strip trailing punctuation (de-DE returns "So.", "Mo." …) before slicing.
            var letters = new string(raw.TakeWhile(char.IsLetter).ToArray());
            result[i] = letters.Length >= 2 ? letters[..2] : letters;
        }
        return result;
    }

    private List<DateOnly> GetCalendarDays(DateOnly monthDate)
    {
        var days = new List<DateOnly>();
        var firstOfMonth = new DateOnly(monthDate.Year, monthDate.Month, 1);
        var startOffset = ((int)firstOfMonth.DayOfWeek - (int)EffectiveFirstDayOfWeek + 7) % 7;
        var start = firstOfMonth.AddDays(-startOffset);

        for (var i = 0; i < 42; i++)
        {
            days.Add(start.AddDays(i));
        }
        return days;
    }

    private int GetIsoWeekNumber(DateOnly date)
    {
        var dt = date.ToDateTime(TimeOnly.MinValue);
        var day = System.Globalization.CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(dt);
        if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday)
        {
            dt = dt.AddDays(3);
        }
        return System.Globalization.CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(dt,
            System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
    }

    private int GetDecadeStart(int year) => year / 10 * 10;

    // Wrapper-div classes that render the connecting range bar BEHIND the
    // day button (2.1.4). Previously each in-range day's button had its own
    // rounded-md bg-accent rectangle, producing a pearl-necklace look with
    // visual gaps between days. The wrapper carries the bar background and
    // edge rounding so adjacent in-range cells touch seamlessly.
    //
    // Returns empty string for non-Range mode or days outside the range so
    // the wrapper has no styling and the button keeps its original look.
    //
    // Edge cases:
    // - First column on a row (col == 0) gets rounded-l-md so the bar starts
    //   with a rounded cap when the range wraps to a new row.
    // - Last column on a row (col == 6) gets rounded-r-md likewise.
    // - The range-start and range-end days get rounded-l-md / rounded-r-md
    //   so the bar terminates with a soft cap behind the primary circle.
    // - Hover during in-flight selection (RangeStart set, RangeEnd null,
    //   _hoverDate carries the preview) extends the bar dynamically.
    private string RangeBarClasses(DateOnly day, DateOnly monthDate, int col)
    {
        if (!IsRange) return "";

        var effectiveEnd = RangeEnd ?? (RangeStart.HasValue && _hoverDate.HasValue ? _hoverDate : null);
        var rangeFrom = RangeStart;
        var rangeTo = effectiveEnd;
        if (rangeFrom.HasValue && rangeTo.HasValue && rangeTo < rangeFrom)
        {
            (rangeFrom, rangeTo) = (rangeTo, rangeFrom);
        }
        if (!rangeFrom.HasValue || !rangeTo.HasValue) return "";
        if (day < rangeFrom.Value || day > rangeTo.Value) return "";

        var isStart = day == rangeFrom.Value;
        var isEnd = day == rangeTo.Value;

        // flex layout keeps the button (h-8 w-8) centered horizontally so the
        // surrounding bar extends to the cell edges on either side.
        var parts = new List<string> { "flex items-center justify-center bg-accent" };
        if (isStart || col == 0) parts.Add("rounded-l-md");
        if (isEnd || col == 6) parts.Add("rounded-r-md");
        return string.Join(" ", parts);
    }

    private string DayClasses(DateOnly day, DateOnly monthDate)
    {
        var isCurrentMonth = day.Month == monthDate.Month;
        var isDisabled = IsDisabled(day);
        var isSelected = !IsRange && Value.HasValue && day == Value.Value;
        var isToday = day == DateOnly.FromDateTime(DateTime.Today);

        // 'relative' kept on every day so DateBadge slots can absolute-position inside.
        var baseClass = "relative inline-flex items-center justify-center rounded-md h-8 w-8 text-sm transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1";
        var extraClass = DateClass != null ? DateClass(day.ToDateTime(TimeOnly.MinValue)) : "";

        if (isDisabled)
            return $"{baseClass} pointer-events-none opacity-50 {extraClass}".Trim();

        if (IsRange)
        {
            var isRangeStart = RangeStart.HasValue && day == RangeStart.Value;
            var isRangeEnd = RangeEnd.HasValue && day == RangeEnd.Value;

            var effectiveEnd = RangeEnd ?? (RangeStart.HasValue && _hoverDate.HasValue ? _hoverDate : null);
            var rangeFrom = RangeStart;
            var rangeTo = effectiveEnd;
            if (rangeFrom.HasValue && rangeTo.HasValue && rangeTo < rangeFrom)
            {
                (rangeFrom, rangeTo) = (rangeTo, rangeFrom);
            }

            var inRange = rangeFrom.HasValue && rangeTo.HasValue && day > rangeFrom.Value && day < rangeTo.Value;

            if (isRangeStart || isRangeEnd)
                return $"{baseClass} bg-primary text-primary-foreground font-medium {extraClass}".Trim();
            if (inRange)
                return $"{baseClass} bg-accent text-accent-foreground {extraClass}".Trim();
            if (isToday)
                return $"{baseClass} bg-accent text-accent-foreground font-medium {extraClass}".Trim();
            if (!isCurrentMonth)
                return $"{baseClass} text-muted-foreground/50 hover:bg-accent hover:text-accent-foreground {extraClass}".Trim();
            return $"{baseClass} hover:bg-accent hover:text-accent-foreground {extraClass}".Trim();
        }

        if (isSelected)
            return $"{baseClass} bg-primary text-primary-foreground font-medium {extraClass}".Trim();
        if (isToday)
            return $"{baseClass} bg-accent text-accent-foreground font-medium {extraClass}".Trim();
        if (!isCurrentMonth)
            return $"{baseClass} text-muted-foreground/50 hover:bg-accent hover:text-accent-foreground {extraClass}".Trim();
        return $"{baseClass} hover:bg-accent hover:text-accent-foreground {extraClass}".Trim();
    }

    private async Task SelectDay(DateOnly day)
    {
        if (IsDisabled(day)) return;

        if (IsRange)
        {
            if (!RangeStart.HasValue || (RangeStart.HasValue && RangeEnd.HasValue))
            {
                await RangeStartChanged.InvokeAsync(day);
                await RangeEndChanged.InvokeAsync(null);
                _hoverDate = null;
            }
            else
            {
                var start = RangeStart.Value;
                if (day < start)
                {
                    await RangeStartChanged.InvokeAsync(day);
                    await RangeEndChanged.InvokeAsync(start);
                }
                else
                {
                    await RangeEndChanged.InvokeAsync(day);
                }
                _hoverDate = null;
            }
            DisplayDate = new DateOnly(day.Year, day.Month, 1);
        }
        else
        {
            DisplayDate = new DateOnly(day.Year, day.Month, 1);
            Value = day;
            await ValueChanged.InvokeAsync(day);
        }
    }

    private void OnDayHover(DateOnly day)
    {
        if (IsRange && RangeStart.HasValue && !RangeEnd.HasValue)
        {
            _hoverDate = day;
        }
    }

    private void PreviousMonth() => DisplayDate = DisplayDate.AddMonths(-1);
    private void NextMonth() => DisplayDate = DisplayDate.AddMonths(1);

    private void SwitchToMonthView() => _currentView = CalendarView.Months;
    private void SwitchToYearView() => _currentView = CalendarView.Years;

    private void SelectMonth(int month)
    {
        DisplayDate = new DateOnly(DisplayDate.Year, month, 1);
        _currentView = CalendarView.Days;
    }

    private void SelectYear(int year)
    {
        DisplayDate = new DateOnly(year, DisplayDate.Month, 1);
        _currentView = CalendarView.Months;
    }

    private void PreviousDecade()
    {
        var decadeStart = GetDecadeStart(DisplayDate.Year);
        DisplayDate = new DateOnly(Math.Max(1, decadeStart - 10), DisplayDate.Month, 1);
    }

    private void NextDecade()
    {
        var decadeStart = GetDecadeStart(DisplayDate.Year);
        DisplayDate = new DateOnly(Math.Min(9999, decadeStart + 10), DisplayDate.Month, 1);
    }

    private void PreviousYear()
    {
        DisplayDate = new DateOnly(Math.Max(1, DisplayDate.Year - 1), DisplayDate.Month, 1);
    }

    private void NextYear()
    {
        DisplayDate = new DateOnly(Math.Min(9999, DisplayDate.Year + 1), DisplayDate.Month, 1);
    }

    private RenderFragment RenderMonth(DateOnly monthDate, bool showPrev = true, bool showNext = true) => __builder =>
    {
        @* Fixed 224px content width matches the 7-column day grid (7 × w-8 = 224px),
           so the Days, Months and Years views all render at the same outer width.
           Without this the Months / Years 3-col grid (w-full cells) would collapse
           to its natural width and the picker would visibly resize between views. *@
        <div class="w-[224px] flex flex-col">
        <div class="flex items-center justify-between px-1 mb-2">
            @if (showPrev)
            {
                if (_currentView == CalendarView.Years)
                {
                    <button type="button" aria-label="@L["Calendar.PrevYear"]" class="inline-flex items-center justify-center rounded-md h-11 w-11 sm:h-7 sm:w-7 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1" @onclick="PreviousDecade">
                        <Blazicon Svg="Lucide.ChevronLeft" class="h-4 w-4" />
                    </button>
                }
                else if (_currentView == CalendarView.Months)
                {
                    <button type="button" aria-label="@L["Calendar.PrevYear"]" class="inline-flex items-center justify-center rounded-md h-11 w-11 sm:h-7 sm:w-7 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1" @onclick="PreviousYear">
                        <Blazicon Svg="Lucide.ChevronLeft" class="h-4 w-4" />
                    </button>
                }
                else
                {
                    <button type="button" aria-label="@L["Calendar.PrevMonth"]" class="inline-flex items-center justify-center rounded-md h-11 w-11 sm:h-7 sm:w-7 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-30" disabled="@IsPrevMonthDisabled" @onclick="PreviousMonth">
                        <Blazicon Svg="Lucide.ChevronLeft" class="h-4 w-4" />
                    </button>
                }
            }
            else
            {
                <div class="w-11 sm:w-7"></div>
            }

            @if (_currentView == CalendarView.Years)
            {
                var decadeStart = GetDecadeStart(monthDate.Year);
                <span class="text-sm font-medium">@decadeStart - @(decadeStart + 9)</span>
            }
            else if (_currentView == CalendarView.Months)
            {
                <button type="button" class="text-sm font-medium hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1 rounded-md px-2 py-1" @onclick="SwitchToYearView">
                    @monthDate.Year
                </button>
            }
            else
            {
                @* Single combined click target: 1st click → Months view, 2nd click
                   (now in Months view, on the year header) → Years view. Cleaner
                   than two adjacent buttons that look identical but route to
                   different pickers. *@
                <button type="button" class="text-sm font-medium hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1 rounded-md px-2 py-1" @onclick="SwitchToMonthView">
                    @monthDate.ToString("MMMM") @monthDate.Year
                </button>
            }

            @if (showNext)
            {
                if (_currentView == CalendarView.Years)
                {
                    <button type="button" aria-label="@L["Calendar.NextYear"]" class="inline-flex items-center justify-center rounded-md h-11 w-11 sm:h-7 sm:w-7 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1" @onclick="NextDecade">
                        <Blazicon Svg="Lucide.ChevronRight" class="h-4 w-4" />
                    </button>
                }
                else if (_currentView == CalendarView.Months)
                {
                    <button type="button" aria-label="@L["Calendar.NextYear"]" class="inline-flex items-center justify-center rounded-md h-11 w-11 sm:h-7 sm:w-7 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1" @onclick="NextYear">
                        <Blazicon Svg="Lucide.ChevronRight" class="h-4 w-4" />
                    </button>
                }
                else
                {
                    <button type="button" aria-label="@L["Calendar.NextMonth"]" class="inline-flex items-center justify-center rounded-md h-11 w-11 sm:h-7 sm:w-7 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-30" disabled="@IsNextMonthDisabled" @onclick="NextMonth">
                        <Blazicon Svg="Lucide.ChevronRight" class="h-4 w-4" />
                    </button>
                }
            }
            else
            {
                <div class="w-11 sm:w-7"></div>
            }
        </div>

        @if (_currentView == CalendarView.Years)
        {
            var decadeStart = GetDecadeStart(monthDate.Year);
            <div class="grid grid-cols-3 gap-2 p-1">
                @for (var y = decadeStart; y < decadeStart + 12; y++)
                {
                    var year = y;
                    var isCurrentYear = year == DateTime.Today.Year;
                    var isSelectedYear = year == monthDate.Year;
                    var yearClass = isSelectedYear
                        ? "inline-flex items-center justify-center rounded-md h-9 w-full text-sm font-medium bg-primary text-primary-foreground"
                        : isCurrentYear
                            ? "inline-flex items-center justify-center rounded-md h-9 w-full text-sm font-medium bg-accent text-accent-foreground"
                            : "inline-flex items-center justify-center rounded-md h-9 w-full text-sm hover:bg-accent hover:text-accent-foreground cursor-pointer focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1";
                    <button type="button" class="@yearClass" @onclick="() => SelectYear(year)">@year</button>
                }
            </div>
        }
        else if (_currentView == CalendarView.Months)
        {
            var monthNames = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.AbbreviatedMonthNames;
            <div class="grid grid-cols-3 gap-2 p-1">
                @for (var m = 1; m <= 12; m++)
                {
                    var month = m;
                    var isCurrentMonth = month == DateTime.Today.Month && monthDate.Year == DateTime.Today.Year;
                    var isSelectedMonth = month == monthDate.Month;
                    var monthClass = isSelectedMonth
                        ? "inline-flex items-center justify-center rounded-md h-9 w-full text-sm font-medium bg-primary text-primary-foreground"
                        : isCurrentMonth
                            ? "inline-flex items-center justify-center rounded-md h-9 w-full text-sm font-medium bg-accent text-accent-foreground"
                            : "inline-flex items-center justify-center rounded-md h-9 w-full text-sm hover:bg-accent hover:text-accent-foreground cursor-pointer focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1";
                    <button type="button" class="@monthClass" @onclick="() => SelectMonth(month)">@monthNames[month - 1]</button>
                }
            </div>
        }
        else
        {
            var gridCols = ShowWeekNumbers ? "grid-cols-8" : "grid-cols-7";
            var gridId = (SwipeEnabled && NumberOfMonths == 1) ? _elementId : null;
            <div id="@gridId" class="grid @gridCols gap-0 text-center">
                @if (ShowWeekNumbers)
                {
                    <div class="text-xs font-medium text-muted-foreground py-1">#</div>
                }
                @foreach (var day in GetDayHeaders())
                {
                    <div class="text-xs font-medium text-muted-foreground py-1">@day</div>
                }

                @{ var calDays = GetCalendarDays(monthDate); }
                @for (var row = 0; row < 6; row++)
                {
                    @if (ShowWeekNumbers)
                    {
                        var weekDate = calDays[row * 7];
                        <div class="inline-flex items-center justify-center h-8 w-8 text-xs text-muted-foreground">@GetIsoWeekNumber(weekDate)</div>
                    }
                    @for (var col = 0; col < 7; col++)
                    {
                        var d = calDays[row * 7 + col];
                        var dt = d.ToDateTime(TimeOnly.MinValue);
                        var tooltip = DateTooltip?.Invoke(dt);
                        var badge = DateBadge?.Invoke(dt);
                        <div class="@RangeBarClasses(d, monthDate, col)">
                            <button type="button" class="@DayClasses(d, monthDate)" disabled="@IsDisabled(d)" title="@tooltip" @onclick="() => SelectDay(d)" @onmouseenter="() => OnDayHover(d)">
                                @d.Day
                                @if (badge is not null)
                                {
                                    <span class="absolute -bottom-0.5 -right-0.5 pointer-events-none flex">@badge</span>
                                }
                            </button>
                        </div>
                    }
                }
            </div>
        }
        </div> @* close the fixed-width wrapper *@
    };

    public enum CalendarView
    {
        Days,
        Months,
        Years
    }

}
