using System.Text.RegularExpressions;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms;
///
/// Per Phase 7 plan decision #13, alarm messages are static-with-substitution
/// templates. The engine resolves {TagPath} tokens at event emission time
/// against current tag values; unresolvable tokens become {?} so the event
/// still fires but the operator sees where the reference broke.
///
///
///
/// Token syntax: {path/with/slashes}. Brace-stripped the contents must
/// match a path the caller's resolver function can look up. No escaping
/// currently — if you need literal braces in the message, reach for a feature
/// request.
///
///
/// Pure function. Same inputs always produce the same string. Tests verify the
/// edge cases (no tokens / one token / many / nested / unresolvable / bad
/// quality / null value).
///
///
public static class MessageTemplate
{
private static readonly Regex TokenRegex = new(@"\{([^{}]+)\}",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
///
/// Resolve every {path} token in using
/// . Tokens whose returned
/// has a non-Good or a null
/// resolve to {?}.
///
public static string Resolve(string template, Func resolveTag)
{
if (string.IsNullOrEmpty(template)) return template ?? string.Empty;
if (resolveTag is null) throw new ArgumentNullException(nameof(resolveTag));
return TokenRegex.Replace(template, match =>
{
var path = match.Groups[1].Value.Trim();
if (path.Length == 0) return "{?}";
var snap = resolveTag(path);
if (snap is null) return "{?}";
if (snap.StatusCode != 0u) return "{?}";
return snap.Value?.ToString() ?? "{?}";
});
}
/// Enumerate the token paths the template references. Used at publish time to validate references exist.
public static IReadOnlyList ExtractTokenPaths(string? template)
{
if (string.IsNullOrEmpty(template)) return Array.Empty();
var tokens = new List();
foreach (Match m in TokenRegex.Matches(template))
{
var path = m.Groups[1].Value.Trim();
if (path.Length > 0) tokens.Add(path);
}
return tokens;
}
}