@namespace Lumeo
@typeparam TItem

<div class="relative w-full overflow-auto" @attributes="AdditionalAttributes">
    <table class="@CssClass">
        <thead class="[&_tr]:border-b [&_tr]:border-border/40">
            <tr class="border-b border-border/40 transition-colors hover:bg-muted/50">
                @if (Selectable)
                {
                    <th class="h-10 w-10 px-2 text-left align-middle font-medium text-muted-foreground">
                        <Checkbox Checked="@IsAllSelected" IsIndeterminate="@IsSomeSelected" CheckedChanged="@ToggleSelectAll" />
                    </th>
                }
                @HeaderTemplate
            </tr>
        </thead>
        <tbody class="[&_tr:last-child]:border-0">
            @if (IsLoading)
            {
                @for (var i = 0; i < SkeletonRows; i++)
                {
                    <tr class="border-b border-border/40">
                        @LoadingTemplate
                    </tr>
                }
            }
            else if (Items is null || !Items.Any())
            {
                <tr>
                    <td colspan="100" class="p-8">
                        @if (EmptyTemplate is not null)
                        {
                            @EmptyTemplate
                        }
                        else
                        {
                            <div class="flex flex-col items-center justify-center py-8 text-center">
                                <p class="text-sm text-muted-foreground">No results.</p>
                            </div>
                        }
                    </td>
                </tr>
            }
            else if (Virtualize)
            {
                <Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize Items="@_itemsList" Context="item" SpacerElement="tr" ItemSize="@(ItemSize > 0 ? ItemSize : 41f)">
                    <tr class="border-b border-border/40 transition-colors hover:bg-muted/50">
                        @if (Selectable)
                        {
                            <td class="w-10 px-2 py-2 align-middle">
                                <Checkbox Checked="@(SelectedItems?.Contains(item) == true)" CheckedChanged="@(c => ToggleItem(item, c))" />
                            </td>
                        }
                        @RowTemplate!(item)
                    </tr>
                </Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize>
            }
            else
            {
                @foreach (var item in _itemsList)
                {
                    <tr @key="item" class="border-b border-border/40 transition-colors hover:bg-muted/50">
                        @if (Selectable)
                        {
                            <td class="w-10 px-2 py-2 align-middle">
                                <Checkbox Checked="@(SelectedItems?.Contains(item) == true)" CheckedChanged="@(c => ToggleItem(item, c))" />
                            </td>
                        }
                        @RowTemplate!(item)
                    </tr>
                }
            }
        </tbody>
    </table>
</div>

@code {
    [Parameter] public IEnumerable<TItem>? Items { get; set; }
    [Parameter] public RenderFragment? HeaderTemplate { get; set; }
    [Parameter] public RenderFragment<TItem>? RowTemplate { get; set; }
    [Parameter] public RenderFragment? LoadingTemplate { get; set; }
    [Parameter] public RenderFragment? EmptyTemplate { get; set; }
    [Parameter] public bool IsLoading { get; set; }
    [Parameter] public int SkeletonRows { get; set; } = 5;
    [Parameter] public string? Class { get; set; }
    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }

    // Sorting
    [Parameter] public string? SortColumn { get; set; }
    [Parameter] public EventCallback<string?> SortColumnChanged { get; set; }
    [Parameter] public SortDirection SortDir { get; set; } = SortDirection.Ascending;
    [Parameter] public EventCallback<SortDirection> SortDirChanged { get; set; }
    [Parameter] public EventCallback<(string Column, SortDirection Direction)> OnSort { get; set; }

    // Selection
    [Parameter] public bool Selectable { get; set; }
    [Parameter] public HashSet<TItem>? SelectedItems { get; set; }
    [Parameter] public EventCallback<HashSet<TItem>> SelectedItemsChanged { get; set; }

    // Virtualization
    /// <summary>Virtualize the row list using Blazor's &lt;Virtualize&gt; — only renders rows in the viewport.</summary>
    [Parameter] public bool Virtualize { get; set; }
    /// <summary>Estimated row height in pixels used by the virtualizer (default 41). Only relevant when <see cref="Virtualize"/> is true.</summary>
    [Parameter] public float ItemSize { get; set; } = 48;

    private string CssClass => $"w-full caption-bottom text-sm {Class}".Trim();

    // Cached per-render — recomputed in OnParametersSet to avoid repeated LINQ on every render pass.
    private List<TItem> _itemsList = [];
    private bool _isAllSelected;
    private bool _isSomeSelected;

    private bool IsAllSelected => _isAllSelected;
    private bool IsSomeSelected => _isSomeSelected;

    protected override void OnParametersSet()
    {
        _itemsList = Items?.ToList() ?? [];
        _isAllSelected = _itemsList.Count > 0 && SelectedItems is not null && _itemsList.All(i => SelectedItems.Contains(i));
        _isSomeSelected = SelectedItems is not null && SelectedItems.Count > 0 && !_isAllSelected;
    }

    private async Task ToggleSelectAll(bool select)
    {
        if (Items is null) return;
        SelectedItems ??= [];
        if (select)
        {
            foreach (var item in Items) SelectedItems.Add(item);
        }
        else
        {
            SelectedItems.Clear();
        }
        RefreshSelectionCache();
        await SelectedItemsChanged.InvokeAsync(SelectedItems);
    }

    private async Task ToggleItem(TItem item, bool selected)
    {
        SelectedItems ??= [];
        if (selected) SelectedItems.Add(item);
        else SelectedItems.Remove(item);
        RefreshSelectionCache();
        await SelectedItemsChanged.InvokeAsync(SelectedItems);
    }

    private void RefreshSelectionCache()
    {
        _isAllSelected = _itemsList.Count > 0 && SelectedItems is not null && _itemsList.All(i => SelectedItems.Contains(i));
        _isSomeSelected = SelectedItems is not null && SelectedItems.Count > 0 && !_isAllSelected;
    }

    public enum SortDirection { None, Ascending, Descending }
}
