@* Generic modal-per-row list editor. The parent owns the List<TRow> (a MUTABLE row VM,
   because driver contracts are immutable records). This renders a read-only table with
   Add/Edit/Delete and a modal that edits a CLONED working copy — commit on Save, discard
   on Cancel. NewRow builds a default VM; Clone copies one for the working copy; Validate
   (optional) returns an error string to block commit or null to allow. *@
@typeparam TRow

<section class="panel rise mt-3" style="@_styleDelay">
    <div class="panel-head d-flex align-items-center">
        <span>@Title (@Items.Count)</span>
        <button type="button" class="btn btn-sm btn-outline-primary ms-auto" @onclick="Add">+ Add @ItemNoun</button>
    </div>
    @if (Items.Count == 0)
    {
        <div style="padding:1rem" class="text-muted">No @ItemNoun.ToLowerInvariant() rows.</div>
    }
    else
    {
        <div class="table-wrap">
            <table class="data-table">
                <thead>@HeaderTemplate</thead>
                <tbody>
                    @for (var i = 0; i < Items.Count; i++)
                    {
                        var idx = i;
                        <tr @key="Items[idx]">
                            @RowTemplate(Items[idx])
                            <td class="text-end" style="white-space:nowrap">
                                <button type="button" class="btn btn-sm btn-link p-0 me-2" @onclick="() => Edit(idx)">Edit</button>
                                <button type="button" class="btn btn-sm btn-link p-0 text-danger" @onclick="() => Delete(idx)">Delete</button>
                            </td>
                        </tr>
                    }
                </tbody>
            </table>
        </div>
    }
</section>

@if (_modalOpen && _working is not null)
{
    <div class="modal-backdrop fade show" style="display:block"></div>
    <div class="modal fade show" tabindex="-1" role="dialog" style="display:block">
        <div class="modal-dialog modal-lg" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">@(_editIndex is null ? $"Add {ItemNoun}" : $"Edit {ItemNoun}")</h5>
                    <button type="button" class="btn-close" aria-label="Close" @onclick="Cancel"></button>
                </div>
                <div class="modal-body">
                    @EditTemplate(_working)
                    @if (!string.IsNullOrEmpty(_validationError))
                    {
                        <div class="text-danger small mt-2">@_validationError</div>
                    }
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-outline-secondary" @onclick="Cancel">Cancel</button>
                    <button type="button" class="btn btn-primary" @onclick="Commit">Save</button>
                </div>
            </div>
        </div>
    </div>
}

@code {
    [Parameter, EditorRequired] public List<TRow> Items { get; set; } = default!;
    [Parameter] public EventCallback ItemsChanged { get; set; }
    [Parameter] public string Title { get; set; } = "Items";
    [Parameter] public string ItemNoun { get; set; } = "row";
    [Parameter] public string AnimationDelay { get; set; } = ".18s";
    [Parameter, EditorRequired] public RenderFragment HeaderTemplate { get; set; } = default!;
    [Parameter, EditorRequired] public RenderFragment<TRow> RowTemplate { get; set; } = default!;
    [Parameter, EditorRequired] public RenderFragment<TRow> EditTemplate { get; set; } = default!;
    [Parameter, EditorRequired] public Func<TRow> NewRow { get; set; } = default!;
    [Parameter, EditorRequired] public Func<TRow, TRow> Clone { get; set; } = default!;
    [Parameter] public Func<TRow, IReadOnlyList<TRow>, int?, string?>? Validate { get; set; }

    private string _styleDelay => $"animation-delay:{AnimationDelay}";
    private bool _modalOpen;
    private int? _editIndex;
    private TRow? _working;
    private string? _validationError;

    private void Add()
    {
        _editIndex = null;
        _working = NewRow();
        _validationError = null;
        _modalOpen = true;
    }

    private void Edit(int index)
    {
        _editIndex = index;
        _working = Clone(Items[index]);
        _validationError = null;
        _modalOpen = true;
    }

    private async Task Delete(int index)
    {
        Items.RemoveAt(index);
        await ItemsChanged.InvokeAsync();
    }

    private void Cancel()
    {
        _modalOpen = false;
        _working = default;
        _editIndex = null;
        _validationError = null;
    }

    private async Task Commit()
    {
        if (_working is null) return;
        _validationError = Validate?.Invoke(_working, Items, _editIndex);
        if (_validationError is not null) return;

        if (_editIndex is int i) Items[i] = _working;
        else Items.Add(_working);

        _modalOpen = false;
        _working = default;
        _editIndex = null;
        await ItemsChanged.InvokeAsync();
    }
}
