@namespace Lumeo
@typeparam TItem
@inject Lumeo.Services.Localization.ILumeoLocalizer L

<tr data-slot="datagrid-row" class="@CssClass" style="@RowStyleString"
    role="row"
    aria-rowindex="@AriaRowIndex"
    aria-level="@AriaLevel"
    aria-expanded="@AriaExpanded"
    aria-selected="@(Context?.SelectionMode != DataGridSelectionMode.None ? (IsSelected ? "true" : "false") : null)"
    @onclick="HandleClick"
    @ondblclick="HandleDoubleClick"
    @oncontextmenu="HandleContextMenu"
    @oncontextmenu:preventDefault="@(Context?.RowContextMenuEnabled ?? false)"
    draggable="@(Context?.RowReorderable == true ? "true" : "false")"
    @ondragstart="HandleDragStart"
    @ondragover:preventDefault="true"
    @ondrop="HandleDrop"
    @attributes="AdditionalAttributes">

    @* Row drag handle *@
    @if (Context?.RowReorderable == true)
    {
        <td role="gridcell" class="px-1 py-2 w-8 cursor-grab active:cursor-grabbing" @onclick:stopPropagation="true">
            <Blazicon Svg="Lucide.GripVertical" class="h-4 w-4 text-muted-foreground/60" />
        </td>
    }

    @if (Context?.SelectionMode == DataGridSelectionMode.Multiple)
    {
        <td role="gridcell" class="px-3 py-2 w-10">
            <Checkbox Checked="@IsSelected" CheckedChanged="@OnCheckChanged" />
        </td>
    }
    else if (Context?.SelectionMode == DataGridSelectionMode.Single)
    {
        <td role="gridcell" class="px-3 py-2 w-10">
            <button type="button"
                    class="h-4 w-4 rounded-full border border-primary @(IsSelected ? "bg-primary" : "bg-background")"
                    @onclick="@(() => Context?.ToggleSelection(Item))"
                    @onclick:stopPropagation="true">
                @if (IsSelected)
                {
                    <span class="block h-2 w-2 mx-auto rounded-full bg-primary-foreground"></span>
                }
            </button>
        </td>
    }

    @if (HasDetailTemplate)
    {
        <td role="gridcell" class="px-1 py-2 w-8">
            <button type="button"
                    class="inline-flex items-center justify-center h-6 w-6 rounded hover:bg-muted transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1"
                    @onclick="ToggleExpand"
                    @onclick:stopPropagation="true">
                @if (IsExpanded)
                {
                    <Blazicon Svg="Lucide.ChevronDown" class="h-4 w-4 text-muted-foreground" />
                }
                else
                {
                    <Blazicon Svg="Lucide.ChevronRight" class="h-4 w-4 text-muted-foreground" />
                }
            </button>
        </td>
    }

    @if (Context is not null)
    {
        @if (IsEditing)
        {
            @foreach (var column in Context.VisibleColumns)
            {
                <td @key="column.Id" role="gridcell" class="px-3 py-2 text-sm">
                    @if (column.EditTemplate is not null)
                    {
                        var editCtx = new CellEditContext<TItem>
                        {
                            Item = Item,
                            Column = column,
                            Value = Context.GetEditValue(Item, column),
                            ValueChanged = () => StateHasChanged()
                        };
                        @column.EditTemplate(editCtx)
                    }
                    else
                    {
                        <input class="w-full h-7 rounded border border-input bg-background px-2 text-sm focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring"
                               value="@(Context.GetEditValue(Item, column)?.ToString() ?? "")"
                               @oninput="@(e => Context.SetEditValue(Item, column, e.Value?.ToString()))" />
                    }
                </td>
            }
            @* Save / Cancel buttons cell *@
            <td role="gridcell" class="px-3 py-2 text-sm w-24">
                <div class="flex items-center gap-1">
                    <button type="button"
                            class="inline-flex items-center justify-center h-6 w-6 rounded bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1"
                            title="@L["DataGrid.CommitEdit"]"
                            @onclick="SaveRowEdit"
                            @onclick:stopPropagation="true">
                        <Blazicon Svg="Lucide.Check" class="h-3.5 w-3.5" />
                    </button>
                    <button type="button"
                            class="inline-flex items-center justify-center h-6 w-6 rounded border border-border hover:bg-muted focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1"
                            title="@L["DataGrid.CancelEdit"]"
                            @onclick="CancelRowEdit"
                            @onclick:stopPropagation="true">
                        <Blazicon Svg="Lucide.X" class="h-3.5 w-3.5" />
                    </button>
                </div>
            </td>
        }
        else
        {
            var colIdx = 0;
            var leadingCols = LeadingColumnCount;
            @foreach (var column in Context.VisibleColumns)
            {
                var ci = colIdx;
                <DataGridCell @key="column.Id" TItem="TItem" Item="Item" Column="column"
                              RowIndex="RowIndex"
                              ColumnIndex="ci"
                              AriaColIndex="@(leadingCols + ci + 1)"
                              Navigable="@(!Context.IsGrouped && Context.GroupTree is null && !Context.IsTreeGrid)" />
                colIdx++;
            }
            @if (Context.EditMode == DataGridEditMode.Row)
            {
                <td role="gridcell" class="px-3 py-2 text-sm w-10">
                    <button type="button"
                            class="inline-flex items-center justify-center h-6 w-6 rounded hover:bg-muted transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1"
                            title="@L["DataGrid.Edit"]"
                            @onclick="StartRowEdit"
                            @onclick:stopPropagation="true">
                        <Blazicon Svg="Lucide.Pencil" class="h-3.5 w-3.5 text-muted-foreground" />
                    </button>
                </td>
            }
        }
    }
</tr>

@if (IsExpanded && DetailTemplate is not null)
{
    <DataGridDetailRow TItem="TItem" Item="Item" DetailTemplate="DetailTemplate" ColSpan="@ColSpan" />
}

@code {
    [CascadingParameter] private DataGridContext<TItem>? Context { get; set; }
    [CascadingParameter] private DataGrid<TItem>? ParentGrid { get; set; }
    [CascadingParameter] private DataGridDragState? DragStateCtx { get; set; }
    [Parameter] public TItem Item { get; set; } = default!;
    [Parameter] public int RowIndex { get; set; }
    [Parameter] public RenderFragment<TItem>? DetailTemplate { get; set; }
    [Parameter] public EventCallback<(TItem, double, double)> OnContextMenu { get; set; }
    [Parameter] public string? Class { get; set; }
    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }

    private bool IsSelected => Context?.IsRowSelected(Item) ?? false;
    private bool IsExpanded => Context?.IsRowExpanded(Item) ?? false;
    private bool IsEditing => Context?.IsRowEditing(Item) ?? false;
    private bool HasDetailTemplate => DetailTemplate is not null;

    // 1-based ARIA row index; header occupies row 1 so body rows start at 2.
    private int AriaRowIndex => RowIndex + 2;

    // ARIA tree-grid attributes — only meaningful when IsTreeGrid is true.
    private int? AriaLevel => (Context?.IsTreeGrid == true) ? (Context.TreeLevelOf(Item) + 1) : null;
    private string? AriaExpanded => (Context?.IsTreeGrid == true) ? (IsExpanded ? "true" : "false") : null;

    /// <summary>Count of non-data leading columns rendered before the first data
    /// cell (drag handle, selection, detail toggle). Mirrors DataGridHeader so
    /// aria-colindex values line up between header and body.</summary>
    private int LeadingColumnCount
    {
        get
        {
            var n = 0;
            if (Context?.RowReorderable == true) n++;
            if (Context?.SelectionMode != DataGridSelectionMode.None) n++;
            if (HasDetailTemplate) n++;
            return n;
        }
    }

    private int ColSpan
    {
        get
        {
            var count = Context?.VisibleColumns.Count ?? 0;
            if (Context?.SelectionMode != DataGridSelectionMode.None) count++;
            if (HasDetailTemplate) count++;
            if (Context?.RowReorderable == true) count++;
            if (Context?.EditMode == DataGridEditMode.Row) count++;
            return count;
        }
    }

    // Memoization: at 1k rows × N renders, allocating the joined class string
    // every access is measurable. We hash the inputs that influence the result
    // into a value tuple and return the cached string when the key matches.
    // Under-caching is safe (we recompute, same as before); over-caching would
    // be a regression — every input that mutates the output participates in the key.
    private (object Key, string Css)? _cssCache;

    private string CssClass
    {
        get
        {
            var custom = Context?.RowClass?.Invoke(Item);
            var key = (RowIndex, IsSelected, IsEditing, custom, Class);
            if (_cssCache is { } c && c.Key.Equals(key)) return c.Css;

            var css = string.Join(" ", new[]
            {
                "border-b border-border/40 transition-colors hover:bg-muted/50",
                IsSelected ? "bg-primary/5" : null,
                IsEditing ? "bg-muted/20" : null,
                custom,
                Class
            }.Where(s => !string.IsNullOrWhiteSpace(s)));
            _cssCache = (key, css);
            return css;
        }
    }

    private string? RowStyleString => Context?.RowStyle?.Invoke(Item);

    private async Task HandleClick()
    {
        if (Context is not null)
        {
            if (Context.SelectionMode != DataGridSelectionMode.None)
            {
                Context.ToggleSelection(Item);
            }
            await Context.OnRowClick.InvokeAsync(Item);
        }
    }

    private async Task HandleDoubleClick()
    {
        if (Context is not null)
        {
            await Context.OnRowDoubleClick.InvokeAsync(Item);
        }
    }

    private async Task HandleContextMenu(MouseEventArgs e)
    {
        if (Context is not null && OnContextMenu.HasDelegate)
        {
            await OnContextMenu.InvokeAsync((Item, e.ClientX, e.ClientY));
        }
    }

    private void ToggleExpand()
    {
        Context?.ToggleRowExpand(Item);
    }

    private async Task OnCheckChanged(bool value)
    {
        Context?.ToggleSelection(Item);
    }

    // --- Row Editing ---

    private void StartRowEdit()
    {
        Context?.StartRowEdit(Item);
    }

    private async Task SaveRowEdit()
    {
        if (Context is not null)
            await Context.CommitRowEdit(Item);
    }

    private void CancelRowEdit()
    {
        Context?.CancelRowEdit(Item);
    }

    // --- Row Drag & Drop ---
    //
    // Drag state is held by an instance-bound DataGridDragState cascaded from
    // the owning DataGrid<TItem> (see DataGridHeaderCell for the column drag
    // counterpart). This replaces the previous static-class holder which was
    // process-global; cross-grid drops were applied to the wrong grid. The
    // drop handler now verifies the drag originated in this grid via
    // DragStateCtx.IsOwnDragRow().

    private void HandleDragStart(DragEventArgs e)
    {
        if (DragStateCtx is null) return;
        DragStateCtx.StartRowDrag(RowIndex);
    }

    private void HandleDrop(DragEventArgs e)
    {
        if (DragStateCtx is null) return;

        // Reject cross-grid drops — only honor drags that originated in our owning grid.
        if (!DragStateCtx.IsOwnDragRow())
        {
            DragStateCtx.ResetRow();
            return;
        }

        var sourceIndex = DragStateCtx.SourceRowIndex;
        DragStateCtx.ResetRow();

        if (sourceIndex < 0 || sourceIndex == RowIndex) return;
        ParentGrid?.MoveRow(sourceIndex, RowIndex);
    }
}
