@* Galaxy address picker:
     1. Manual tag/attribute entry (always available).
     2. (DriverOperator-gated) Live browse: object tree on the left,
        attribute side-panel on the right. Clicking an attribute commits
        tag_name.AttributeName into the result. *@
@implements IAsyncDisposable
@using Microsoft.AspNetCore.Authorization
@using ZB.MOM.WW.OtOpcUa.AdminUI.Browsing
@using ZB.MOM.WW.OtOpcUa.Commons.Browsing
@inject IBrowserSessionService BrowserService
@inject AuthenticationStateProvider AuthState
@inject IAuthorizationService AuthorizationService

<div class="row g-3">
    <div class="col-md-5">
        <label class="form-label">Tag name</label>
        <input type="text" class="form-control form-control-sm mono" placeholder="DelmiaReceiver_001"
               @bind="_tagName" @bind:after="OnManualChangedAsync" />
        <div class="form-text">Globally unique system (tag) name.</div>
    </div>
    <div class="col-md-5">
        <label class="form-label">Attribute name</label>
        <input type="text" class="form-control form-control-sm mono" placeholder="DownloadPath"
               @bind="_attributeName" @bind:after="OnManualChangedAsync" />
        <div class="form-text">MXAccess attribute name.</div>
    </div>
</div>

@if (_canOperate)
{
    <div class="mt-3 d-flex align-items-center gap-2">
        @if (_token == Guid.Empty)
        {
            <button type="button" class="btn btn-outline-primary btn-sm" disabled="@_opening"
                    @onclick="OpenBrowseAsync">
                @if (_opening) { <span class="spinner-border spinner-border-sm me-1"></span> }
                Browse galaxy
            </button>
        }
        else
        {
            <span class="chip chip-ok">Browser open</span>
            <button type="button" class="btn btn-outline-secondary btn-sm" @onclick="CloseBrowseAsync">Close</button>
        }
        @if (_openError is not null) { <span class="chip chip-bad" title="@_openError">@TruncatedError()</span> }
    </div>

    @if (_token != Guid.Empty)
    {
        <div class="row g-3 mt-1">
            <div class="col-md-7">
                <label class="form-label small">Objects</label>
                <DriverBrowseTree SessionToken="_token" OnNodeSelected="OnObjectSelectAsync"
                                  SelectedNodeId="@_tagName" />
            </div>
            <div class="col-md-5">
                <label class="form-label small">Attributes of @(string.IsNullOrEmpty(_tagName) ? "—" : _tagName)</label>
                <div class="border rounded p-2" style="max-height:420px; overflow:auto; min-height:240px">
                    @if (_attrsLoading)
                    {
                        <span class="spinner-border spinner-border-sm me-1"></span>
                    }
                    else if (_attrsError is not null)
                    {
                        <span class="text-danger small">@_attrsError</span>
                    }
                    else if (_attrs is null)
                    {
                        <span class="text-muted small">Pick an object.</span>
                    }
                    else if (_attrs.Count == 0)
                    {
                        <span class="text-muted small">No attributes.</span>
                    }
                    else
                    {
                        @foreach (var a in _attrs)
                        {
                            var sel = _attributeName == a.Name ? "bg-primary-subtle" : "";
                            <div class="d-flex justify-content-between align-items-center py-1 @sel"
                                 style="cursor:pointer" @onclick="@(() => SelectAttributeAsync(a))">
                                <span class="mono small">@a.Name</span>
                                <span class="text-muted small">@a.DriverDataType@(a.IsArray ? "[]" : "") · @a.SecurityClass</span>
                            </div>
                        }
                    }
                </div>
            </div>
        </div>
    }
}

<div class="mt-3">
    <span class="text-muted small">Result:</span>
    <code class="mono ms-2">@_built</code>
</div>

@code {
    [Parameter] public string CurrentAddress { get; set; } = "";
    [Parameter] public EventCallback<string> CurrentAddressChanged { get; set; }
    [Parameter, EditorRequired] public Func<string> GetConfigJson { get; set; } = () => "{}";

    /// <summary>True when the currently-selected attribute is itself an alarm condition
    /// (<c>DriverAttributeInfo.IsAlarm</c>). The host (TagModal) pre-fills a default native-alarm
    /// object into the TagConfig on commit. Only a side-panel attribute click sets this; manual
    /// tag/attribute typing or an object re-selection clears it (we can't infer alarm-ness by name).</summary>
    [Parameter] public bool SelectedIsAlarm { get; set; }
    [Parameter] public EventCallback<bool> SelectedIsAlarmChanged { get; set; }

    private string _tagName = "";
    private string _attributeName = "";
    private string _built = "";
    private Guid _token = Guid.Empty;
    private bool _opening;
    private bool _attrsLoading;
    private bool _canOperate;
    private string? _openError;
    private string? _attrsError;
    private IReadOnlyList<AttributeInfo>? _attrs;

    protected override async Task OnInitializedAsync()
    {
        var auth = await AuthState.GetAuthenticationStateAsync();
        var authResult = await AuthorizationService.AuthorizeAsync(auth.User, null, AdminUiPolicies.DriverOperator);
        _canOperate = authResult.Succeeded;
        _built = Build();
        await CurrentAddressChanged.InvokeAsync(_built);
    }

    private async Task OpenBrowseAsync()
    {
        _opening = true; _openError = null; StateHasChanged();
        try
        {
            var json = GetConfigJson() ?? "{}";
            var result = await BrowserService.OpenAsync("GalaxyMxGateway", json, default);
            if (result.Ok) _token = result.Token;
            else _openError = result.Message;
        }
        finally { _opening = false; StateHasChanged(); }
    }

    private async Task CloseBrowseAsync()
    {
        var t = _token;
        _token = Guid.Empty;
        _attrs = null;
        StateHasChanged();
        if (t != Guid.Empty) await BrowserService.CloseAsync(t);
    }

    private async Task OnObjectSelectAsync(BrowseNode node)
    {
        _tagName = node.NodeId;
        _attributeName = "";
        await SetSelectedIsAlarmAsync(false);
        _attrs = null;
        _attrsLoading = true;
        _attrsError = null;
        StateHasChanged();
        try
        {
            _attrs = await BrowserService.AttributesAsync(_token, _tagName, default);
        }
        catch (Exception ex)
        {
            _attrsError = ex.Message;
        }
        finally
        {
            _attrsLoading = false;
            await OnChangedAsync();
        }
    }

    private async Task SelectAttributeAsync(AttributeInfo a)
    {
        _attributeName = a.Name;
        await SetSelectedIsAlarmAsync(a.IsAlarm);
        await OnChangedAsync();
    }

    private async Task OnChangedAsync()
    {
        _built = Build();
        await CurrentAddressChanged.InvokeAsync(_built);
    }

    /// <summary>Manual tag/attribute typing: we can't infer alarm-ness from a hand-typed name, so
    /// clear the pre-fill flag before publishing the rebuilt address.</summary>
    private async Task OnManualChangedAsync()
    {
        await SetSelectedIsAlarmAsync(false);
        await OnChangedAsync();
    }

    /// <summary>Publishes the selected attribute's alarm-ness to the host, only when it changed.</summary>
    private async Task SetSelectedIsAlarmAsync(bool isAlarm)
    {
        if (SelectedIsAlarm == isAlarm) { return; }
        SelectedIsAlarm = isAlarm;
        await SelectedIsAlarmChanged.InvokeAsync(isAlarm);
    }

    private string Build()
    {
        if (string.IsNullOrWhiteSpace(_tagName))
            return "";
        if (string.IsNullOrWhiteSpace(_attributeName))
            return _tagName;
        return $"{_tagName}.{_attributeName}";
    }

    private string TruncatedError() =>
        _openError is null ? "" : (_openError.Length > 60 ? _openError[..60] + "…" : _openError);

    public ValueTask DisposeAsync()
    {
        if (_token != Guid.Empty)
        {
            // Fire-and-forget — don't block circuit teardown on a slow remote.
            _ = BrowserService.CloseAsync(_token);
        }
        return ValueTask.CompletedTask;
    }
}
