@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>
}

@if (Variant == TimePickerVariant.Wheel)
{
    @* iOS-style wheel picker — hours + minutes (+ AM/PM if 12-hour).
       Seconds are not supported in the wheel variant. *@
    <TimeWheelPicker Value="@Value" ValueChanged="HandleWheelChanged"
                     Use24Hour="@Use24Hour" MinuteStep="@MinuteStep"
                     Culture="@Culture" Class="@Class" />
}
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.Clock" class="mr-2 h-4 w-4 shrink-0" />
            @if (Value.HasValue)
            {
                <span>@FormatTime(Value.Value)</span>
            }
            else
            {
                <span class="text-muted-foreground group-hover:text-accent-foreground">@EffectivePlaceholder</span>
            }
        </button>
    </PopoverTrigger>
    <PopoverContent Align="PopoverContent.PopoverAlign.Start" Class="w-auto p-0">
        <div class="flex divide-x divide-border/40">
            @* Hours column *@
            <div class="flex flex-col h-[200px] overflow-y-auto py-1 scrollbar-thin">
                @foreach (var hour in GetHours())
                {
                    var h = hour;
                    var isSelected = Value.HasValue && GetDisplayHour(Value.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-[200px] overflow-y-auto py-1 scrollbar-thin">
                @foreach (var minute in GetMinutes())
                {
                    var m = minute;
                    var isSelected = Value.HasValue && Value.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-[200px] overflow-y-auto py-1 scrollbar-thin">
                    @foreach (var second in GetSeconds())
                    {
                        var s = second;
                        var isSelected = Value.HasValue && Value.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 (!EffectiveUse24Hour)
            {
                <div class="flex flex-col py-1">
                    @{
                        var isAm = !Value.HasValue || Value.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>
    </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 {
    /// <summary>
    /// Visual style of the picker. <see cref="TimePickerVariant.List"/> renders the
    /// existing scrollable hour/minute columns inside a popover (default).
    /// <see cref="TimePickerVariant.Wheel"/> renders inline iOS-style snap-scrolling
    /// columns. The Wheel variant doesn't show seconds.
    /// </summary>
    public enum TimePickerVariant { List, Wheel }

    [Parameter] public TimePickerVariant Variant { get; set; } = TimePickerVariant.List;

    [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 TimeSpan? Value { get; set; }
    [Parameter] public EventCallback<TimeSpan?> ValueChanged { get; set; }
    /// <summary>
    /// When <c>true</c> renders a 24-hour clock; when <c>false</c> renders 12-hour with AM/PM.
    /// When <c>null</c> (default) the picker derives the choice from
    /// <see cref="EffectiveCulture"/>'s <c>ShortTimePattern</c> — <c>de-DE</c>, <c>fr-FR</c>
    /// and similar see 24-hour; <c>en-US</c> sees 12-hour.
    /// </summary>
    [Parameter] public bool? Use24Hour { get; set; }
    [Parameter] public bool ShowSeconds { get; set; }
    [Parameter] public int MinuteStep { get; set; } = 1;
    [Parameter] public int SecondStep { get; set; } = 1;
    [Parameter] public string? Placeholder { get; set; }

    /// <summary>
    /// Overrides <see cref="System.Globalization.CultureInfo.CurrentCulture"/> for
    /// 24h/12h detection and AM/PM designators. 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;
    // Per CLDR/.NET conventions a lowercase "h" in the short-time pattern means 12-hour;
    // uppercase "H" means 24-hour. We honour an explicit Use24Hour first, then derive.
    private bool EffectiveUse24Hour => Use24Hour ?? !EffectiveCulture.DateTimeFormat.ShortTimePattern.Contains('h');

    private string EffectivePlaceholder => Placeholder ?? L["TimePicker.Placeholder"];
    [Parameter] public bool Disabled { get; set; }
    [Parameter] public string? Class { get; set; }
    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }

    private bool _isOpen;

    private string CssClass => $"group flex h-9 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 string FormatTime(TimeSpan time)
    {
        if (EffectiveUse24Hour)
        {
            return ShowSeconds
                ? $"{time.Hours:D2}:{time.Minutes:D2}:{time.Seconds:D2}"
                : $"{time.Hours:D2}:{time.Minutes:D2}";
        }
        else
        {
            var hour12 = time.Hours % 12;
            if (hour12 == 0) hour12 = 12;
            var dtf = EffectiveCulture.DateTimeFormat;
            var ampm = time.Hours < 12
                ? (string.IsNullOrEmpty(dtf.AMDesignator) ? "AM" : dtf.AMDesignator)
                : (string.IsNullOrEmpty(dtf.PMDesignator) ? "PM" : dtf.PMDesignator);
            return ShowSeconds
                ? $"{hour12:D2}:{time.Minutes:D2}:{time.Seconds:D2} {ampm}"
                : $"{hour12:D2}:{time.Minutes:D2} {ampm}";
        }
    }

    private List<int> GetHours()
    {
        var hours = new List<int>();
        if (EffectiveUse24Hour)
        {
            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 (EffectiveUse24Hour) return time.Hours;
        var h = time.Hours % 12;
        return h == 0 ? 12 : h;
    }

    private List<int> GetMinutes()
    {
        var minutes = new List<int>();
        var step = Math.Max(1, MinuteStep);
        for (var i = 0; i < 60; i += step) minutes.Add(i);
        return minutes;
    }

    private List<int> GetSeconds()
    {
        var seconds = new List<int>();
        var step = Math.Max(1, SecondStep);
        for (var i = 0; i < 60; i += step) seconds.Add(i);
        return seconds;
    }

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

    private async Task SelectMinute(int minute)
    {
        var current = Value ?? TimeSpan.Zero;
        var newValue = new TimeSpan(current.Hours, minute, current.Seconds);
        Value = newValue;
        await ValueChanged.InvokeAsync(newValue);
    }

    private async Task SelectSecond(int second)
    {
        var current = Value ?? TimeSpan.Zero;
        var newValue = new TimeSpan(current.Hours, current.Minutes, second);
        Value = newValue;
        await ValueChanged.InvokeAsync(newValue);
    }

    private async Task HandleWheelChanged(TimeSpan? newValue)
    {
        Value = newValue;
        await ValueChanged.InvokeAsync(newValue);
    }

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