@namespace Lumeo
@typeparam T

<div class="group focus-visible:outline-none" role="treeitem"
     aria-expanded="@AriaExpanded"
     aria-selected="@IsSelected.ToString().ToLowerInvariant()"
     aria-disabled="@Item.IsDisabled.ToString().ToLowerInvariant()"
     tabindex="@TabIndex"
     @onfocus="HandleFocus"
     @onblur="HandleBlur"
     @onkeydown="HandleKeyDown"
     @onkeydown:stopPropagation="true">

    <div class="@NodeClass" style="padding-left: @(Level * 16)px" @onclick="HandleClick">

        @* Expand chevron / loading spinner / leaf spacer *@
        @if (Item.IsLoading)
        {
            <span class="shrink-0 p-0.5 w-6 flex items-center justify-center">
                <Blazicon Svg="Lucide.Loader" class="h-4 w-4 text-muted-foreground animate-spin" />
            </span>
        }
        else if (ShowExpandChevron && Expandable)
        {
            <button type="button"
                    class="shrink-0 p-0.5 rounded hover:bg-accent transition-colors"
                    @onclick="ToggleExpand"
                    @onclick:stopPropagation="true"
                    tabindex="-1"
                    aria-label="@ExpandLabel">
                <Blazicon Svg="Lucide.ChevronRight" class="@ChevronClass" />
            </button>
        }
        else
        {
            <span class="w-5"></span>
        }

        @* Checkbox *@
        @if (ShowCheckboxes)
        {
            <span @onclick:stopPropagation="true">
                <Checkbox Checked="@Item.IsChecked"
                          IsIndeterminate="@Item.IsIndeterminate"
                          CheckedChanged="HandleCheck"
                          Disabled="@Item.IsDisabled"
                          Class="mr-1" />
            </span>
        }

        @* Label area: custom template OR default icon + text *@
        @if (NodeTemplate is not null)
        {
            @NodeTemplate(Item)
        }
        else
        {
            @if (Item.Icon is { } icon)
            {
                <Blazicon Svg="icon" class="@IconClass" />
            }
            <span class="truncate">@Item.Text</span>
        }
    </div>

    @* Children *@
    @if (HasLoadedChildren && Item.IsExpanded)
    {
        <div role="group">
            @foreach (var child in Item.Children!)
            {
                <TreeViewNode T="T" @key="child" Item="child" Level="@(Level + 1)"
                              ShowCheckboxes="ShowCheckboxes"
                              Expandable="Expandable"
                              CascadeCheck="CascadeCheck"
                              MultiSelect="MultiSelect"
                              SelectedValues="SelectedValues"
                              NodeTemplate="NodeTemplate"
                              LoadChildren="LoadChildren"
                              OnItemClick="OnItemClick"
                              OnItemCheck="OnItemCheck"
                              OnExpandChanged="OnExpandChanged"
                              OnSelectionChanged="OnSelectionChanged" />
            }
        </div>
    }
</div>

@code {
    [Parameter] public TreeView<T>.TreeViewItem<T> Item { get; set; } = default!;
    [Parameter] public int Level { get; set; }
    [Parameter] public bool ShowCheckboxes { get; set; }
    [Parameter] public bool Expandable { get; set; }
    [Parameter] public EventCallback<TreeView<T>.TreeViewItem<T>> OnItemClick { get; set; }
    [Parameter] public EventCallback<TreeView<T>.TreeViewItem<T>> OnItemCheck { get; set; }

    [Parameter] public bool CascadeCheck { get; set; }
    [Parameter] public bool MultiSelect { get; set; }
    [Parameter] public HashSet<T>? SelectedValues { get; set; }
    [Parameter] public RenderFragment<TreeView<T>.TreeViewItem<T>>? NodeTemplate { get; set; }
    [Parameter] public Func<TreeView<T>.TreeViewItem<T>, Task<List<TreeView<T>.TreeViewItem<T>>>>? LoadChildren { get; set; }
    [Parameter] public EventCallback<TreeView<T>.TreeViewItem<T>> OnExpandChanged { get; set; }
    [Parameter] public EventCallback<TreeView<T>.TreeViewItem<T>> OnSelectionChanged { get; set; }

    // Roving tabindex: any node that currently holds DOM focus gets tabindex=0 so
    // the browser can return focus to it. Level-0 nodes are also in the tab sequence
    // initially (before any node has been focused) so the tree is reachable via Tab.
    // Child nodes that have never been focused stay at -1 — they are only reachable
    // via keyboard arrow-key navigation, matching the WAI-ARIA tree pattern.
    private bool _isFocused;
    private string TabIndex => (_isFocused || Level == 0) ? "0" : "-1";
    private string ExpandLabel => Item.IsExpanded ? "Collapse" : "Expand";

    private bool HasLoadedChildren => Item.Children is { Count: > 0 };

    private bool ShowExpandChevron =>
        HasLoadedChildren ||
        (LoadChildren is not null && !Item.IsLeaf && !Item.ChildrenLoaded);

    private bool IsSelected => SelectedValues?.Contains(Item.Value!) == true;

    private string? AriaExpanded => ShowExpandChevron
        ? Item.IsExpanded.ToString().ToLowerInvariant()
        : null;

    private string NodeClass
    {
        get
        {
            var parts = new List<string>
            {
                "flex items-center gap-1.5 py-1 px-2 rounded-md transition-colors text-sm group-focus-visible:ring-[1.5px] group-focus-visible:ring-ring group-focus-visible:ring-offset-1",
                Item.IsDisabled ? "opacity-50 cursor-not-allowed text-muted-foreground" : "cursor-pointer hover:bg-accent/50"
            };
            if (IsSelected && !Item.IsDisabled)
                parts.Add("bg-accent text-accent-foreground");
            return string.Join(" ", parts);
        }
    }

    private string ChevronClass =>
        string.Join(" ", new[]
        {
            "h-4 w-4 text-muted-foreground transition-transform",
            Item.IsExpanded ? "rotate-90" : null
        }.Where(s => !string.IsNullOrWhiteSpace(s)));

    private string IconClass =>
        Item.IsDisabled
            ? "h-4 w-4 text-muted-foreground opacity-50 shrink-0"
            : "h-4 w-4 text-muted-foreground shrink-0";

    private void HandleFocus()
    {
        _isFocused = true;
    }

    private void HandleBlur()
    {
        _isFocused = false;
    }

    private async Task ToggleExpand()
    {
        if (Item.IsExpanded)
        {
            Item.IsExpanded = false;
            await OnExpandChanged.InvokeAsync(Item);
            return;
        }

        if (LoadChildren is not null && !Item.ChildrenLoaded && !Item.IsLeaf && !HasLoadedChildren)
        {
            Item.IsLoading = true;
            StateHasChanged();

            try
            {
                var loaded = await LoadChildren(Item);
                Item.Children = loaded;
                Item.ChildrenLoaded = true;

                // rc.42: if the parent was fully checked before lazy-load, cascade
                // that state to the freshly-loaded children. Without this, a
                // checked parent would expand to reveal unchecked children — a
                // visibly broken tri-state. Indeterminate parents leave loaded
                // children at their default (unchecked) state, then we
                // recompute the parent to reflect actual loaded counts.
                if (Item.IsChecked && !Item.IsIndeterminate)
                {
                    CascadeChecked(Item.Children, true);
                }
                else if (Item.IsIndeterminate)
                {
                    // All loaded children default to unchecked → parent is no
                    // longer indeterminate if nothing is checked. Recompute.
                    var anyChecked = Item.Children.Any(c => c.IsChecked || c.IsIndeterminate);
                    if (!anyChecked)
                    {
                        Item.IsIndeterminate = false;
                        Item.IsChecked = false;
                    }
                }
            }
            finally
            {
                Item.IsLoading = false;
            }
        }

        Item.IsExpanded = true;
        await OnExpandChanged.InvokeAsync(Item);
    }

    private async Task HandleClick()
    {
        if (Item.IsDisabled) return;
        await OnItemClick.InvokeAsync(Item);
    }

    private async Task HandleCheck(bool value)
    {
        if (Item.IsDisabled) return;
        Item.IsChecked = value;
        if (Item.IsIndeterminate && !value)
            Item.IsIndeterminate = false;
        await OnItemCheck.InvokeAsync(Item);
    }

    private async Task HandleKeyDown(KeyboardEventArgs e)
    {
        switch (e.Key)
        {
            case "ArrowRight":
                if (ShowExpandChevron && Expandable && !Item.IsExpanded)
                    await ToggleExpand();
                break;

            case "ArrowLeft":
                if (Item.IsExpanded)
                {
                    Item.IsExpanded = false;
                    await OnExpandChanged.InvokeAsync(Item);
                }
                break;

            case "Enter":
            case " ":
                if (!Item.IsDisabled)
                {
                    if (ShowCheckboxes)
                    {
                        if (Item.IsIndeterminate)
                        {
                            Item.IsIndeterminate = false;
                            Item.IsChecked = true;
                        }
                        else
                        {
                            Item.IsChecked = !Item.IsChecked;
                        }
                        await OnItemCheck.InvokeAsync(Item);
                    }
                    else
                    {
                        await OnItemClick.InvokeAsync(Item);
                    }
                }
                break;
        }
    }

    /// <summary>Cascades a check state to a freshly-loaded subtree (rc.42).
    /// Used after <see cref="LoadChildren"/> resolves so children inherit a
    /// fully-checked parent's state instead of staying unchecked.</summary>
    private static void CascadeChecked(List<TreeView<T>.TreeViewItem<T>> items, bool value)
    {
        foreach (var item in items)
        {
            item.IsChecked = value;
            item.IsIndeterminate = false;
            if (item.Children is { Count: > 0 })
            {
                CascadeChecked(item.Children, value);
            }
        }
    }
}
