@page "/uns"
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Uns
@inject IUnsTreeService Svc
@inject NavigationManager Nav

<PageTitle>UNS</PageTitle>

<div class="d-flex justify-content-between align-items-center mb-3">
    <h4 class="mb-0">UNS</h4>
    <span class="text-muted small">Changes apply on the next deployment.</span>
</div>

<section class="panel rise" style="animation-delay:.02s">
    <div class="panel-head d-flex justify-content-between align-items-center flex-wrap gap-2">
        <span>Unified namespace</span>
        <div class="d-flex align-items-center gap-2 flex-wrap">
            <input type="text" class="form-control form-control-sm" style="max-width:240px"
                   placeholder="Filter by name (substring)…"
                   @bind="_filter" @bind:event="oninput" />
            <button type="button" class="btn btn-outline-secondary btn-sm" @onclick="ExpandAll">Expand all</button>
            <button type="button" class="btn btn-outline-secondary btn-sm" @onclick="CollapseAll">Collapse all</button>
            <button type="button" class="btn btn-outline-primary btn-sm" @onclick="OpenImportModal">Import equipment CSV</button>
        </div>
    </div>

    @if (_loading)
    {
        <div style="padding:1rem" class="text-muted">
            <span class="spinner-border spinner-border-sm me-1"></span>Loading…
        </div>
    }
    else if (_roots.Count == 0)
    {
        <div style="padding:1rem" class="text-muted">No clusters yet.</div>
    }
    else
    {
        <div style="padding:.5rem 1rem">
            <UnsTree Roots="_roots" Filter="@_filter"
                     OnToggleExpand="ToggleAsync"
                     OnAddChild="HandleAddChild"
                     OnEdit="HandleEdit"
                     OnDelete="HandleDelete" />
        </div>
    }
</section>

<AreaModal Visible="_areaModalVisible"
           IsNew="_areaModalIsNew"
           ClusterId="@_areaModalClusterId"
           Existing="_areaModalExisting"
           Clusters="ClusterOptions"
           OnSaved="OnModalSavedAsync"
           OnCancel="CloseModals" />

<LineModal Visible="_lineModalVisible"
           IsNew="_lineModalIsNew"
           UnsAreaId="@_lineModalAreaId"
           Existing="_lineModalExisting"
           Areas="_lineModalAreaOptions"
           OnSaved="OnModalSavedAsync"
           OnCancel="CloseModals" />

<ImportEquipmentModal Visible="_importModalVisible"
                      OnImported="OnImportedAsync"
                      OnCancel="CloseModals" />

@if (_confirmNode is not null)
{
    <div class="modal-backdrop fade show" style="display:block"></div>
    <div class="modal fade show" tabindex="-1" role="dialog" style="display:block">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">Delete @_confirmNode.Kind</h5>
                    <button type="button" class="btn-close" aria-label="Close" @onclick="CloseModals"></button>
                </div>
                <div class="modal-body">
                    <p>Delete <span class="mono">@_confirmNode.DisplayName</span>? This cannot be undone.</p>
                    @if (_confirmNode.Kind == UnsNodeKind.Enterprise)
                    {
                        <p class="text-danger">This will delete every cluster under this enterprise.</p>
                    }
                    @if (!string.IsNullOrWhiteSpace(_confirmError))
                    {
                        <div class="text-danger small mt-2">@_confirmError</div>
                    }
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-outline-secondary" @onclick="CloseModals" disabled="@_confirmBusy">Cancel</button>
                    <button type="button" class="btn btn-danger" @onclick="ConfirmDeleteAsync" disabled="@_confirmBusy">
                        @if (_confirmBusy) { <span class="spinner-border spinner-border-sm me-1"></span> }
                        Delete
                    </button>
                </div>
            </div>
        </div>
    </div>
}

@code {
    private IReadOnlyList<UnsNode> _roots = Array.Empty<UnsNode>();
    private string? _filter;
    private bool _loading = true;

    // Guards the async modal openers (HandleAddChild/HandleEdit) so a rapid
    // double-action can't race two service loads into the same modal state.
    private bool _modalBusy;

    // --- Area modal state ---
    private bool _areaModalVisible;
    private bool _areaModalIsNew;
    private string? _areaModalClusterId;
    private AreaEditDto? _areaModalExisting;

    // --- Line modal state ---
    private bool _lineModalVisible;
    private bool _lineModalIsNew;
    private string? _lineModalAreaId;
    private LineEditDto? _lineModalExisting;
    private IReadOnlyList<(string Id, string Display)> _lineModalAreaOptions = Array.Empty<(string, string)>();

    // --- Import-equipment-CSV modal state ---
    private bool _importModalVisible;

    // --- Delete-confirm state ---
    private UnsNode? _confirmNode;
    private bool _confirmBusy;
    private string? _confirmError;

    /// <summary>The served-by cluster options for the AreaModal, derived from the loaded tree.</summary>
    private IReadOnlyList<(string Id, string Display)> ClusterOptions =>
        _roots
            .SelectMany(ent => ent.Children)
            .Where(n => n.Kind == UnsNodeKind.Cluster && n.EntityId is not null)
            .Select(n => (n.EntityId!, n.DisplayName))
            .ToList();

    protected override async Task OnInitializedAsync()
    {
        _roots = await Svc.LoadStructureAsync();
        _loading = false;
    }

    /// <summary>Returns the <c>(Id, Display)</c> area options inside a single cluster, for the line picker.</summary>
    private IReadOnlyList<(string Id, string Display)> AreaOptionsForCluster(string? clusterId) =>
        _roots
            .SelectMany(ent => ent.Children)
            .Where(c => c.Kind == UnsNodeKind.Cluster && c.ClusterId == clusterId)
            .SelectMany(c => c.Children)
            .Where(a => a.Kind == UnsNodeKind.Area && a.EntityId is not null)
            .Select(a => (a.EntityId!, a.DisplayName))
            .ToList();

    /// <summary>
    /// Toggles a structural node's expansion. Equipment nodes are leaves (no expander),
    /// so this path only ever fires for Enterprise/Cluster/Area/Line.
    /// </summary>
    private Task ToggleAsync(UnsNode node)
    {
        if (node.Loading) { return Task.CompletedTask; }

        node.Expanded = !node.Expanded;
        return Task.CompletedTask;
    }

    /// <summary>
    /// Opens the create modal for a structural node's primary child: a cluster gets a new area; an area
    /// gets a new line scoped to its cluster. A line's "+ Equipment" navigates to the equipment detail
    /// page in create mode rather than opening a modal.
    /// </summary>
    private async Task HandleAddChild(UnsNode node)
    {
        if (_modalBusy) { return; }
        _modalBusy = true;
        try
        {
            CloseModals();
            switch (node.Kind)
            {
                case UnsNodeKind.Cluster:
                    _areaModalIsNew = true;
                    _areaModalExisting = null;
                    _areaModalClusterId = node.ClusterId ?? node.EntityId;
                    _areaModalVisible = true;
                    break;

                case UnsNodeKind.Area:
                    _lineModalIsNew = true;
                    _lineModalExisting = null;
                    _lineModalAreaId = node.EntityId;
                    _lineModalAreaOptions = AreaOptionsForCluster(node.ClusterId);
                    _lineModalVisible = true;
                    break;

                case UnsNodeKind.Line:
                    Nav.NavigateTo($"/uns/equipment/new?lineId={node.EntityId}");
                    break;
            }
        }
        finally
        {
            _modalBusy = false;
        }
    }

    /// <summary>
    /// Opens the edit modal for the clicked node, loading the entity first to prefill the form and
    /// capture its RowVersion. Only Area and Line are edited via a modal here; equipment is edited on
    /// its own detail page (opened via the tree's "Open" link).
    /// </summary>
    private async Task HandleEdit(UnsNode node)
    {
        if (_modalBusy) { return; }
        _modalBusy = true;
        try
        {
            CloseModals();
            switch (node.Kind)
            {
                case UnsNodeKind.Area:
                    var area = await Svc.LoadAreaAsync(node.EntityId!);
                    if (area is null) { return; }
                    _areaModalIsNew = false;
                    _areaModalExisting = area;
                    _areaModalClusterId = area.ClusterId;
                    _areaModalVisible = true;
                    break;

                case UnsNodeKind.Line:
                    var line = await Svc.LoadLineAsync(node.EntityId!);
                    if (line is null) { return; }
                    _lineModalIsNew = false;
                    _lineModalExisting = line;
                    _lineModalAreaId = line.UnsAreaId;
                    _lineModalAreaOptions = AreaOptionsForCluster(node.ClusterId);
                    _lineModalVisible = true;
                    break;
            }
        }
        finally
        {
            _modalBusy = false;
        }
    }

    /// <summary>Opens the bulk equipment-CSV import modal.</summary>
    private void OpenImportModal()
    {
        CloseModals();
        _importModalVisible = true;
    }

    /// <summary>
    /// Handles the import modal's close after a run. A bulk import can add equipment across many
    /// lines and clusters, so the whole structural tree is reloaded rather than refreshed in place.
    /// </summary>
    private async Task OnImportedAsync()
    {
        _roots = await Svc.LoadStructureAsync();
        CloseModals();
        StateHasChanged();
    }

    /// <summary>Opens the delete-confirm modal for a node, stashing it as the pending target.</summary>
    private void HandleDelete(UnsNode node)
    {
        CloseModals();
        _confirmNode = node;
    }

    /// <summary>
    /// Performs the pending delete. Loads the entity's RowVersion first, then dispatches on Kind.
    /// Area/Line/Equipment trigger a full structural reload on success.
    /// </summary>
    private async Task ConfirmDeleteAsync()
    {
        if (_confirmNode is null) { return; }

        _confirmBusy = true;
        _confirmError = null;
        try
        {
            var node = _confirmNode;
            UnsMutationResult result;

            switch (node.Kind)
            {
                case UnsNodeKind.Area:
                    var area = await Svc.LoadAreaAsync(node.EntityId!);
                    if (area is null) { await ReloadAndCloseAsync(); return; }
                    result = await Svc.DeleteAreaAsync(node.EntityId!, area.RowVersion);
                    break;

                case UnsNodeKind.Line:
                    var line = await Svc.LoadLineAsync(node.EntityId!);
                    if (line is null) { await ReloadAndCloseAsync(); return; }
                    result = await Svc.DeleteLineAsync(node.EntityId!, line.RowVersion);
                    break;

                case UnsNodeKind.Equipment:
                    var equipment = await Svc.LoadEquipmentAsync(node.EntityId!);
                    if (equipment is null) { await ReloadAndCloseAsync(); return; }
                    result = await Svc.DeleteEquipmentAsync(node.EntityId!, equipment.RowVersion);
                    break;

                case UnsNodeKind.Cluster:
                    // Cluster carries no RowVersion (Option B); EntityId mirrors the ClusterId.
                    result = await Svc.DeleteClusterAsync(node.EntityId!);
                    break;

                case UnsNodeKind.Enterprise:
                    // Enterprise is a tree label, not an entity — its name is the DisplayName.
                    // Deletes every cluster carrying that label (refuse-if-children, all-or-nothing).
                    result = await Svc.DeleteEnterpriseAsync(node.DisplayName);
                    break;

                default:
                    // Tag/VirtualTag are not deletable from the global tree.
                    result = new UnsMutationResult(false, "Delete for this node kind is not yet available.");
                    break;
            }

            if (result.Ok)
            {
                await ReloadAndCloseAsync();
            }
            else
            {
                _confirmError = result.Error;
            }
        }
        finally
        {
            _confirmBusy = false;
        }
    }

    /// <summary>Reloads the tree after a successful modal save and closes any open modal.</summary>
    private async Task OnModalSavedAsync()
    {
        _roots = await Svc.LoadStructureAsync();
        CloseModals();
        StateHasChanged();
    }

    /// <summary>Reloads the tree after a successful delete and closes the confirm modal.</summary>
    private async Task ReloadAndCloseAsync()
    {
        _roots = await Svc.LoadStructureAsync();
        CloseModals();
        StateHasChanged();
    }

    /// <summary>Closes every modal and clears its transient state.</summary>
    private void CloseModals()
    {
        _areaModalVisible = false;
        _areaModalIsNew = false;
        _areaModalClusterId = null;
        _areaModalExisting = null;
        _lineModalVisible = false;
        _lineModalIsNew = false;
        _lineModalAreaId = null;
        _lineModalExisting = null;
        _lineModalAreaOptions = Array.Empty<(string, string)>();
        _importModalVisible = false;
        _confirmNode = null;
        _confirmError = null;
    }

    /// <summary>
    /// Expands every structural container node (Enterprise/Cluster/Area/Line) in the tree.
    /// Equipment is a leaf node and has no expansion; <see cref="ExpandStructural"/> skips it.
    /// </summary>
    private void ExpandAll()
    {
        foreach (var root in _roots)
        {
            ExpandStructural(root);
        }
    }

    private static void ExpandStructural(UnsNode node)
    {
        if (node.Kind is UnsNodeKind.Enterprise or UnsNodeKind.Cluster
            or UnsNodeKind.Area or UnsNodeKind.Line)
        {
            node.Expanded = true;
        }

        foreach (var child in node.Children)
        {
            ExpandStructural(child);
        }
    }

    /// <summary>Collapses every node in the tree.</summary>
    private void CollapseAll()
    {
        foreach (var root in _roots)
        {
            CollapseNode(root);
        }
    }

    private static void CollapseNode(UnsNode node)
    {
        node.Expanded = false;
        foreach (var child in node.Children)
        {
            CollapseNode(child);
        }
    }
}
