using ZB.MOM.WW.ScadaBridge.Commons.Messages.Health;
namespace ZB.MOM.WW.ScadaBridge.HealthMonitoring;
///
/// In-memory state for a single site's health, stored by the central aggregator.
/// Immutable: every state transition produces a new instance which the aggregator
/// installs into its ConcurrentDictionary via an atomic compare-and-swap.
/// This makes handing the reference straight to UI callers safe — a consumer can
/// never observe a torn or half-applied update.
///
public sealed record SiteHealthState
{
/// Gets the unique identifier of the site this state record belongs to.
public required string SiteId { get; init; }
///
/// The latest full received for the site, or
/// null if the site is known only via heartbeats and has not yet sent
/// a report.
///
public SiteHealthReport? LatestReport { get; init; }
///
/// Time the latest full was processed, or
/// null if the site is known only via heartbeats and has not yet sent
/// a report. Used by the UI to surface report staleness during failover;
/// the null case must be rendered as "no report yet" rather than as a
/// timestamp (a default sentinel would display as year-0001).
///
public DateTimeOffset? LastReportReceivedAt { get; init; }
///
/// Time the most recent signal of any kind (full report OR heartbeat) was
/// received. Drives offline detection — heartbeats from the standby keep the
/// site marked online even when the active node is unable to produce a report
/// (mid-failover, brief stalls). Heartbeat cadence is owned by the Cluster
/// Infrastructure / SiteCommunicationActor (every
/// CommunicationOptions.TransportHeartbeatInterval — 5s by default).
///
public DateTimeOffset LastHeartbeatAt { get; init; }
///
/// Gets the instant this site was first observed by the aggregator (via a
/// report or a heartbeat); null only for states not built by the
/// aggregator (hand-constructed fixtures). Anchors metrics-staleness for a
/// site that has NEVER delivered a report (review 01 round-2 N3): with
/// null the staleness check previously
/// skipped the site forever. cannot anchor
/// this — every heartbeat advances it.
///
public DateTimeOffset? FirstSeenAt { get; init; }
/// Gets the sequence number of the last accepted health report, used to reject out-of-order duplicates.
public long LastSequenceNumber { get; init; }
/// Gets a value indicating whether the site is currently considered online.
public bool IsOnline { get; init; }
///
/// Gets a value indicating whether the site is online (heartbeats still
/// arriving) but has sent no full within
/// . This is a signal
/// distinct from liveness: a site whose HealthReportSender died keeps
/// heartbeating and would otherwise show "online with frozen metrics forever".
/// Cleared whenever a fresh report is processed. Heartbeats never set or clear
/// it — they say nothing about the metrics pipeline.
///
public bool IsMetricsStale { get; init; }
///
/// Gets the instant of the last online↔offline flip, or null if the
/// site has never changed status since it was first observed. Answers "when
/// did this site drop / come back". Not moved by report/heartbeat traffic that
/// leaves the online/offline status unchanged, nor by the metrics-stale flag.
///
public DateTimeOffset? LastStatusChangeAt { get; init; }
}