@page "/alerts"
@* Live alarm tail via SignalR. Subscribes to /hubs/alerts and shows the most-recent
   AlarmTransitionEvent entries published by ScriptedAlarmActor (Runtime/ScriptedAlarms)
   and the AB CIP ALMD bridge. *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@rendermode RenderMode.InteractiveServer
@using ZB.MOM.WW.OtOpcUa.AdminUI.Hubs
@using ZB.MOM.WW.OtOpcUa.Commons.Messages.Alerts
@inject IInProcessBroadcaster<AlarmTransitionEvent> Alarms
@implements IDisposable

<div class="d-flex justify-content-between align-items-center mb-3">
    <h4 class="mb-0">Alerts</h4>
    <div class="d-flex align-items-center gap-2">
        <span class="conn-pill" data-state="@(_connected ? "connected" : "disconnected")">
            <span class="dot"></span><span>@(_connected ? "live" : "disconnected")</span>
        </span>
        <button class="btn btn-sm btn-outline-secondary" @onclick="ClearAsync">Clear</button>
    </div>
</div>

<section class="panel notice rise" style="animation-delay:.02s">
    Live alarm transitions from the cluster's <span class="mono">alerts</span> DPS topic. Shows
    the most-recent @Capacity entries since the page opened; reload for a fresh window. Sources:
    ScriptedAlarmActor, native driver alarm bridges (AB CIP ALMD, Galaxy where wired).
</section>

@if (_rows.Count == 0)
{
    <section class="panel notice rise mt-3" style="animation-delay:.08s">
        No alarms in the current window. The table will populate as soon as a
        ScriptedAlarmActor or driver alarm bridge publishes a transition.
    </section>
}
else
{
    <section class="panel rise mt-3" style="animation-delay:.08s">
        <div class="panel-head">Recent transitions (@_rows.Count)</div>
        <div class="table-wrap">
            <table class="data-table">
                <thead>
                    <tr>
                        <th>Time</th>
                        <th>Alarm</th>
                        <th>Equipment</th>
                        <th>Kind</th>
                        <th class="num">Severity</th>
                        <th>User</th>
                        <th>Message</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (var e in _rows)
                    {
                        <tr>
                            <td><span class="mono small">@e.TimestampUtc.ToString("HH:mm:ss.fff")</span></td>
                            <td><span class="mono">@e.AlarmId</span><div class="text-muted small">@e.AlarmName</div></td>
                            <td><span class="mono small">@e.EquipmentPath</span></td>
                            <td><span class="chip @KindChipClass(e.TransitionKind)">@e.TransitionKind</span></td>
                            <td class="num">@e.Severity</td>
                            <td>@e.User</td>
                            <td>@e.Message</td>
                        </tr>
                    }
                </tbody>
            </table>
        </div>
    </section>
}

@code {
    private const int Capacity = 200;

    private readonly List<AlarmTransitionEvent> _rows = new();
    private bool _connected;

    protected override void OnInitialized()
    {
        // Live alarm tail straight from the in-process broadcaster (fed by AlertSignalRBridge off the
        // 'alerts' DPS topic). A Blazor Server component can't self-connect a SignalR HubConnection
        // behind a reverse proxy — see IInProcessBroadcaster — so we subscribe in-process instead.
        Alarms.Received += OnAlarm;
        _connected = true;
    }

    private void OnAlarm(AlarmTransitionEvent evt) =>
        // Marshal both the mutation and the re-render onto the circuit sync context so this can't
        // race ClearAsync (which runs there) over the shared _rows list.
        InvokeAsync(() =>
        {
            _rows.Insert(0, evt);
            if (_rows.Count > Capacity) _rows.RemoveAt(_rows.Count - 1);
            StateHasChanged();
        });

    private async Task ClearAsync()
    {
        _rows.Clear();
        await InvokeAsync(StateHasChanged);
    }

    private static string KindChipClass(string kind) => kind switch
    {
        "Activated" => "chip-alert",
        "Cleared" => "chip-ok",
        "Acknowledged" or "Confirmed" => "chip-caution",
        "Shelved" or "Disabled" => "chip-idle",
        _ => "chip-idle",
    };

    public void Dispose() => Alarms.Received -= OnAlarm;
}
