@page "/alarms-historian"
@* Live status of the local node's IAlarmHistorianSink (queue depth, drain state) via the
   HistorianAdapterActor.GetStatus query landed in F11. *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@rendermode RenderMode.InteractiveServer
@using Akka.Actor
@using Akka.Hosting
@using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian
@using ZB.MOM.WW.OtOpcUa.Runtime
@using ZB.MOM.WW.OtOpcUa.Runtime.Historian
@inject IRequiredActor<HistorianAdapterActorKey> HistorianActor
@implements IDisposable

<div class="d-flex justify-content-between align-items-center mb-3">
    <h4 class="mb-0">Alarms historian sink</h4>
</div>

<section class="panel notice rise" style="animation-delay:.02s">
    Snapshot from the local node's <span class="mono">HistorianAdapterActor</span>. Default sink
    is a no-op (<span class="mono">NullAlarmHistorianSink</span>); production wires
    <span class="mono">SqliteStoreAndForwardSink</span> with the Wonderware historian sidecar
    behind it. Polling every @PollSeconds s.
</section>

@if (_status is null)
{
    <p class="mt-3">Loading…</p>
}
else
{
    <section class="card-grid rise mt-3" style="animation-delay:.08s">
        <div class="metric-card">
            <div class="panel-head">Queue</div>
            <div class="kv"><span class="k">Depth</span><span class="v numeric">@_status.QueueDepth</span></div>
            <div class="kv"><span class="k">Dead-lettered</span><span class="v numeric">@_status.DeadLetterDepth</span></div>
            <div class="kv"><span class="k">Evicted (lifetime)</span><span class="v numeric">@_status.EvictedCount</span></div>
        </div>

        <div class="metric-card">
            <div class="panel-head">Drain state</div>
            <div class="kv"><span class="k">State</span><span class="v"><span class="@StateChipClass(_status.DrainState)">@_status.DrainState</span></span></div>
            <div class="kv"><span class="k">Last drain</span><span class="v">@(_status.LastDrainUtc?.ToString("u") ?? "—")</span></div>
            <div class="kv"><span class="k">Last success</span><span class="v">@(_status.LastSuccessUtc?.ToString("u") ?? "—")</span></div>
            @if (!string.IsNullOrWhiteSpace(_status.LastError))
            {
                <div class="kv"><span class="k">Last error</span><span class="v text-danger small">@_status.LastError</span></div>
            }
        </div>
    </section>
}

@code {
    private const int PollSeconds = 5;

    private HistorianSinkStatus? _status;
    private Timer? _timer;

    protected override async Task OnInitializedAsync()
    {
        await RefreshAsync();
        _timer = new Timer(_ => _ = InvokeAsync(RefreshAsync), null,
            TimeSpan.FromSeconds(PollSeconds), TimeSpan.FromSeconds(PollSeconds));
    }

    private async Task RefreshAsync()
    {
        try
        {
            _status = await HistorianActor.ActorRef.Ask<HistorianSinkStatus>(
                HistorianAdapterActor.GetStatus.Instance, TimeSpan.FromSeconds(2));
            StateHasChanged();
        }
        catch
        {
            // Actor unavailable (admin-only node, not driver-role) — leave _status null and let
            // the page show "Loading…". A dedicated "this role doesn't run a historian" message
            // would be nicer; lands when we add role gating to the UI.
        }
    }

    private static string StateChipClass(HistorianDrainState state) => state switch
    {
        HistorianDrainState.Disabled => "chip chip-idle",
        HistorianDrainState.Idle => "chip chip-idle",
        HistorianDrainState.Draining => "chip chip-ok",
        HistorianDrainState.BackingOff => "chip chip-caution",
        _ => "chip chip-idle",
    };

    public void Dispose() => _timer?.Dispose();
}
