@namespace Lumeo

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

<div class="@CssClass" @attributes="AdditionalAttributes">
    <button type="button"
            class="@($"inline-flex h-9 w-9 items-center justify-center rounded-l-md border border-r-0 {(EffectiveInvalid ? "border-destructive" : "border-border/60")} bg-muted text-muted-foreground transition-colors hover:bg-muted/80 focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:relative focus-visible:z-10 disabled:pointer-events-none disabled:opacity-50")"
            disabled="@(Disabled || IsAtMin)"
            @onclick="Decrement"
            aria-label="@L["NumberInput.Decrease"]">
        <Blazicon Svg="Lucide.Minus" class="h-4 w-4" />
    </button>
    <div class="@($"relative inline-flex items-center h-9 border {(EffectiveInvalid ? "border-destructive" : "border-border/60")} bg-background")">
        @if (!string.IsNullOrEmpty(Prefix))
        {
            <span class="pl-2 text-sm text-muted-foreground select-none">@Prefix</span>
        }
        <input type="number"
               name="@Name"
               aria-required="@Required.ToString().ToLowerInvariant()"
               aria-invalid="@EffectiveInvalid.ToString().ToLowerInvariant()"
               class="h-9 w-20 bg-transparent @(string.IsNullOrEmpty(Prefix) && string.IsNullOrEmpty(Suffix) ? "px-3" : "px-1") text-center text-sm text-foreground outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
               value="@DisplayValue"
               disabled="@Disabled"
               min="@Min"
               max="@Max"
               step="@Step"
               @onchange="OnInputChange"
               @onkeydown="HandleKeyDown"
               @onwheel="HandleWheel"
               @onwheel:preventDefault="@UseMouseWheel" />
        @if (!string.IsNullOrEmpty(Suffix))
        {
            <span class="pr-2 text-sm text-muted-foreground select-none">@Suffix</span>
        }
    </div>
    <button type="button"
            class="@($"inline-flex h-9 w-9 items-center justify-center rounded-r-md border border-l-0 {(EffectiveInvalid ? "border-destructive" : "border-border/60")} bg-muted text-muted-foreground transition-colors hover:bg-muted/80 focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:relative focus-visible:z-10 disabled:pointer-events-none disabled:opacity-50")"
            disabled="@(Disabled || IsAtMax)"
            @onclick="Increment"
            aria-label="@L["NumberInput.Increase"]">
        <Blazicon Svg="Lucide.Plus" class="h-4 w-4" />
    </button>
</div>

@if (!InsideFormField)
{
    @if (EffectiveInvalid && !string.IsNullOrEmpty(ErrorText))
    {
        <p class="mt-1 text-xs text-destructive">@ErrorText</p>
    }
    else if (!string.IsNullOrEmpty(HelperText))
    {
        <p class="mt-1 text-xs text-muted-foreground">@HelperText</p>
    }
}

@inject Lumeo.Services.Localization.ILumeoLocalizer L

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

    [Parameter] public double? Value { get; set; }
    [Parameter] public EventCallback<double?> ValueChanged { get; set; }
    [Parameter] public double? Min { get; set; }
    [Parameter] public double? Max { get; set; }
    [Parameter] public double Step { get; set; } = 1;
    [Parameter] public int? Precision { get; set; }
    [Parameter] public bool Disabled { get; set; }
    [Parameter] public bool UseMouseWheel { get; set; }
    [Parameter] public string? Prefix { get; set; }
    [Parameter] public string? Suffix { get; set; }

    /// <summary>
    /// Culture used to parse typed input. Display of the value inside the native
    /// <c>&lt;input type="number"&gt;</c> element uses <see cref="System.Globalization.CultureInfo.InvariantCulture"/>
    /// because HTML5 requires period-as-decimal — browsers will not accept a comma decimal
    /// in this input type. Leave null to honour ASP.NET request localization for parsing
    /// (lets a <c>de-DE</c> user paste <c>1,5</c> and have it round-trip to <c>1.5</c>).
    /// </summary>
    [Parameter] public System.Globalization.CultureInfo? Culture { get; set; }

    private System.Globalization.CultureInfo EffectiveCulture => Culture ?? System.Globalization.CultureInfo.CurrentCulture;
    [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; }
    [Parameter] public string? Class { get; set; }
    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }

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

    private string CssClass => $"inline-flex items-center {Class}".Trim();

    private bool IsAtMin => Min.HasValue && Value.HasValue && Value.Value <= Min.Value;
    private bool IsAtMax => Max.HasValue && Value.HasValue && Value.Value >= Max.Value;

    private string DisplayValue
    {
        get
        {
            if (Value is null) return "";
            if (Precision.HasValue)
                return Value.Value.ToString($"F{Precision.Value}", System.Globalization.CultureInfo.InvariantCulture);
            return Value.Value.ToString(System.Globalization.CultureInfo.InvariantCulture);
        }
    }

    private async Task Increment()
    {
        var newValue = (Value ?? 0) + Step;
        await SetValue(newValue);
    }

    private async Task Decrement()
    {
        var newValue = (Value ?? 0) - Step;
        await SetValue(newValue);
    }

    private async Task HandleKeyDown(KeyboardEventArgs e)
    {
        if (Disabled) return;
        switch (e.Key)
        {
            case "ArrowUp":
                await Increment();
                break;
            case "ArrowDown":
                await Decrement();
                break;
        }
    }

    private async Task HandleWheel(WheelEventArgs e)
    {
        if (!UseMouseWheel || Disabled) return;
        if (e.DeltaY < 0)
            await Increment();
        else if (e.DeltaY > 0)
            await Decrement();
    }

    private async Task OnInputChange(ChangeEventArgs e)
    {
        var raw = e.Value?.ToString();
        // Try invariant first (what the native <input type="number"> reports), then the
        // effective culture as a fallback for consumers that render via FormatType=Text
        // or feed values programmatically.
        if (double.TryParse(raw, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var parsed)
            || double.TryParse(raw, System.Globalization.NumberStyles.Any, EffectiveCulture, out parsed))
        {
            await SetValue(parsed);
        }
    }

    private async Task SetValue(double newValue)
    {
        if (Min.HasValue && newValue < Min.Value) newValue = Min.Value;
        if (Max.HasValue && newValue > Max.Value) newValue = Max.Value;

        if (Precision.HasValue)
        {
            newValue = Math.Round(newValue, Precision.Value);
        }

        Value = newValue;
        await ValueChanged.InvokeAsync(Value);
    }
}
