@namespace Lumeo
@inject Lumeo.Services.Localization.ILumeoLocalizer L

@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 (Orientation == "Vertical")
{
    <div class="@VerticalCssClass" @attributes="AdditionalAttributes">
        <div class="relative flex h-full flex-col items-center">
            @if (Marks is not null || ShowTicks)
            {
                <div class="absolute left-6 top-0 flex h-full flex-col justify-between">
                    @foreach (var mark in GetTickValues())
                    {
                        var pos = GetPercent(mark);
                        <span class="text-xs text-muted-foreground whitespace-nowrap" style="position: absolute; top: @(pos)%; transform: translateY(-50%);">@FormatValue(mark)</span>
                    }
                </div>
            }
            <div class="relative h-full w-2 rounded-[var(--radius)] @(EffectiveInvalid ? "bg-destructive/20" : "bg-primary/20")">
                <div class="absolute bottom-0 w-full rounded-[var(--radius)] @(EffectiveInvalid ? "bg-destructive" : "bg-primary")" style="height: @(GetPercent(Value))%"></div>
                <input type="range" min="@Min" max="@Max" step="@Step" value="@Value" disabled="@Disabled"
                       name="@Name"
                       aria-label="@AriaLabel" aria-valuemin="@Min" aria-valuemax="@Max" aria-valuenow="@Value"
                       aria-required="@Required.ToString().ToLower()"
                       aria-invalid="@EffectiveInvalid.ToString().ToLower()"
                       class="@VerticalInputClasses"
                       @oninput="OnInput"
                       @onpointerenter="() => _showTooltipThumb = true"
                       @onpointerleave="() => _showTooltipThumb = false" />
            </div>
            @if (ShowTooltip && _showTooltipThumb)
            {
                <div class="absolute -left-10 rounded-md bg-popover px-2 py-1 text-xs text-popover-foreground shadow-md"
                     style="top: @(100 - GetPercent(Value))%; transform: translateY(-50%);">
                    @FormatValue(Value)
                </div>
            }
        </div>
    </div>
}
else if (IsRange)
{
    <div class="@CssClass" @attributes="AdditionalAttributes">
        <div class="relative w-full">
            <div class="absolute inset-0 flex items-center">
                <div class="h-2 w-full rounded-[var(--radius)] @(EffectiveInvalid ? "bg-destructive/20" : "bg-primary/20")"></div>
            </div>
            <div class="absolute inset-0 flex items-center" style="pointer-events: none;">
                <div class="h-2 rounded-[var(--radius)] @(EffectiveInvalid ? "bg-destructive" : "bg-primary")" style="margin-left: @(StartPercent)%; width: @(RangePercent)%;"></div>
            </div>
            <div class="relative flex items-center" style="pointer-events: none;">
                <input type="range" min="@Min" max="@Max" step="@Step" value="@Value" disabled="@Disabled"
                       name="@Name"
                       aria-label="@AriaLabel" aria-valuemin="@Min" aria-valuemax="@Max" aria-valuenow="@Value"
                       aria-required="@Required.ToString().ToLower()"
                       aria-invalid="@EffectiveInvalid.ToString().ToLower()"
                       class="@RangeInputClasses"
                       style="pointer-events: auto;"
                       @oninput="OnStartInput" />
                <input type="range" min="@Min" max="@Max" step="@Step" value="@ValueEnd" disabled="@Disabled"
                       aria-label="@EndAriaLabel" aria-valuemin="@Min" aria-valuemax="@Max" aria-valuenow="@ValueEnd"
                       aria-required="@Required.ToString().ToLower()"
                       aria-invalid="@EffectiveInvalid.ToString().ToLower()"
                       class="@RangeInputClasses"
                       style="pointer-events: auto; position: absolute; left: 0; width: 100%;"
                       @oninput="OnEndInput" />
            </div>
            @if (Marks is not null || ShowTicks)
            {
                <div class="relative mt-2 w-full">
                    @foreach (var mark in GetTickValues())
                    {
                        var pos = GetPercent(mark);
                        <div class="absolute flex flex-col items-center" style="left: @(pos)%; transform: translateX(-50%);">
                            @if (ShowTicks)
                            {
                                <div class="h-1 w-px bg-muted-foreground/50"></div>
                            }
                            <span class="mt-1 text-xs text-muted-foreground">@FormatValue(mark)</span>
                        </div>
                    }
                </div>
            }
        </div>
    </div>
}
else
{
    <div class="@CssClass" @attributes="AdditionalAttributes">
        <div class="relative w-full">
            <div class="relative">
                <input type="range" min="@Min" max="@Max" step="@Step" value="@Value" disabled="@Disabled"
                       name="@Name"
                       aria-label="@AriaLabel" aria-valuemin="@Min" aria-valuemax="@Max" aria-valuenow="@Value"
                       aria-required="@Required.ToString().ToLower()"
                       aria-invalid="@EffectiveInvalid.ToString().ToLower()"
                       class="@InputClasses"
                       @oninput="OnInput"
                       @onpointerenter="() => _showTooltipThumb = true"
                       @onpointerleave="() => _showTooltipThumb = false" />
                @if (ShowTooltip && _showTooltipThumb)
                {
                    <div class="absolute -top-8 rounded-md bg-popover px-2 py-1 text-xs text-popover-foreground shadow-md"
                         style="left: @(GetPercent(Value))%; transform: translateX(-50%);">
                        @FormatValue(Value)
                    </div>
                }
            </div>
            @if (Marks is not null || ShowTicks)
            {
                <div class="relative mt-1 w-full">
                    @foreach (var mark in GetTickValues())
                    {
                        var pos = GetPercent(mark);
                        <div class="absolute flex flex-col items-center" style="left: @(pos)%; transform: translateX(-50%);">
                            @if (ShowTicks)
                            {
                                <div class="h-1 w-px bg-muted-foreground/50"></div>
                            }
                            <span class="mt-1 text-xs text-muted-foreground">@FormatValue(mark)</span>
                        </div>
                    }
                </div>
            }
        </div>
    </div>
}

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

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

    // Localized "(end)" suffix for the range slider's second thumb so screen
    // readers in non-English locales announce a meaningful label.
    private string? EndAriaLabel => string.IsNullOrEmpty(AriaLabel) ? null : $"{AriaLabel} ({L["Slider.End"]})";

    [Parameter] public double Value { get; set; }
    [Parameter] public EventCallback<double> ValueChanged { get; set; }
    [Parameter] public bool IsRange { get; set; }
    [Parameter] public double ValueEnd { get; set; }
    [Parameter] public EventCallback<double> ValueEndChanged { get; set; }
    [Parameter] public double Min { get; set; } = 0;
    [Parameter] public double Max { get; set; } = 100;
    [Parameter] public double Step { get; set; } = 1;
    [Parameter] public bool Disabled { get; set; }
    [Parameter] public string Orientation { get; set; } = "Horizontal";
    /// <summary>
    /// Accessible name exposed via <c>aria-label</c> on the underlying range input.
    /// Required for WCAG compliance when the slider is not visually labelled by
    /// adjacent text connected via <c>for</c>/<c>htmlFor</c>. For range sliders
    /// (<see cref="IsRange"/>=true) the end handle's aria-label is automatically
    /// suffixed with "(end)".
    /// </summary>
    [Parameter] public string? AriaLabel { get; set; }
    [Parameter] public bool ShowTooltip { get; set; }
    [Parameter] public List<double>? Marks { get; set; }
    [Parameter] public bool ShowTicks { get; set; }
    [Parameter] public Func<double, string>? FormatTooltip { get; set; }

    /// <summary>
    /// Culture used for the default tooltip/mark value formatting. When null (default)
    /// falls back to <see cref="System.Globalization.CultureInfo.CurrentCulture"/>. Ignored
    /// when <see cref="FormatTooltip"/> is supplied.
    /// </summary>
    [Parameter] public System.Globalization.CultureInfo? Culture { get; set; }

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

    private bool _showTooltipThumb;

    private string CssClass => $"relative flex w-full touch-none select-none items-center {Class}".Trim();
    private string VerticalCssClass => $"relative flex h-full min-h-[200px] touch-none select-none justify-center {Class}".Trim();

    private string InputClasses => $"w-full h-2 cursor-pointer appearance-none rounded-[var(--radius)] {(EffectiveInvalid ? "bg-destructive/20 accent-destructive" : "bg-primary/20 accent-primary")} disabled:cursor-not-allowed disabled:opacity-50";

    private string VerticalInputClasses => $"absolute inset-0 h-full w-full cursor-pointer appearance-none bg-transparent {(EffectiveInvalid ? "accent-destructive" : "accent-primary")} disabled:cursor-not-allowed disabled:opacity-50 [writing-mode:vertical-lr] [direction:rtl]";

    private const string RangeInputClasses = "w-full h-2 cursor-pointer appearance-none bg-transparent accent-primary disabled:cursor-not-allowed disabled:opacity-50";

    private double StartPercent => Max > Min ? ((Math.Min(Value, ValueEnd) - Min) / (Max - Min)) * 100 : 0;
    private double RangePercent => Max > Min ? ((Math.Abs(ValueEnd - Value)) / (Max - Min)) * 100 : 0;

    private double GetPercent(double value) => Max > Min ? ((value - Min) / (Max - Min)) * 100 : 0;

    private string FormatValue(double value) => FormatTooltip is not null ? FormatTooltip(value) : value.ToString("G", EffectiveCulture);

    private List<double> GetTickValues()
    {
        if (Marks is not null) return Marks;
        return new List<double> { Min, Max };
    }

    private async Task OnInput(ChangeEventArgs e)
    {
        if (double.TryParse(e.Value?.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var newValue))
        {
            Value = newValue;
            await ValueChanged.InvokeAsync(Value);
        }
    }

    private async Task OnStartInput(ChangeEventArgs e)
    {
        if (double.TryParse(e.Value?.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var newValue))
        {
            Value = newValue;
            await ValueChanged.InvokeAsync(Value);
        }
    }

    private async Task OnEndInput(ChangeEventArgs e)
    {
        if (double.TryParse(e.Value?.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var newValue))
        {
            ValueEnd = newValue;
            await ValueEndChanged.InvokeAsync(ValueEnd);
        }
    }
}
