namespace ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
///
/// Wrapper that defers to an inner sink swapped in at
/// runtime. Needed because the production sink (SdkAddressSpaceSink) wraps an
/// OtOpcUaNodeManager that only exists after the SDK StandardServer has
/// started — but Akka actors resolve their sink dependency at construction time, before
/// the hosted service has booted the SDK.
///
/// Bound as a singleton in DI on driver-role hosts; the OPC UA hosted service calls
/// once the server is up. Until that swap happens, every call is a
/// no-op against , so the actor stays safe to
/// receive messages from the moment it boots.
///
public sealed class DeferredAddressSpaceSink : IOpcUaAddressSpaceSink, ISurgicalAddressSpaceSink
{
private volatile IOpcUaAddressSpaceSink _inner = NullOpcUaAddressSpaceSink.Instance;
/// Swap in the production sink. Pass null to revert to the null sink
/// (used during graceful shutdown so post-stop writes don't hit a half-disposed manager).
/// The sink implementation to use, or null to use the null sink.
public void SetSink(IOpcUaAddressSpaceSink? sink) =>
_inner = sink ?? NullOpcUaAddressSpaceSink.Instance;
/// Writes a value to the OPC UA address space through the inner sink.
/// The node ID of the variable.
/// The value to write.
/// The OPC UA quality value.
/// The source timestamp in UTC.
public void WriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime sourceTimestampUtc)
=> _inner.WriteValue(nodeId, value, quality, sourceTimestampUtc);
/// Writes a full alarm-condition state through the inner sink.
/// The node ID of the alarm condition.
/// The full condition state to project onto the node.
/// The source timestamp in UTC.
public void WriteAlarmCondition(string alarmNodeId, AlarmConditionSnapshot state, DateTime sourceTimestampUtc)
=> _inner.WriteAlarmCondition(alarmNodeId, state, sourceTimestampUtc);
/// Materialises a real Part 9 alarm-condition node through the inner sink.
/// The alarm node ID (== ScriptedAlarmId).
/// The equipment folder node ID the condition parents under.
/// The human-readable condition name.
/// The domain alarm type.
/// The domain severity.
/// True for a driver-fed (native) equipment-tag alarm; false (default) for a scripted alarm.
public void MaterialiseAlarmCondition(string alarmNodeId, string equipmentNodeId, string displayName, string alarmType, int severity, bool isNative = false)
=> _inner.MaterialiseAlarmCondition(alarmNodeId, equipmentNodeId, displayName, alarmType, severity, isNative);
/// Ensures a folder exists in the address space through the inner sink.
/// The node ID of the folder.
/// The node ID of the parent folder, or null for root.
/// The display name of the folder.
public void EnsureFolder(string folderNodeId, string? parentNodeId, string displayName)
=> _inner.EnsureFolder(folderNodeId, parentNodeId, displayName);
/// Ensures a variable exists in the address space through the inner sink.
/// The node ID of the variable.
/// The node ID of the parent folder, or null for root.
/// The display name of the variable.
/// The OPC UA data type of the variable.
/// When true the node is created read/write; otherwise read-only.
/// null ⇒ not historized; non-null ⇒ create Historizing with the
/// HistoryRead access bit and register the historian tagname.
/// When true the node is created as a 1-D array; when false (default) scalar.
/// The declared length of the 1-D array when is true.
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable, string? historianTagname = null, bool isArray = false, uint? arrayLength = null)
=> _inner.EnsureVariable(variableNodeId, parentFolderNodeId, displayName, dataType, writable, historianTagname, isArray, arrayLength);
/// Rebuilds the address space through the inner sink.
public void RebuildAddressSpace() => _inner.RebuildAddressSpace();
/// Forwards an in-place tag-attribute update (F10b) to the inner sink when it supports the
/// surgical capability. Returns false otherwise — before the real SdkAddressSpaceSink is
/// swapped in (inner is still the null sink), or any inner sink that isn't surgical — so the caller
/// (AddressSpaceApplier) falls back to a full rebuild. Without this forward the surgical optimization is
/// inert on every driver-role host, because actors inject THIS wrapper, not the inner sink.
/// The node ID of the variable to update in place.
/// Whether the node should be read/write.
/// null ⇒ not historized; non-null ⇒ Historizing + historian binding.
/// True when the inner sink applied the update; false when it lacks the capability or the node is missing.
public bool UpdateTagAttributes(string variableNodeId, bool writable, string? historianTagname)
=> _inner is ISurgicalAddressSpaceSink surgical
&& surgical.UpdateTagAttributes(variableNodeId, writable, historianTagname);
}