using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.ScriptAnalysis;
///
/// Runtime globals for the Test Run sandbox. Mirrors the real site-runtime
/// ScriptGlobals surface (ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts) member-for-member
/// so the same user code that runs at a site also compiles and runs here.
///
/// Instance-context members — Instance.GetAttribute/SetAttribute/CallScript,
/// Attributes, Children, Parent — need a live deployed
/// instance. With no instance bound they throw ;
/// with one bound (see ) they route to it.
///
/// ExternalSystem, Database, and Scripts.CallShared run
/// against central's real services and fire for real; Notify is a
/// signature-faithful no-op fake. None of them depend on a bound instance.
///
public class SandboxScriptHost
{
///
/// Script parameters passed to the sandbox.
///
public ScriptParameters Parameters { get; init; } = new();
///
/// Cancellation token for the sandbox execution.
///
public CancellationToken CancellationToken { get; init; }
///
/// Alarm context for the sandbox.
///
public AlarmContext? Alarm { get; init; }
///
/// Script scope defining the execution context.
///
public ScriptScope Scope { get; init; } = ScriptScope.Root;
///
/// Instance context providing access to deployed instance data.
///
public SandboxInstanceContext Instance { get; init; } = new();
///
/// Helper for external system calls.
///
public SandboxExternalHelper ExternalSystem => Instance.ExternalSystem;
///
/// Helper for database operations.
///
public SandboxDatabaseHelper Database => Instance.Database;
///
/// Helper for sending notifications.
///
public SandboxNotifyHelper Notify => Instance.Notify;
///
/// Helper for calling scripts.
///
public SandboxScriptCallHelper Scripts => Instance.Scripts;
///
/// Accessor for attributes scoped to the current instance.
///
public SandboxAttributeAccessor Attributes => new(Instance, Scope.SelfPath);
///
/// Accessor for child compositions.
///
public SandboxChildrenAccessor Children => new(Instance, Scope.SelfPath);
///
/// Accessor for the parent composition, or null if at root.
///
public SandboxCompositionAccessor? Parent =>
Scope.ParentPath == null ? null : new SandboxCompositionAccessor(Instance, Scope.ParentPath);
///
/// Read-only accessor for the instance's current alarm conditions, mirroring the site
/// runtime's Alarms global (MES alarm-status API §5.2). Present so a script using
/// Alarms.CurrentAsync() BINDS in the Test Run editor — without it the editor
/// would show a spurious CS1061 on a script that deploys and runs perfectly.
///
public SandboxAlarmsAccessor Alarms { get; } = new();
///
/// Sandbox mirror of the site runtime's Log global (step logs design 2026-09-09 D4).
/// Present so a script that logs BINDS in the Test Run editor; lines go to the Test Run
/// console pane and are never audited. Delegates to
/// exactly as the runtime's ScriptGlobals.Log => Instance.Log does, so
/// Log and Instance.Log are the same object here too.
///
public SandboxLog Log => Instance.Log;
}
///
/// Sandbox mirror of ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts.ScriptLogger (step logs
/// design 2026-09-09 D4). Signature-faithful so the same script body compiles here and at a site,
/// but a Test Run has no execution to trace and no audit trail: every line is written to
/// , which the sandbox captures into
/// SandboxRunResult.ConsoleOutput (see ) and shows in
/// the editor's console pane. Nothing here reaches the node log or the audit log.
///
public class SandboxLog
{
/// Writes a debug-level line to the Test Run console pane.
/// The message text.
/// An optional exception, rendered on a following line.
public void Debug(string message, Exception? exception = null) => Write("debug", message, exception);
/// Writes an information-level line to the Test Run console pane.
/// The message text.
/// An optional exception, rendered on a following line.
public void Info(string message, Exception? exception = null) => Write("info", message, exception);
/// Writes a warning-level line to the Test Run console pane.
/// The message text.
/// An optional exception, rendered on a following line.
public void Warn(string message, Exception? exception = null) => Write("warn", message, exception);
///
/// Writes an error-level line to the Test Run console pane. Like the runtime logger, this
/// does NOT fail the run — it is a message, not a throw.
///
/// The message text.
/// An optional exception, rendered on a following line.
public void Error(string message, Exception? exception = null) => Write("error", message, exception);
private static void Write(string level, string message, Exception? exception)
{
Console.Out.WriteLine($"[{level}] {message}");
if (exception != null)
{
Console.Out.WriteLine(exception.ToString());
}
}
}
///
/// Sandbox mirror of ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts.AlarmsAccessor.
/// Exists for editor/compile parity only: alarm state is held per-instance by the site's
/// Instance Actor and there is no central Test Run route to it, so an actual call throws a
/// clearly-labelled rather than quietly returning an
/// empty list that would read as "nothing is in alarm".
///
public class SandboxAlarmsAccessor
{
///
/// Mirrors AlarmsAccessor.CurrentAsync for compile parity; unsupported at run
/// time in the central Test Run sandbox.
///
/// Token used to cancel the read (unused in the sandbox).
/// Never returns; always throws in the sandbox.
public Task> CurrentAsync(CancellationToken cancellationToken = default)
=> throw new ScriptSandboxException(
"Alarms.CurrentAsync() reads the site Instance Actor's live alarm conditions, " +
"which aren't available in the central Test Run sandbox — deploy to a site to " +
"exercise alarm-reading scripts.");
///
/// Mirrors AlarmsAccessor.MesReportableAsync for compile parity; unsupported at
/// run time in the central Test Run sandbox.
///
/// Token used to cancel the read (unused in the sandbox).
/// Never returns; always throws in the sandbox.
public Task> MesReportableAsync(CancellationToken cancellationToken = default)
=> throw new ScriptSandboxException(
"Alarms.MesReportableAsync() reads the site Instance Actor's live alarm conditions, " +
"which aren't available in the central Test Run sandbox — deploy to a site to " +
"exercise alarm-reading scripts.");
}
///
/// Backs the sandbox Instance when a Test Run is bound to a real
/// deployed instance. Null when unbound. The implementation routes to the
/// instance cross-site over the cluster transport.
///
public interface ISandboxInstanceGateway
{
///
/// Gets the value of an attribute with the specified canonical name.
///
/// The canonical name of the attribute.
/// Cancellation token.
/// The attribute value, or null if not found.
Task