@namespace Lumeo

@{
    var context = new TabsContext(ActiveValue, EventCallback.Factory.Create<string>(this, SetValue), Orientation, Variant, _tabIds, AnimatedIndicator, RenderMode, _activatedValues, SwipeEnabled, SwipeWrap, RegisterContent, NavigateBySwipe);
}

<div class="@CssClass" @attributes="AdditionalAttributes">
    <CascadingValue Value="context" IsFixed="false">
        @ChildContent
    </CascadingValue>
</div>

@code {
    [Parameter] public RenderFragment? ChildContent { get; set; }
    [Parameter] public string ActiveValue { get; set; } = "";
    [Parameter] public EventCallback<string> ActiveValueChanged { get; set; }
    [Parameter] public TabsOrientation Orientation { get; set; } = TabsOrientation.Horizontal;
    [Parameter] public TabsVariant Variant { get; set; } = TabsVariant.Default;

    /// <summary>
    /// When true, renders a sliding underline that animates between active
    /// triggers inside each <see cref="TabsList"/>. Default is <c>false</c>
    /// (no underline, original behaviour).
    /// </summary>
    [Parameter] public bool AnimatedIndicator { get; set; }

    /// <summary>
    /// Controls when each <see cref="TabsContent"/> panel is rendered into
    /// the DOM and whether its state survives a tab switch:
    /// <list type="bullet">
    /// <item><see cref="TabsRenderMode.Active"/> (default) — only the active
    /// panel is in the DOM; switching away unmounts it (form inputs, scroll
    /// position and any component state are lost). Lightest footprint.</item>
    /// <item><see cref="TabsRenderMode.Lazy"/> — a panel is rendered the
    /// first time its tab becomes active, then kept in the DOM (hidden) so
    /// its state is preserved on subsequent switches. Like DevExpress
    /// <c>OnDemand</c>.</item>
    /// <item><see cref="TabsRenderMode.Eager"/> — every panel is rendered
    /// immediately; inactive ones are hidden. State always preserved, but
    /// the heaviest initial render. Like DevExpress <c>Always</c>.</item>
    /// </list>
    /// A single <see cref="TabsContent"/> can opt into eager rendering
    /// regardless of this setting via <c>ForceMount="true"</c>.
    /// </summary>
    [Parameter] public TabsRenderMode RenderMode { get; set; } = TabsRenderMode.Active;

    /// <summary>
    /// When true, a horizontal pointer drag (&gt;50 px horizontal, &lt;30 px
    /// vertical) on any <see cref="TabsContent"/> panel switches to the
    /// next or previous tab. Default is <c>false</c> (opt-in).
    /// </summary>
    [Parameter] public bool SwipeEnabled { get; set; }

    /// <summary>
    /// When true, swiping past the last tab wraps around to the first (and
    /// vice-versa). Default is <c>false</c> (no wrap-around at boundaries).
    /// </summary>
    [Parameter] public bool SwipeWrap { get; set; }

    [Parameter] public string? Class { get; set; }
    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }

    // Registry: value -> (tabId, panelId) — populated by TabsTrigger on first render
    private readonly Dictionary<string, (string TabId, string PanelId)> _tabIds = new();
    // Set of tab values that have ever been the active tab — drives Lazy mode.
    private readonly HashSet<string> _activatedValues = new();
    // Ordered list of registered TabsContent values — used for swipe navigation.
    private readonly List<string> _contentOrder = new();

    private string CssClass => Orientation == TabsOrientation.Vertical
        ? $"flex flex-row {Class}".Trim()
        : $"{Class}".Trim();

    protected override void OnParametersSet()
    {
        // Track activations so Lazy mode knows which panels to keep mounted.
        if (!string.IsNullOrEmpty(ActiveValue))
            _activatedValues.Add(ActiveValue);
    }

    internal void RegisterContent(string value)
    {
        if (!_contentOrder.Contains(value))
            _contentOrder.Add(value);
    }

    internal async Task NavigateBySwipe(string direction)
    {
        if (_contentOrder.Count == 0) return;
        var idx = _contentOrder.IndexOf(ActiveValue);
        if (idx < 0) return;

        int next;
        if (direction == "next")
        {
            next = idx + 1;
            if (next >= _contentOrder.Count)
            {
                if (!SwipeWrap) return;
                next = 0;
            }
        }
        else // "prev"
        {
            next = idx - 1;
            if (next < 0)
            {
                if (!SwipeWrap) return;
                next = _contentOrder.Count - 1;
            }
        }

        await SetValue(_contentOrder[next]);
        StateHasChanged();
    }

    private async Task SetValue(string value)
    {
        ActiveValue = value;
        if (!string.IsNullOrEmpty(value))
            _activatedValues.Add(value);
        await ActiveValueChanged.InvokeAsync(value);
    }

    public record TabsContext(
        string ActiveValue,
        EventCallback<string> SetValue,
        TabsOrientation Orientation,
        TabsVariant Variant,
        Dictionary<string, (string TabId, string PanelId)> TabIds,
        bool AnimatedIndicator,
        TabsRenderMode RenderMode,
        HashSet<string> ActivatedValues,
        bool SwipeEnabled,
        bool SwipeWrap,
        Action<string> RegisterContent,
        Func<string, Task> SwipeNavigate)
    {
        public TabsContext(string ActiveValue, EventCallback<string> SetValue, TabsOrientation Orientation = TabsOrientation.Horizontal, TabsVariant Variant = TabsVariant.Default)
            : this(ActiveValue, SetValue, Orientation, Variant, new Dictionary<string, (string, string)>(), false, TabsRenderMode.Active, new HashSet<string>(), false, false, _ => { }, _ => Task.CompletedTask) { }
    }

    public enum TabsOrientation { Horizontal, Vertical }

    public enum TabsVariant
    {
        Default,
        Card,
        Pill
    }

    /// <summary>Render strategy for <see cref="TabsContent"/> panels. See
    /// <see cref="Tabs.RenderMode"/> for the semantics of each value.</summary>
    public enum TabsRenderMode
    {
        /// <summary>Only the active panel is in the DOM; switching away unmounts it. Default.</summary>
        Active,
        /// <summary>Panels render on first activation and stay mounted (hidden) afterwards.</summary>
        Lazy,
        /// <summary>All panels render immediately; inactive ones are hidden.</summary>
        Eager
    }
}
