@namespace Lumeo
@implements IAsyncDisposable
@inject Lumeo.Services.ComponentInteropService Interop

@{
    var context = new HoverCardContext(
        Open || _pinned,
        EventCallback.Factory.Create(this, RequestOpen),
        EventCallback.Factory.Create(this, RequestClose),
        EventCallback.Factory.Create(this, TogglePin),
        _wrapperId);
}

<CascadingValue Value="context" IsFixed="false">
    <div id="@_wrapperId" class="relative inline-block" @attributes="AdditionalAttributes">
        @ChildContent
    </div>
</CascadingValue>

@code {
    [Parameter] public RenderFragment? ChildContent { get; set; }
    [Parameter] public bool Open { get; set; }
    [Parameter] public EventCallback<bool> OpenChanged { get; set; }

    [Obsolete("Use Open instead. IsOpen will be removed in a future release.")]
    [Parameter] public bool IsOpen { get => Open; set => Open = value; }

    [Obsolete("Use OpenChanged instead. IsOpenChanged will be removed in a future release.")]
    [Parameter] public EventCallback<bool> IsOpenChanged { get => OpenChanged; set => OpenChanged = value; }

    [Parameter] public int OpenDelay { get; set; } = 200;
    [Parameter] public int CloseDelay { get; set; } = 300;
    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }

    private readonly string _wrapperId = $"hovercard-{Guid.NewGuid():N}";
    private readonly DelayedDispatch _dispatch = new();
    // rc.44 — tap-pin path for touch devices. The trigger's @onclick toggles
    // _pinned; click-outside auto-unpins via the global mousedown/touchstart
    // listener in components.js.
    private bool _pinned;
    private bool _clickOutsideRegistered;

    private void RequestOpen()
    {
        _dispatch.Cancel();
        if (Open) return;
        _dispatch.Schedule(OpenDelay, () => InvokeAsync(async () =>
        {
            Open = true;
            await OpenChanged.InvokeAsync(true);
            StateHasChanged();
        }));
    }

    private void RequestClose()
    {
        // If the card is pinned (touch), hover-leave must NOT close it.
        if (_pinned) return;
        _dispatch.Cancel();
        if (!Open) return;
        _dispatch.Schedule(CloseDelay, () => InvokeAsync(async () =>
        {
            Open = false;
            await OpenChanged.InvokeAsync(false);
            StateHasChanged();
        }));
    }

    private async Task TogglePin()
    {
        _pinned = !_pinned;
        if (_pinned)
        {
            // Stop any in-flight close-delay so the tap reliably keeps it open.
            _dispatch.Cancel();
            if (!_clickOutsideRegistered)
            {
                await Interop.RegisterClickOutside(_wrapperId, null, OnClickOutside);
                _clickOutsideRegistered = true;
            }
        }
        else
        {
            // Optional auto-collapse on unpin if the user wasn't hovering.
            if (Open)
            {
                Open = false;
                await OpenChanged.InvokeAsync(false);
            }
        }
        StateHasChanged();
    }

    private Task OnClickOutside()
    {
        if (_pinned)
        {
            _pinned = false;
            if (Open)
            {
                Open = false;
                _ = OpenChanged.InvokeAsync(false);
            }
            StateHasChanged();
        }
        return Task.CompletedTask;
    }

    public async ValueTask DisposeAsync()
    {
        _dispatch.Dispose();
        if (_clickOutsideRegistered)
        {
            try { await Interop.UnregisterClickOutside(_wrapperId); }
            catch (Microsoft.JSInterop.JSDisconnectedException) { }
            _clickOutsideRegistered = false;
        }
    }

    public record HoverCardContext(bool IsOpen, EventCallback RequestOpen, EventCallback RequestClose, EventCallback RequestTogglePin, string WrapperId);
}
