@using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol
@using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management
@using ZB.MOM.WW.ScadaBridge.CentralUI.Services
@inject IBrowseService BrowseService

@if (_isVisible)
{
    <div class="modal show d-block" tabindex="-1" role="dialog" style="background-color: rgba(0,0,0,0.5);">
        <div class="modal-dialog modal-xl" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">Browse — @ConnectionName</h5>
                    <button type="button" class="btn-close" @onclick="Cancel"></button>
                </div>
                <div class="modal-body">
                    @if (_failure is not null)
                    {
                        <div class="alert alert-danger">
                            @_failureMessage
                            <button class="btn btn-sm btn-outline-danger ms-2" @onclick="RetryRootLoad">Retry</button>
                        </div>
                    }

                    <div class="node-browser-tree">
                        @if (_rootNodes.Count == 0 && _failure is null)
                        {
                            <em class="text-muted">Loading…</em>
                        }
                        else
                        {
                            <ul class="list-unstyled mb-0">
                                @foreach (var node in _rootNodes)
                                {
                                    <TreeRow Node="node" OnToggle="ToggleAsync" OnSelect="Select" SelectedNodeId="@_selectedNodeId" />
                                }
                            </ul>
                        }
                    </div>

                    <hr />

                    <div class="input-group">
                        <span class="input-group-text">Manual node id:</span>
                        <input class="form-control" @bind="_manualNodeId" placeholder="ns=2;s=..." />
                        <button class="btn btn-outline-secondary" @onclick="UseManual" disabled="@string.IsNullOrWhiteSpace(_manualNodeId)">Use</button>
                    </div>
                </div>
                <div class="modal-footer">
                    <span class="me-auto text-muted">Selected: <code>@(_selectedNodeId ?? "(none)")</code></span>
                    <button class="btn btn-secondary" @onclick="Cancel">Cancel</button>
                    <button class="btn btn-primary" @onclick="Confirm" disabled="@string.IsNullOrWhiteSpace(_selectedNodeId)">Select</button>
                </div>
            </div>
        </div>
    </div>
}

@code {
    [Parameter] public string SiteId { get; set; } = "";
    /// <summary>
    /// Name of the site-local data connection. Serves both as the modal-header
    /// display label AND as the routing key for the browse round-trip — the
    /// site's <c>DataConnectionManagerActor</c> indexes its children by
    /// connection name (no id-keyed lookup at the site).
    /// </summary>
    [Parameter] public string ConnectionName { get; set; } = "";
    [Parameter] public string? InitialNodeId { get; set; }
    [Parameter] public EventCallback<string> OnSelected { get; set; }
    [Parameter] public EventCallback OnCancelled { get; set; }

    private bool _isVisible;
    private string? _selectedNodeId;
    private string _manualNodeId = "";
    private BrowseFailure? _failure;
    private string _failureMessage = "";
    private List<TreeNode> _rootNodes = new();

    public sealed class TreeNode
    {
        public TreeNode(string nodeId, string displayName, BrowseNodeClass nodeClass, bool hasChildren)
        {
            NodeId = nodeId;
            DisplayName = displayName;
            NodeClass = nodeClass;
            HasChildren = hasChildren;
        }

        public string NodeId { get; }
        public string DisplayName { get; }
        public BrowseNodeClass NodeClass { get; }
        public bool HasChildren { get; }

        public List<TreeNode>? Children { get; set; }   // null = not loaded yet
        public bool Expanded { get; set; }
        public bool Loading { get; set; }
        public bool Truncated { get; set; }
    }

    private string _runtimeSiteId = "";
    private string _runtimeConnectionName = "";

    public async Task ShowAsync(string siteId, string connectionName, string? initialNodeId)
    {
        // Snapshot at click time. Razor parameter binding propagates on the next
        // render, which would race the immediate LoadRootAsync below.
        _runtimeSiteId = siteId;
        _runtimeConnectionName = connectionName;
        _isVisible = true;
        _manualNodeId = initialNodeId ?? "";
        _selectedNodeId = initialNodeId;
        await LoadRootAsync();
    }

    private async Task LoadRootAsync()
    {
        _failure = null;
        _rootNodes = new();
        StateHasChanged();

        var result = await BrowseService.BrowseChildrenAsync(_runtimeSiteId, _runtimeConnectionName, parentNodeId: null);
        if (result.Failure is not null)
        {
            SetFailure(result.Failure);
            return;
        }

        _rootNodes = result.Children.Select(c => new TreeNode(c.NodeId, c.DisplayName, c.NodeClass, c.HasChildren)).ToList();
        StateHasChanged();
    }

    private async Task ToggleAsync(TreeNode node)
    {
        if (!node.HasChildren) return;

        if (node.Expanded)
        {
            node.Expanded = false;
            return;
        }

        if (node.Children is null)
        {
            node.Loading = true;
            StateHasChanged();
            var result = await BrowseService.BrowseChildrenAsync(_runtimeSiteId, _runtimeConnectionName, node.NodeId);
            node.Loading = false;

            if (result.Failure is not null)
            {
                SetFailure(result.Failure);
                return;
            }

            node.Children = result.Children
                .Select(c => new TreeNode(c.NodeId, c.DisplayName, c.NodeClass, c.HasChildren))
                .ToList();
            node.Truncated = result.Truncated;
        }

        node.Expanded = true;
    }

    private void Select(TreeNode node)
    {
        if (node.NodeClass != BrowseNodeClass.Variable) return;
        _selectedNodeId = node.NodeId;
        _manualNodeId = node.NodeId;
    }

    // Task 17: map each BrowseFailureKind to a friendly UI message. Messages are
    // protocol-agnostic (the dialog serves every browsable protocol — OPC UA,
    // MxGateway, …). The raw failure.Message is surfaced verbatim for ServerError
    // (which carries the underlying protocol SDK's own error text), for
    // NotBrowsable when the adapter supplied a reason (e.g. a gateway build that
    // lacks the browse RPC), and as the default fallback for any future failure
    // kind added without a UI mapping.
    private void SetFailure(BrowseFailure failure)
    {
        _failure = failure;
        _failureMessage = failure.Kind switch
        {
            BrowseFailureKind.ConnectionNotFound      => "Connection no longer exists at the site.",
            BrowseFailureKind.ConnectionNotConnected  => "Connection not connected — retry shortly or use manual entry.",
            BrowseFailureKind.NotBrowsable            => string.IsNullOrWhiteSpace(failure.Message)
                                                            ? "This connection does not support browsing."
                                                            : failure.Message,
            BrowseFailureKind.Timeout                 => "Browse timed out — the server may be slow. Try again or enter the node id manually.",
            BrowseFailureKind.ServerError             => $"Server error: {failure.Message}",
            _                                          => failure.Message
        };
        StateHasChanged();
    }

    private Task RetryRootLoad() => LoadRootAsync();

    private void UseManual()
    {
        _selectedNodeId = _manualNodeId.Trim();
    }

    private async Task Confirm()
    {
        _isVisible = false;
        if (!string.IsNullOrWhiteSpace(_selectedNodeId))
            await OnSelected.InvokeAsync(_selectedNodeId!);
    }

    private async Task Cancel()
    {
        _isVisible = false;
        await OnCancelled.InvokeAsync();
    }
}
