@using ZB.MOM.WW.GalaxyRepository.Grpc

@*
    Recursive Browse hierarchy node. Renders one Galaxy object, its child
    objects (recursively, lazy-loaded on first expand), and its attributes as
    right-clickable tag rows. Expansion state is local; children render only
    while expanded.

    The expand triangle is shown whenever the server's child_has_children
    projector hint is set (HasChildrenHint), even before children have been
    loaded — clicking it triggers OnLoadChildren so the parent page can fill
    in Node.Children, then the view re-renders.
*@

<div class="tree-node">
    <div class="tree-row @(Node.IsArea ? "tree-row-area" : "tree-row-object")">
        @if (ShowToggle())
        {
            <button type="button" class="tree-toggle" @onclick="ToggleAsync" aria-label="Toggle">
                @(_expanded ? "▾" : "▸")
            </button>
        }
        else
        {
            <span class="tree-toggle tree-toggle-empty"></span>
        }
        <span class="tree-label" @onclick="ToggleAsync">
            <span class="tree-icon">@(Node.IsArea ? "▣" : "◇")</span>
            <span class="tree-name">@Node.DisplayName</span>
            @if (!string.IsNullOrWhiteSpace(Node.Object.TagName)
                && !string.Equals(Node.Object.TagName, Node.DisplayName, StringComparison.Ordinal))
            {
                <code class="tree-tag">@Node.Object.TagName</code>
            }
        </span>
    </div>
    @if (_expanded)
    {
        <div class="tree-children">
            @if (Node.LoadState == BrowseLoadState.Loading)
            {
                <div class="tree-load-status text-secondary">
                    <span class="tree-toggle tree-toggle-empty"></span>
                    <span>⌛ Loading…</span>
                </div>
            }
            else if (Node.LoadState == BrowseLoadState.Error)
            {
                @* Abbreviated: sibling tree rows are nowrap inside a fixed-height
                   scroller, so a full COM/SQL error would stretch the whole pane. *@
                <div class="tree-load-status text-danger" title="@Node.LoadError">
                    <span class="tree-toggle tree-toggle-empty"></span>
                    <span>Failed to load: @DashboardDisplay.Abbreviate(Node.LoadError, 60)</span>
                </div>
            }

            @foreach (DashboardBrowseNode child in Node.Children)
            {
                <BrowseTreeNodeView Node="child"
                                    OnAddTag="OnAddTag"
                                    OnTagContextMenu="OnTagContextMenu"
                                    OnLoadChildren="OnLoadChildren" />
            }
            @foreach (GalaxyAttribute attr in Node.Attributes)
            {
                GalaxyAttribute row = attr;
                <div class="tree-attr"
                     title="@row.FullTagReference"
                     @oncontextmenu:preventDefault="true"
                     @oncontextmenu="@(args => OnTagContextMenu.InvokeAsync((args, row)))"
                     @ondblclick="@(() => OnAddTag.InvokeAsync(row.FullTagReference))">
                    <span class="tree-toggle tree-toggle-empty"></span>
                    <span class="attr-icon">·</span>
                    <span class="attr-name">@row.AttributeName</span>
                    <span class="attr-type">@DisplayType(row)</span>
                    @if (row.IsAlarm)
                    {
                        <span class="attr-flag attr-flag-alarm">alarm</span>
                    }
                    @if (row.IsHistorized)
                    {
                        <span class="attr-flag attr-flag-hist">hist</span>
                    }
                </div>
            }
        </div>
    }
</div>

@code {
    /// <summary>The hierarchy node this view renders.</summary>
    [Parameter]
    [EditorRequired]
    public DashboardBrowseNode Node { get; set; } = null!;

    /// <summary>Raised with a tag's full reference when the operator double-clicks it.</summary>
    [Parameter]
    public EventCallback<string> OnAddTag { get; set; }

    /// <summary>Raised when an attribute row is right-clicked, for the context menu.</summary>
    [Parameter]
    public EventCallback<(MouseEventArgs Event, GalaxyAttribute Attribute)> OnTagContextMenu { get; set; }

    /// <summary>
    ///     Invoked on first expand when the projector hint says this node has children
    ///     but they have not been fetched yet. The callback is expected to populate
    ///     <see cref="DashboardBrowseNode.Children"/> on the node it receives and then
    ///     trigger a re-render.
    /// </summary>
    [Parameter]
    public Func<DashboardBrowseNode, Task>? OnLoadChildren { get; set; }

    private bool _expanded;

    // The triangle is shown whenever the projector says children exist (even
    // pre-load), or attributes are already present, or already-loaded children
    // are sitting on the node.
    private bool ShowToggle()
    {
        return Node.HasChildrenHint
            || Node.Attributes.Count > 0
            || Node.Children.Count > 0;
    }

    private async Task ToggleAsync()
    {
        if (!ShowToggle())
        {
            return;
        }

        _expanded = !_expanded;

        if (_expanded
            && Node.HasChildrenHint
            && Node.LoadState == BrowseLoadState.NotLoaded
            && OnLoadChildren is not null)
        {
            Node.LoadState = BrowseLoadState.Loading;
            try
            {
                await OnLoadChildren(Node);
                Node.LoadState = BrowseLoadState.Loaded;
            }
            catch (Exception ex)
            {
                Node.LoadState = BrowseLoadState.Error;
                Node.LoadError = ex.Message;
            }
        }
    }

    private static string DisplayType(GalaxyAttribute attribute)
    {
        string baseType = string.IsNullOrWhiteSpace(attribute.DataTypeName)
            ? "type?"
            : attribute.DataTypeName;
        if (attribute.IsArray)
        {
            return attribute.ArrayDimensionPresent
                ? $"{baseType}[{attribute.ArrayDimension}]"
                : $"{baseType}[]";
        }

        return baseType;
    }
}
