@namespace Lumeo

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

<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="mr-2 h-4 w-4 shrink-0" />
            @if (Value.HasValue)
            {
                <span>@FormatDateTime(Value.Value)</span>
            }
            else
            {
                <span class="text-muted-foreground group-hover:text-accent-foreground">@EffectivePlaceholder</span>
            }
            @if (Clearable && Value.HasValue)
            {
                <button type="button" class="ml-auto pl-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["DateTimePicker.ClearDate"]" @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">
        <div class="flex">
            <div>
                <Calendar Value="@_dateValue" ValueChanged="HandleDateChanged"
                  MinDate="@(MinDate.HasValue ? (DateOnly?)DateOnly.FromDateTime(MinDate.Value) : null)"
                  MaxDate="@(MaxDate.HasValue ? (DateOnly?)DateOnly.FromDateTime(MaxDate.Value) : null)"
                  IsDateDisabled="@IsDateDisabled"
                  FirstDayOfWeek="@FirstDayOfWeek" />
            </div>
            <div class="border-l border-border/40">
                <div class="p-3 pb-1">
                    <span class="text-xs font-medium text-muted-foreground">@L["DateTimePicker.TimeLabel"]</span>
                </div>
                <div class="flex divide-x divide-border/40">
                    @* Hours column *@
                    <div class="flex flex-col h-[240px] overflow-y-auto py-1 scrollbar-thin">
                        @foreach (var hour in GetHours())
                        {
                            var h = hour;
                            var isSelected = _timeValue.HasValue && GetDisplayHour(_timeValue.Value) == h;
                            <button type="button"
                                    class="@(isSelected ? "px-3 py-1.5 text-sm cursor-pointer bg-primary text-primary-foreground" : "px-3 py-1.5 text-sm cursor-pointer hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:bg-muted/50")"
                                    @onclick="() => SelectHour(h)">
                                @h.ToString("D2")
                            </button>
                        }
                    </div>

                    @* Minutes column *@
                    <div class="flex flex-col h-[240px] overflow-y-auto py-1 scrollbar-thin">
                        @for (var i = 0; i < 60; i++)
                        {
                            var m = i;
                            var isSelected = _timeValue.HasValue && _timeValue.Value.Minutes == m;
                            <button type="button"
                                    class="@(isSelected ? "px-3 py-1.5 text-sm cursor-pointer bg-primary text-primary-foreground" : "px-3 py-1.5 text-sm cursor-pointer hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:bg-muted/50")"
                                    @onclick="() => SelectMinute(m)">
                                @m.ToString("D2")
                            </button>
                        }
                    </div>

                    @* Seconds column *@
                    @if (ShowSeconds)
                    {
                        <div class="flex flex-col h-[240px] overflow-y-auto py-1 scrollbar-thin">
                            @for (var i = 0; i < 60; i++)
                            {
                                var s = i;
                                var isSelected = _timeValue.HasValue && _timeValue.Value.Seconds == s;
                                <button type="button"
                                        class="@(isSelected ? "px-3 py-1.5 text-sm cursor-pointer bg-primary text-primary-foreground" : "px-3 py-1.5 text-sm cursor-pointer hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:bg-muted/50")"
                                        @onclick="() => SelectSecond(s)">
                                    @s.ToString("D2")
                                </button>
                            }
                        </div>
                    }

                    @* AM/PM column *@
                    @if (!Use24Hour)
                    {
                        <div class="flex flex-col py-1">
                            @{
                                var isAm = !_timeValue.HasValue || _timeValue.Value.Hours < 12;
                                var amLabel = string.IsNullOrEmpty(EffectiveCulture.DateTimeFormat.AMDesignator) ? "AM" : EffectiveCulture.DateTimeFormat.AMDesignator;
                                var pmLabel = string.IsNullOrEmpty(EffectiveCulture.DateTimeFormat.PMDesignator) ? "PM" : EffectiveCulture.DateTimeFormat.PMDesignator;
                            }
                            <button type="button"
                                    class="@(isAm ? "px-3 py-1.5 text-sm cursor-pointer bg-primary text-primary-foreground" : "px-3 py-1.5 text-sm cursor-pointer hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:bg-muted/50")"
                                    @onclick="() => SelectAmPm(true)">
                                @amLabel
                            </button>
                            <button type="button"
                                    class="@(!isAm ? "px-3 py-1.5 text-sm cursor-pointer bg-primary text-primary-foreground" : "px-3 py-1.5 text-sm cursor-pointer hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:bg-muted/50")"
                                    @onclick="() => SelectAmPm(false)">
                                @pmLabel
                            </button>
                        </div>
                    }
                </div>
            </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 {
    [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 DateTime? Value { get; set; }
    [Parameter] public EventCallback<DateTime?> ValueChanged { get; set; }
    [Parameter] public bool Use24Hour { get; set; } = true;
    [Parameter] public bool ShowSeconds { get; set; }
    /// <summary>
    /// Date portion format. When null (default) falls back to the effective culture's
    /// <c>ShortDatePattern</c>.
    /// </summary>
    [Parameter] public string? DateFormat { get; set; }
    [Parameter] public string? Placeholder { get; set; }

    /// <summary>
    /// Overrides <see cref="System.Globalization.CultureInfo.CurrentCulture"/> for
    /// date/time formatting. 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 EffectiveDateFormat => DateFormat ?? EffectiveCulture.DateTimeFormat.ShortDatePattern;

    private string EffectivePlaceholder => Placeholder ?? L["DateTimePicker.Placeholder"];
    [Parameter] public bool Disabled { get; set; }
    [Parameter] public bool Clearable { get; set; }
    [Parameter] public DateTime? MinDate { get; set; }
    [Parameter] public DateTime? MaxDate { get; set; }
    [Parameter] public Func<DateTime, bool>? IsDateDisabled { get; set; }
    /// <summary>
    /// Override the locale-derived first day of week shown by the embedded calendar.
    /// Forwarded to the inner <see cref="Calendar"/>; leave null to fall back to
    /// <see cref="System.Globalization.CultureInfo.CurrentCulture"/>.
    /// </summary>
    [Parameter] public DayOfWeek? FirstDayOfWeek { get; set; }
    [Parameter] public string? Class { get; set; }
    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }

    private bool _isOpen;
    private DateOnly? _dateValue;
    private TimeSpan? _timeValue;

    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();

    protected override void OnParametersSet()
    {
        if (Value.HasValue)
        {
            _dateValue = DateOnly.FromDateTime(Value.Value);
            _timeValue = Value.Value.TimeOfDay;
        }
        else
        {
            _dateValue = null;
            _timeValue = null;
        }
    }

    private string FormatDateTime(DateTime dt)
    {
        var timeFormat = Use24Hour
            ? (ShowSeconds ? "HH:mm:ss" : "HH:mm")
            : (ShowSeconds ? "hh:mm:ss tt" : "hh:mm tt");
        return $"{dt.ToString(EffectiveDateFormat, EffectiveCulture)} {dt.ToString(timeFormat, EffectiveCulture)}";
    }

    private List<int> GetHours()
    {
        var hours = new List<int>();
        if (Use24Hour)
        {
            for (var i = 0; i < 24; i++) hours.Add(i);
        }
        else
        {
            for (var i = 1; i <= 12; i++) hours.Add(i);
        }
        return hours;
    }

    private int GetDisplayHour(TimeSpan time)
    {
        if (Use24Hour) return time.Hours;
        var h = time.Hours % 12;
        return h == 0 ? 12 : h;
    }

    private async Task HandleDateChanged(DateOnly? date)
    {
        _dateValue = date;
        await UpdateValue();
    }

    private async Task SelectHour(int hour)
    {
        var current = _timeValue ?? TimeSpan.Zero;
        int actualHour;
        if (Use24Hour)
        {
            actualHour = hour;
        }
        else
        {
            var isPm = current.Hours >= 12;
            actualHour = hour % 12;
            if (isPm) actualHour += 12;
        }
        _timeValue = new TimeSpan(actualHour, current.Minutes, current.Seconds);
        await UpdateValue();
    }

    private async Task SelectMinute(int minute)
    {
        var current = _timeValue ?? TimeSpan.Zero;
        _timeValue = new TimeSpan(current.Hours, minute, current.Seconds);
        await UpdateValue();
    }

    private async Task SelectSecond(int second)
    {
        var current = _timeValue ?? TimeSpan.Zero;
        _timeValue = new TimeSpan(current.Hours, current.Minutes, second);
        await UpdateValue();
    }

    private async Task SelectAmPm(bool isAm)
    {
        var current = _timeValue ?? TimeSpan.Zero;
        var hour = current.Hours % 12;
        if (!isAm) hour += 12;
        _timeValue = new TimeSpan(hour, current.Minutes, current.Seconds);
        await UpdateValue();
    }

    private async Task UpdateValue()
    {
        if (_dateValue.HasValue)
        {
            var time = _timeValue ?? TimeSpan.Zero;
            var dt = _dateValue.Value.ToDateTime(new TimeOnly(time.Hours, time.Minutes, time.Seconds));
            Value = dt;
            await ValueChanged.InvokeAsync(dt);
        }
    }

    private async Task HandleClear()
    {
        Value = null;
        _dateValue = null;
        _timeValue = null;
        await ValueChanged.InvokeAsync(null);
    }
}
