@* Reusable diff/comparison dialog using Bootstrap modal.
   Mirrors the ConfirmDialog API: callers invoke ShowAsync(title, before, after)
   via @ref to display a side-by-side or simple before/after comparison.
   z-index ladder follows ConfirmDialog: modal 1055 > backdrop 1040 (toasts at 1090). *@
@inject IJSRuntime JS
@inject ILogger<DiffDialog> Logger
@implements IAsyncDisposable

@if (_visible)
{
    <div class="modal-backdrop fade show"></div>
    <div @ref="_modalRef"
         class="modal fade show d-block"
         tabindex="-1"
         role="dialog"
         @onkeydown="OnKeyDownAsync">
        <div class="modal-dialog modal-lg modal-dialog-centered" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">@Title</h5>
                    <button type="button" class="btn-close" aria-label="Close diff dialog" @onclick="Close"></button>
                </div>
                <div class="modal-body">
                    @if (BodyContent != null)
                    {
                        @BodyContent
                    }
                    else
                    {
                        <div class="row g-3">
                            <div class="col-md-6">
                                <div class="small text-muted mb-1">Before</div>
                                <pre class="border rounded p-2 small bg-light mb-0" style="max-height: 50vh; overflow: auto; white-space: pre-wrap;">@Before</pre>
                            </div>
                            <div class="col-md-6">
                                <div class="small text-muted mb-1">After</div>
                                <pre class="border rounded p-2 small bg-light mb-0" style="max-height: 50vh; overflow: auto; white-space: pre-wrap;">@After</pre>
                            </div>
                        </div>
                    }
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary btn-sm" @onclick="Close">Close</button>
                </div>
            </div>
        </div>
    </div>
}

@code {
    private bool _visible;
    private bool _bodyLocked;
    private TaskCompletionSource<bool>? _tcs;
    private ElementReference _modalRef;

    [Parameter] public string Title { get; set; } = "Diff";
    [Parameter] public string Before { get; set; } = string.Empty;
    [Parameter] public string After { get; set; } = string.Empty;
    /// <summary>
    /// Optional custom body content. When supplied, it replaces the default
    /// before/after panes — useful when the caller wants to render a richer
    /// comparison (e.g. metadata badges, file lists, etc.).
    /// </summary>
    [Parameter] public RenderFragment? BodyContent { get; set; }

    /// <summary>
    /// Show the dialog with the supplied title and before/after text.
    /// Returns when the user dismisses the dialog.
    /// </summary>
    public Task<bool> ShowAsync(string title, string before, string after)
    {
        Title = title;
        Before = before;
        After = after;
        BodyContent = null;
        return OpenAsync();
    }

    /// <summary>
    /// Show the dialog with a custom body. Useful when the diff is not a
    /// simple before/after string pair (e.g. a deployment comparison summary).
    /// </summary>
    public Task<bool> ShowAsync(string title, RenderFragment body)
    {
        Title = title;
        BodyContent = body;
        return OpenAsync();
    }

    private Task<bool> OpenAsync()
    {
        _visible = true;
        _tcs = new TaskCompletionSource<bool>();
        StateHasChanged();
        return _tcs.Task;
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (_visible && !_bodyLocked)
        {
            _bodyLocked = true;
            await TryLockBodyAsync();
            try { await _modalRef.FocusAsync(); }
            catch (InvalidOperationException)
            {
                // Prerender: the element reference is not attached yet — the
                // next interactive render focuses it. Expected, not logged.
            }
            catch (JSDisconnectedException)
            {
                // Circuit gone before focus could run — nothing to do.
            }
            catch (JSException ex)
            {
                // A genuine focus interop failure (CentralUI-023) — log it.
                Logger.LogWarning(ex, "DiffDialog: failed to focus the modal.");
            }
        }
    }

    private async Task OnKeyDownAsync(KeyboardEventArgs e)
    {
        if (e.Key == "Escape")
        {
            Close();
            await Task.CompletedTask;
        }
    }

    private void Close()
    {
        _visible = false;
        _ = TryUnlockBodyAsync();
        _tcs?.TrySetResult(true);
    }

    private async Task TryLockBodyAsync()
    {
        try
        {
            await JS.InvokeVoidAsync("document.body.classList.add", "modal-open");
        }
        catch (JSDisconnectedException)
        {
            // Circuit gone — the body scroll lock is moot. Expected, silent.
        }
        catch (JSException ex)
        {
            // CentralUI-023: a genuine interop failure — log instead of doing
            // another (also-failing) JS call inside a bare catch.
            Logger.LogWarning(ex, "DiffDialog: failed to apply body scroll lock.");
        }
    }

    private async Task TryUnlockBodyAsync()
    {
        _bodyLocked = false;
        try
        {
            await JS.InvokeVoidAsync("document.body.classList.remove", "modal-open");
        }
        catch (JSDisconnectedException)
        {
            // Circuit gone — the body scroll lock is moot. Expected, silent.
        }
        catch (JSException ex)
        {
            Logger.LogWarning(ex, "DiffDialog: failed to remove body scroll lock.");
        }
    }

    public async ValueTask DisposeAsync()
    {
        // CentralUI-011: if the dialog is disposed while still open (the user
        // navigated away), complete the pending task so the awaiting caller
        // resumes deterministically instead of hanging forever.
        _tcs?.TrySetResult(false);

        if (_bodyLocked)
        {
            await TryUnlockBodyAsync();
        }
    }
}
