@* Single global host component for IDialogService. Mounted once in MainLayout.
   Listens to DialogService.OnChange and renders the current dialog state.
   z-index ladder follows the same convention as ConfirmDialog/DiffDialog:
   Toast container 1090 > this modal 1055 > this backdrop 1040. *@
@implements IDisposable
@inject IDialogService Service
@inject IJSRuntime JS

@if (Service is DialogService svc && svc.Current is { } state)
{
    <div class="modal-backdrop fade show"></div>
    <div @ref="_modalRef"
         class="modal fade show d-block"
         tabindex="-1"
         role="dialog"
         aria-modal="true"
         @onkeydown="OnKeyDown">
        <div class="modal-dialog modal-dialog-centered" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">@state.Title</h5>
                    <button type="button" class="btn-close" aria-label="Close" @onclick="Cancel"></button>
                </div>
                <div class="modal-body">
                    @if (state.Kind == DialogKind.Confirm)
                    {
                        <p class="mb-0">@state.Body</p>
                    }
                    else
                    {
                        <label class="form-label">@state.Body</label>
                        <input @ref="_promptInputRef"
                               class="form-control form-control-sm"
                               placeholder="@state.Placeholder"
                               value="@_promptValue"
                               @oninput="OnPromptInput" />
                    }
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-outline-secondary btn-sm" @onclick="Cancel">Cancel</button>
                    <button type="button"
                            class="btn @(state.Danger ? "btn-danger" : "btn-primary") btn-sm"
                            @onclick="Confirm">
                        @ConfirmLabel(state)
                    </button>
                </div>
            </div>
        </div>
    </div>
}

@code {
    private ElementReference _modalRef;
    private ElementReference _promptInputRef;
    private string _promptValue = string.Empty;
    private DialogState? _lastSeenState;
    private DialogState? _focusedForState;

    protected override void OnInitialized()
    {
        // OnChange lives on the concrete DialogService — the interface stays
        // narrow (just ConfirmAsync / PromptAsync). DI hands us the concrete
        // instance, so a cast here is safe.
        if (Service is DialogService svc) svc.OnChange += OnServiceChanged;
    }

    public void Dispose()
    {
        if (Service is DialogService svc) svc.OnChange -= OnServiceChanged;
    }

    private void OnServiceChanged()
    {
        // Seed prompt input value when a new prompt dialog opens.
        if (Service is DialogService s && s.Current is { Kind: DialogKind.Prompt } promptState
            && !ReferenceEquals(promptState, _lastSeenState))
        {
            _promptValue = promptState.PromptInitial;
        }
        _lastSeenState = (Service as DialogService)?.Current;
        InvokeAsync(StateHasChanged);
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        var current = (Service as DialogService)?.Current;
        if (current is not null)
        {
            try { await JS.InvokeVoidAsync("document.body.classList.add", "modal-open"); }
            catch { /* prerender: no JS — ignore */ }

            // Focus once per opened dialog. Without this guard, every input
            // keystroke triggers a re-render which would re-focus the modal
            // element and yank the caret off the prompt input.
            if (!ReferenceEquals(current, _focusedForState))
            {
                _focusedForState = current;
                try
                {
                    if (current.Kind == DialogKind.Prompt)
                        await _promptInputRef.FocusAsync();
                    else
                        await _modalRef.FocusAsync();
                }
                catch { /* element not yet attached: ignore */ }
            }
        }
        else
        {
            _focusedForState = null;
            try { await JS.InvokeVoidAsync("document.body.classList.remove", "modal-open"); }
            catch { /* prerender: no JS — ignore */ }
        }
    }

    private void OnKeyDown(KeyboardEventArgs e)
    {
        if (e.Key == "Escape")
        {
            Cancel();
        }
    }

    private void OnPromptInput(ChangeEventArgs e)
    {
        _promptValue = e.Value?.ToString() ?? string.Empty;
    }

    private void Cancel()
    {
        if (Service is not DialogService svc || svc.Current is null) return;
        var resolveValue = svc.Current.Kind == DialogKind.Confirm
            ? (object)false
            : (object?)null;
        _promptValue = string.Empty;
        svc.Resolve(resolveValue);
    }

    private void Confirm()
    {
        if (Service is not DialogService svc || svc.Current is null) return;
        var resolveValue = svc.Current.Kind == DialogKind.Confirm
            ? (object)true
            : (object?)_promptValue;
        _promptValue = string.Empty;
        svc.Resolve(resolveValue);
    }

    private static string ConfirmLabel(DialogState state) => state.Kind switch
    {
        DialogKind.Prompt => "Save",
        _ => state.Danger ? "Delete" : "Confirm",
    };
}
