using System.Text.Json;
namespace ScadaLink.SiteRuntime.Scripts;
///
/// Read-only globals a trigger expression is compiled against. Exposes only
/// attribute reads, backed by an in-memory snapshot — no I/O, no actor Ask,
/// no side-effecting APIs. A missing attribute key reads as null and
/// never throws.
///
/// Canonical attribute keys are dotted (e.g. "TempSensor.Reading"); the prefix
/// logic here mirrors .
///
public sealed class TriggerExpressionGlobals
{
///
/// Extracts the "expression" field from an Expression-trigger config
/// JSON document. Returns null for a missing, blank, or malformed
/// config — the single parsing idiom shared by InstanceActor, ScriptActor,
/// and AlarmActor.
///
public static string? ExtractExpression(string? triggerConfigJson)
{
if (string.IsNullOrEmpty(triggerConfigJson)) return null;
try
{
using var doc = JsonDocument.Parse(triggerConfigJson);
var expr = doc.RootElement.TryGetProperty("expression", out var e)
? e.GetString()
: null;
return string.IsNullOrWhiteSpace(expr) ? null : expr;
}
catch (JsonException)
{
return null;
}
}
private readonly IReadOnlyDictionary _snapshot;
public TriggerExpressionGlobals(IReadOnlyDictionary snapshot)
=> _snapshot = snapshot;
/// Attributes in the expression's own scope (root prefix).
public ReadOnlyAttributes Attributes => new(_snapshot, "");
/// Indexed access to child compositions' attributes.
public ReadOnlyChildren Children => new(_snapshot);
///
/// Parent composition (null at root). Set by the caller for derived/composed
/// scopes; the runtime actors evaluate at root scope, so this stays null.
///
public ReadOnlyComposition? Parent { get; init; }
///
/// Read-only attribute view anchored at a canonical-name prefix. Indexing
/// resolves to the canonical key ("" → key, "TempSensor" → "TempSensor.key").
///
public sealed class ReadOnlyAttributes
{
private readonly IReadOnlyDictionary _s;
private readonly string _prefix;
public ReadOnlyAttributes(IReadOnlyDictionary s, string prefix)
{
_s = s;
_prefix = prefix;
}
public object? this[string key] =>
_s.TryGetValue(_prefix.Length == 0 ? key : _prefix + "." + key, out var v) ? v : null;
}
/// A read-only view of one composition at a canonical-name path.
public sealed class ReadOnlyComposition
{
private readonly IReadOnlyDictionary _s;
private readonly string _path;
public ReadOnlyComposition(IReadOnlyDictionary s, string path)
{
_s = s;
_path = path;
}
public ReadOnlyAttributes Attributes => new(_s, _path);
}
/// Dictionary-style accessor for child compositions.
public sealed class ReadOnlyChildren
{
private readonly IReadOnlyDictionary _s;
public ReadOnlyChildren(IReadOnlyDictionary s) => _s = s;
public ReadOnlyComposition this[string compositionName] => new(_s, compositionName);
}
}