@namespace Lumeo
@inject IJSRuntime JS
@implements IAsyncDisposable

@if (!Overflow)
{
    <div role="toolbar"
         aria-orientation="horizontal"
         tabindex="0"
         class="@CssClass"
         @attributes="AdditionalAttributes">
        @ChildContent
    </div>
}
else
{
    <div role="toolbar"
         aria-orientation="horizontal"
         class="@CssClassOverflow"
         id="@_toolbarId"
         @ref="_toolbarRef"
         @attributes="AdditionalAttributes">
        <CascadingValue Value="_overflowCtx" IsFixed="false">
            @ChildContent
        </CascadingValue>

        @if (_overflowCtx.VisibleCount >= 0 && _overflowCtx.VisibleCount < _overflowCtx.TotalCount)
        {
            <div class="shrink-0" data-toolbar-overflow-trigger>
                <DropdownMenu @bind-Open="_overflowOpen">
                    <DropdownMenuTrigger>
                        <Button Variant="Button.ButtonVariant.Ghost"
                                Size="Button.ButtonSize.Sm"
                                aria-label="@(OverflowLabel ?? "More")">
                            <Blazicon Svg="Lucide.Ellipsis" class="h-4 w-4" />
                        </Button>
                    </DropdownMenuTrigger>
                    <DropdownMenuContent Align="DropdownMenuContent.DropdownMenuAlign.End">
                        <CascadingValue Value="_dropdownCtx" IsFixed="false">
                            @ChildContent
                        </CascadingValue>
                    </DropdownMenuContent>
                </DropdownMenu>
            </div>
        }
    </div>
}

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

    /// <summary>
    /// When true the toolbar collapses items that don't fit into a trailing "⋯" dropdown.
    /// Use <see cref="VisibleCount"/> to control how many items are shown inline, or leave
    /// it at -1 (the default) to let the toolbar auto-measure via <c>ResizeObserver</c>.
    /// </summary>
    [Parameter] public bool Overflow { get; set; }

    /// <summary>
    /// Number of leading items to show inline when <see cref="Overflow"/> is true.
    /// <list type="bullet">
    ///   <item><description>
    ///     <b>-1 (default)</b> — auto-measure mode: a <c>ResizeObserver</c> measures the
    ///     container width on every resize and computes the maximum number of items that fit
    ///     before a "⋯" trigger button is needed. No consumer configuration required.
    ///   </description></item>
    ///   <item><description>
    ///     <b>Positive integer</b> — manual mode: exactly that many items are shown inline
    ///     and the rest collapse into the "⋯" dropdown regardless of available space.
    ///   </description></item>
    /// </list>
    /// </summary>
    [Parameter] public int VisibleCount { get; set; } = -1;

    /// <summary>aria-label for the "⋯" overflow button. Default: "More".</summary>
    [Parameter] public string? OverflowLabel { get; set; }

    // ── State ─────────────────────────────────────────────────────────────────

    private bool _overflowOpen;
    private ElementReference _toolbarRef;
    // rc.42: per-instance DOM id so the JS observer can `document.getElementById`
    // the toolbar without needing the ElementReference internal-id dance. Unique
    // per component instance so multiple Toolbars on a page don't share state.
    private readonly string _toolbarId = $"lumeo-toolbar-{Guid.NewGuid():N}";

    // Auto-measurement (VisibleCount == -1 + Overflow == true)
    private IJSObjectReference? _toolbarModule;
    private bool _isObservingOverflow;
    private DotNetObjectReference<Toolbar>? _selfRef;
    private int _autoVisibleCount = -1; // -1 = not yet measured

    private ToolbarOverflowContext _overflowCtx = new(ToolbarRenderSlot.Visible, -1, 0);
    private ToolbarOverflowContext _dropdownCtx = new(ToolbarRenderSlot.Dropdown, -1, 0);

    // ── Lifecycle ─────────────────────────────────────────────────────────────

    protected override void OnParametersSet()
    {
        if (!Overflow) return;

        // Effective visible count: prefer the explicit consumer value if > -1;
        // otherwise fall back to the JS-measured _autoVisibleCount.
        var effective = VisibleCount > -1 ? VisibleCount : _autoVisibleCount;

        _overflowCtx = new ToolbarOverflowContext(ToolbarRenderSlot.Visible, effective, int.MaxValue);
        _dropdownCtx = new ToolbarOverflowContext(ToolbarRenderSlot.Dropdown, effective, int.MaxValue);
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender) return;
        if (!Overflow || VisibleCount != -1) return; // manual mode — nothing to wire up

        try
        {
            _toolbarModule ??= await JS.InvokeAsync<IJSObjectReference>(
                "import", "./_content/Lumeo/js/toolbar.js");

            _selfRef ??= DotNetObjectReference.Create(this);

            // rc.42: pass the DOM id (string) instead of the ElementReference —
            // observeToolbarOverflow does document.getElementById(elementId).
            // Previously this passed `_toolbarRef` which the JS module didn't
            // know how to convert to a DOM Element, AND the function itself
            // didn't exist — so the whole feature was a runtime no-op.
            await _toolbarModule.InvokeVoidAsync(
                "observeToolbarOverflow", _toolbarId, _selfRef, nameof(OnOverflowMeasured));
            _isObservingOverflow = true;
        }
        catch (JSDisconnectedException) { }
        catch (ObjectDisposedException) { }
    }

    /// <summary>
    /// Called by JS <c>observeToolbarOverflow</c> each time the fitting count changes.
    /// Only triggers a re-render when the measured value actually changes.
    /// </summary>
    [JSInvokable]
    public Task OnOverflowMeasured(int fittingCount, int totalCount)
    {
        if (fittingCount == _autoVisibleCount) return Task.CompletedTask; // no change — skip re-render

        _autoVisibleCount = fittingCount;

        _overflowCtx = new ToolbarOverflowContext(ToolbarRenderSlot.Visible, fittingCount, totalCount);
        _dropdownCtx = new ToolbarOverflowContext(ToolbarRenderSlot.Dropdown, fittingCount, totalCount);

        StateHasChanged();
        return Task.CompletedTask;
    }

    // ── Disposal ──────────────────────────────────────────────────────────────

    public async ValueTask DisposeAsync()
    {
        try
        {
            if (_isObservingOverflow && _toolbarModule is not null)
            {
                // rc.42: dispose by elementId — see observe site above for why.
                await _toolbarModule.InvokeVoidAsync("disposeToolbarOverflow", _toolbarId);
            }

            if (_toolbarModule is not null)
                await _toolbarModule.DisposeAsync();
        }
        catch (JSDisconnectedException) { }
        catch (ObjectDisposedException) { }
        finally
        {
            _selfRef?.Dispose();
        }
    }

    // ── CSS ──────────────────────────────────────────────────────────────────
    private string CssClass =>
        $"flex items-center gap-1 p-1 bg-card border border-border/40 rounded-lg {Class}".Trim();

    private string CssClassOverflow =>
        $"flex items-center gap-1 p-1 bg-card border border-border/40 rounded-lg overflow-hidden {Class}".Trim();

    // ── Context record ────────────────────────────────────────────────────────

    /// <summary>Slot that a child item should render for in overflow mode.</summary>
    public enum ToolbarRenderSlot { Visible, Dropdown }

    /// <summary>
    /// Cascaded by <see cref="Toolbar"/> to all descendant items when overflow mode is active.
    /// Items use this to decide whether to render normally (Visible slot) or as a dropdown row
    /// (Dropdown slot), and whether to hide themselves when they fall past the VisibleCount boundary.
    /// </summary>
    public record ToolbarOverflowContext(ToolbarRenderSlot Slot, int VisibleCount, int TotalCount);
}
