using Microsoft.CodeAnalysis.Scripting;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Scripting;

namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.Tests;

/// <summary>
///     Compiles scripts against the Phase 7 sandbox + asserts every forbidden API
///     (HttpClient / File / Process / reflection) fails at compile, not at evaluation.
///     Locks decision #6 — scripts can't escape to the broader .NET surface.
/// </summary>
[Trait("Category", "Unit")]
public sealed class ScriptSandboxTests
{
    /// <summary>Verifies that a baseline script compiles successfully.</summary>
    [Fact]
    public void Happy_path_script_compiles_and_returns()
    {
        // Baseline — ctx + Math + basic types must work.
        var evaluator = ScriptEvaluator<FakeScriptContext, double>.Compile(
            """
            var v = (double)ctx.GetTag("X").Value;
            return Math.Abs(v) * 2.0;
            """);
        evaluator.ShouldNotBeNull();
    }

    /// <summary>Verifies that a script can compile, run, and read a seeded tag.</summary>
    [Fact]
    public async Task Happy_path_script_runs_and_reads_seeded_tag()
    {
        var evaluator = ScriptEvaluator<FakeScriptContext, double>.Compile(
            """return (double)ctx.GetTag("In").Value * 2.0;""");

        var ctx = new FakeScriptContext().Seed("In", 21.0);
        var result = await evaluator.RunAsync(ctx, TestContext.Current.CancellationToken);
        result.ShouldBe(42.0);
    }

    /// <summary>Verifies that SetVirtualTag records write operations.</summary>
    [Fact]
    public async Task SetVirtualTag_records_the_write()
    {
        var evaluator = ScriptEvaluator<FakeScriptContext, int>.Compile(
            """
            ctx.SetVirtualTag("Out", 42);
            return 0;
            """);
        var ctx = new FakeScriptContext();
        await evaluator.RunAsync(ctx, TestContext.Current.CancellationToken);
        ctx.Writes.Count.ShouldBe(1);
        ctx.Writes[0].Path.ShouldBe("Out");
        ctx.Writes[0].Value.ShouldBe(42);
    }

    /// <summary>Verifies that file I/O is rejected at compile time.</summary>
    [Fact]
    public void Rejects_File_IO_at_compile()
    {
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, string>.Compile(
                """return System.IO.File.ReadAllText("c:/secrets.txt");"""));
    }

    /// <summary>Verifies that HttpClient is rejected at compile time.</summary>
    [Fact]
    public void Rejects_HttpClient_at_compile()
    {
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var c = new System.Net.Http.HttpClient();
                return 0;
                """));
    }

    /// <summary>Verifies that Process.Start is rejected at compile time.</summary>
    [Fact]
    public void Rejects_Process_Start_at_compile()
    {
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                System.Diagnostics.Process.Start("cmd.exe");
                return 0;
                """));
    }

    /// <summary>Verifies that Assembly.Load is rejected at compile time.</summary>
    [Fact]
    public void Rejects_Reflection_Assembly_Load_at_compile()
    {
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                System.Reflection.Assembly.Load("System.Core");
                return 0;
                """));
    }

    /// <summary>Verifies that Environment.Exit is rejected at compile time.</summary>
    [Fact]
    public void Rejects_Environment_Exit_at_compile()
    {
        // System.Environment lives in System.Private.CoreLib (allow-listed for
        // primitives) so a namespace-prefix deny-list cannot block it. Environment.Exit
        // terminates the whole in-process OPC UA server — every connected client and
        // every driver — so it MUST be rejected member-granularly. (Core.Scripting-001.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                System.Environment.Exit(0);
                return 0;
                """));
    }

    /// <summary>Verifies that Environment.FailFast is rejected at compile time.</summary>
    [Fact]
    public void Rejects_Environment_FailFast_at_compile()
    {
        // Environment.FailFast crashes the host process immediately — same outage as
        // Exit. (Core.Scripting-001.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                System.Environment.FailFast("boom");
                return 0;
                """));
    }

    /// <summary>Verifies that AppDomain is rejected at compile time.</summary>
    [Fact]
    public void Rejects_AppDomain_at_compile()
    {
        // AppDomain.CurrentDomain exposes process-wide control (assembly load events,
        // unhandled-exception hooks). Not script surface. (Core.Scripting-001.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var n = System.AppDomain.CurrentDomain.FriendlyName;
                return 0;
                """));
    }

    /// <summary>Verifies that GC.Collect is rejected at compile time.</summary>
    [Fact]
    public void Rejects_GC_Collect_at_compile()
    {
        // GC.Collect / GC.AddMemoryPressure let a script perturb the whole process's
        // memory subsystem. Not script surface. (Core.Scripting-001.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                System.GC.Collect();
                return 0;
                """));
    }

    /// <summary>Verifies that Activator.CreateInstance is rejected at compile time.</summary>
    [Fact]
    public void Rejects_Activator_CreateInstance_at_compile()
    {
        // Activator.CreateInstance is a reflection-equivalent escape — it can construct
        // a forbidden type by name without ever naming it syntactically. (Core.Scripting-001.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var o = System.Activator.CreateInstance(typeof(object));
                return 0;
                """));
    }

    /// <summary>Verifies that Environment.GetEnvironmentVariable is rejected at compile time.</summary>
    [Fact]
    public void Rejects_Environment_GetEnvironmentVariable_at_compile()
    {
        // The whole System.Environment type is forbidden (Core.Scripting-001) — even the
        // read-only GetEnvironmentVariable member. Once Exit / FailFast made the type
        // dangerous, the cleanest member-granular rule is to deny the type outright; the
        // read path has no legitimate use in a SCADA predicate either.
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, string?>.Compile(
                """return System.Environment.GetEnvironmentVariable("PATH");"""));
    }

    // --- Core.Scripting-002: type-reference node forms that bypassed the old walker ---
    // The old walker only inspected ObjectCreation / Invocation-with-member-access /
    // MemberAccess / bare-Identifier nodes. typeof, generic type arguments, casts,
    // is/as patterns, default(T), array element types, and declared variable types all
    // name a forbidden type without producing any of those nodes. The broadened walker
    // resolves GetTypeInfo on every TypeSyntax / ExpressionSyntax so they are all caught.

    /// <summary>Verifies that typeof a forbidden type is rejected at compile time.</summary>
    [Fact]
    public void Rejects_typeof_forbidden_type_at_compile()
    {
        // Phase 7 plan A.6 explicitly calls out typeof as a sandbox-escape that must
        // fail at compile. (Core.Scripting-002.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, string>.Compile(
                """return typeof(System.IO.File).Name;"""));
    }

    /// <summary>Verifies that a forbidden type in a generic argument is rejected at compile time.</summary>
    [Fact]
    public void Rejects_generic_type_argument_forbidden_type_at_compile()
    {
        // new List<System.IO.FileInfo>() — the forbidden type is nested inside an
        // allowed generic. The walker unwraps type arguments recursively. (Core.Scripting-002.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var l = new System.Collections.Generic.List<System.IO.FileInfo>();
                return l.Count;
                """));
    }

    /// <summary>Verifies that a cast to a forbidden type is rejected at compile time.</summary>
    [Fact]
    public void Rejects_cast_to_forbidden_type_at_compile()
    {
        // (System.IO.Stream)null — a cast expression names the type without invoking
        // or constructing it. (Core.Scripting-002.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var s = (System.IO.Stream)null;
                return 0;
                """));
    }

    /// <summary>Verifies that default of a forbidden type is rejected at compile time.</summary>
    [Fact]
    public void Rejects_default_of_forbidden_type_at_compile()
    {
        // default(System.Reflection.Assembly) names a forbidden type via the default
        // operator. (Core.Scripting-002.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var a = default(System.Reflection.Assembly);
                return 0;
                """));
    }

    /// <summary>Verifies that an is-pattern with a forbidden type is rejected at compile time.</summary>
    [Fact]
    public void Rejects_is_pattern_forbidden_type_at_compile()
    {
        // 'o is System.IO.FileStream' names a forbidden type as the pattern's type
        // operand. (Core.Scripting-002.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, bool>.Compile(
                """
                object o = ctx.GetTag("X").Value;
                return o is System.IO.FileStream;
                """));
    }

    /// <summary>Verifies that an as-expression with a forbidden type is rejected at compile time.</summary>
    [Fact]
    public void Rejects_as_expression_forbidden_type_at_compile()
    {
        // 'o as System.IO.Stream' names a forbidden type via the as operator. (Core.Scripting-002.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                object o = ctx.GetTag("X").Value;
                var s = o as System.IO.Stream;
                return 0;
                """));
    }

    /// <summary>Verifies that array creation with a forbidden element type is rejected at compile time.</summary>
    [Fact]
    public void Rejects_array_creation_forbidden_element_type_at_compile()
    {
        // new System.IO.FileInfo[0] — the forbidden type is the array element type.
        // (Core.Scripting-002.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var a = new System.IO.FileInfo[0];
                return a.Length;
                """));
    }

    /// <summary>Verifies that a local variable with a forbidden type is rejected at compile time.</summary>
    [Fact]
    public void Rejects_local_declared_variable_forbidden_type_at_compile()
    {
        // An explicitly-typed local declaration 'System.Net.Http.HttpClient c;' names
        // a forbidden type with no construction or call. (Core.Scripting-002.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                System.Net.Http.HttpClient c = null;
                return 0;
                """));
    }

    /// <summary>Verifies that typeof a forbidden type inside Activator is rejected at compile time.</summary>
    [Fact]
    public void Rejects_typeof_forbidden_type_inside_Activator_at_compile()
    {
        // Activator is already denied by ForbiddenFullTypeNames, but the typeof operand
        // is independently a forbidden-type reference — confirm the typeof path catches
        // it even when the Activator type itself were allowed. (Core.Scripting-002.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var t = typeof(System.Diagnostics.Process);
                return 0;
                """));
    }

    /// <summary>Verifies that an allowed generic type argument still compiles.</summary>
    [Fact]
    public async Task Allowed_generic_type_argument_still_compiles()
    {
        // Guard against over-blocking: a generic with only allow-listed type arguments
        // (List<int>) must still compile and run. (Core.Scripting-002.)
        var evaluator = ScriptEvaluator<FakeScriptContext, int>.Compile(
            """
            var l = new System.Collections.Generic.List<int> { 1, 2, 3 };
            return l.Count;
            """);
        var result = await evaluator.RunAsync(new FakeScriptContext(), TestContext.Current.CancellationToken);
        result.ShouldBe(3);
    }

    /// <summary>Verifies that typeof an allowed type still compiles.</summary>
    [Fact]
    public async Task Allowed_typeof_still_compiles()
    {
        // typeof against an allow-listed type (int) must not be rejected. (Core.Scripting-002.)
        var evaluator = ScriptEvaluator<FakeScriptContext, string>.Compile(
            """return typeof(int).Name;""");
        var result = await evaluator.RunAsync(new FakeScriptContext(), TestContext.Current.CancellationToken);
        result.ShouldBe("Int32");
    }

    /// <summary>Verifies that script exceptions propagate unwrapped.</summary>
    [Fact]
    public async Task Script_exception_propagates_unwrapped()
    {
        var evaluator = ScriptEvaluator<FakeScriptContext, int>.Compile(
            """throw new InvalidOperationException("boom");""");
        await Should.ThrowAsync<InvalidOperationException>(async () =>
            await evaluator.RunAsync(new FakeScriptContext(), TestContext.Current.CancellationToken));
    }

    /// <summary>Verifies that ctx.Now is available without reaching the wall clock.</summary>
    [Fact]
    public void Ctx_Now_is_available_without_DateTime_UtcNow_reaching_wall_clock()
    {
        // Scripts that need a timestamp go through ctx.Now so tests can pin it.
        var evaluator = ScriptEvaluator<FakeScriptContext, DateTime>.Compile("""return ctx.Now;""");
        evaluator.ShouldNotBeNull();
    }

    /// <summary>Verifies that the Deadband helper is reachable from scripts.</summary>
    [Fact]
    public void Deadband_helper_is_reachable_from_scripts()
    {
        var evaluator = ScriptEvaluator<FakeScriptContext, bool>.Compile(
            """return ScriptContext.Deadband(10.5, 10.0, 0.3);""");
        evaluator.ShouldNotBeNull();
    }

    /// <summary>Verifies that LINQ Enumerable is available from scripts.</summary>
    [Fact]
    public async Task Linq_Enumerable_is_available_from_scripts()
    {
        // LINQ is in the allow-list because SCADA math frequently wants Sum / Average
        // / Where. Confirm it works.
        var evaluator = ScriptEvaluator<FakeScriptContext, int>.Compile(
            """
            var nums = new[] { 1, 2, 3, 4, 5 };
            return nums.Where(n => n > 2).Sum();
            """);
        var result = await evaluator.RunAsync(new FakeScriptContext(), TestContext.Current.CancellationToken);
        result.ShouldBe(12);
    }

    /// <summary>Verifies that DataValueSnapshot is usable in scripts.</summary>
    [Fact]
    public async Task DataValueSnapshot_is_usable_in_scripts()
    {
        // ctx.GetTag returns DataValueSnapshot so scripts branch on quality.
        var evaluator = ScriptEvaluator<FakeScriptContext, bool>.Compile(
            """
            var v = ctx.GetTag("T");
            return v.StatusCode == 0;
            """);
        var ctx = new FakeScriptContext().Seed("T", 5.0);
        var result = await evaluator.RunAsync(ctx, TestContext.Current.CancellationToken);
        result.ShouldBeTrue();
    }

    /// <summary>Verifies that compile errors include location information in diagnostics.</summary>
    [Fact]
    public void Compile_error_gives_location_in_diagnostics()
    {
        // Compile errors must carry the source span so the Admin UI can point at them.
        try
        {
            ScriptEvaluator<FakeScriptContext, int>.Compile("""return fooBarBaz + 1;""");
            Assert.Fail("expected CompilationErrorException");
        }
        catch (CompilationErrorException ex)
        {
            ex.Diagnostics.ShouldNotBeEmpty();
            ex.Diagnostics[0].Location.ShouldNotBeNull();
        }
    }

    // --- Core.Scripting-010: remaining forbidden-namespace vectors not previously tested ---
    // System.Threading.Thread, System.Threading.Tasks, System.Runtime.InteropServices, and
    // Microsoft.Win32 were all in ForbiddenNamespacePrefixes but had no test asserting their
    // rejection. Adding them here closes the coverage gap that allowed Core.Scripting-001 and
    // -002 to go undetected.

    /// <summary>Verifies that Thread instantiation is rejected at compile time.</summary>
    [Fact]
    public void Rejects_Thread_new_at_compile()
    {
        // System.Threading.Thread is in ForbiddenNamespacePrefixes — raw thread creation
        // in a script would bypass the per-evaluation timeout and tie up a thread-pool thread
        // indefinitely. (Core.Scripting-010.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var t = new System.Threading.Thread(() => { });
                t.Start();
                return 0;
                """));
    }

    /// <summary>Verifies that Task.Run is rejected at compile time.</summary>
    [Fact]
    public void Rejects_Tasks_TaskRun_at_compile()
    {
        // System.Threading.Tasks is now in ForbiddenNamespacePrefixes (Core.Scripting-003).
        // Scripts are synchronous predicates — background tasks would outlive the evaluation
        // timeout. (Core.Scripting-010.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var t = System.Threading.Tasks.Task.Run(() => 42);
                return 0;
                """));
    }

    /// <summary>Verifies that InteropServices is rejected at compile time.</summary>
    [Fact]
    public void Rejects_InteropServices_at_compile()
    {
        // System.Runtime.InteropServices gives access to native memory and COM — clearly
        // outside the safe predicate surface. (Core.Scripting-010.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var p = System.Runtime.InteropServices.Marshal.AllocHGlobal(256);
                return 0;
                """));
    }

    /// <summary>Verifies that Win32 Registry access is rejected at compile time.</summary>
    [Fact]
    public void Rejects_Win32_Registry_at_compile()
    {
        // Microsoft.Win32 provides registry access — not appropriate from a sandboxed
        // SCADA predicate. (Core.Scripting-010.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var k = Microsoft.Win32.Registry.CurrentUser;
                return 0;
                """));
    }

    // --- Core.Scripting-012: BCL refs broadened by -008/-016 re-exposed three vectors
    // the original deny-list missed. Each is denied type-granularly in
    // ForbiddenTypeAnalyzer.ForbiddenFullTypeNames; these tests pin the rejection.

    /// <summary>Verifies that ThreadPool.QueueUserWorkItem is rejected at compile time.</summary>
    [Fact]
    public void Rejects_ThreadPool_QueueUserWorkItem_at_compile()
    {
        // System.Threading.ThreadPool.QueueUserWorkItem re-introduces the background-
        // fanout threat Core.Scripting-003 closed against System.Threading.Tasks. The
        // ThreadPool type lives in System.Threading (shared with allowed sync primitives
        // like CancellationToken), so it must be denied type-granularly. (Core.Scripting-012.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                System.Threading.ThreadPool.QueueUserWorkItem(_ => { });
                return 0;
                """));
    }

    /// <summary>Verifies that Timer instantiation is rejected at compile time.</summary>
    [Fact]
    public void Rejects_Timer_new_at_compile()
    {
        // System.Threading.Timer schedules unbounded callback work that outlives the
        // per-evaluation timeout. Same namespace as CancellationToken so type-granular.
        // (Core.Scripting-012.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var t = new System.Threading.Timer(_ => { });
                return 0;
                """));
    }

    /// <summary>Verifies that AssemblyLoadContext is rejected at compile time.</summary>
    [Fact]
    public void Rejects_AssemblyLoadContext_at_compile()
    {
        // System.Runtime.Loader.AssemblyLoadContext loads arbitrary DLLs into the
        // process — defense-in-depth gap that the BCL-wide reference set re-opened.
        // System.Reflection already denies the typical invocation path, but the
        // sandbox boundary stays type-explicit. (Core.Scripting-012.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var alc = System.Runtime.Loader.AssemblyLoadContext.Default;
                return 0;
                """));
    }

    // --- Core.Scripting-013: wrapper-source injection ---

    /// <summary>Verifies that sibling method injection via balanced braces is rejected.</summary>
    [Fact]
    public void Rejects_sibling_method_injection_via_balanced_braces()
    {
        // Core.Scripting-013 — a brace-balanced source that closes Run early and
        // declares a sibling method inside CompiledScript. The analyzer would still
        // walk the injected member, but the documented "method body" contract
        // forbids the shape outright now.
        var ex = Should.Throw<CompilationErrorException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                return 0;
                } public static int Evil() { return 1;
                """));
        ex.Message.ShouldContain("Core.Scripting-013");
    }

    /// <summary>Verifies that sibling class injection via balanced braces is rejected.</summary>
    [Fact]
    public void Rejects_sibling_class_injection_via_balanced_braces()
    {
        // Same shape, but injecting an entire sibling class inside the synthesized
        // namespace. Reject at the type-count check.
        var ex = Should.Throw<CompilationErrorException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                return 0;
                }
                }
                public static class CompiledScript2 { public static int M() { return 0; } }
                namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.Compiled { public static class CompiledScript3 { public static int M() {
                return 0;
                """));
        ex.Message.ShouldContain("Core.Scripting-013");
    }

    // --- Core.Scripting-017: System.Runtime.CompilerServices.Unsafe accessible ---
    // System.Runtime.CompilerServices is not in ForbiddenNamespacePrefixes (only .InteropServices
    // and .Loader are blocked). Unsafe.As<TFrom, TTo> / Unsafe.As<T>(object) allow raw type-
    // reinterpretation without unsafe context at the call site — bypassing the CLR's type-safety
    // checks and enabling type confusion / managed heap corruption. The fix adds
    // System.Runtime.CompilerServices.Unsafe to ForbiddenFullTypeNames (type-granular, shared
    // namespace with harmless CompilerServices types like CallerMemberName, MethodImpl).

    /// <summary>Verifies that Unsafe.As type-reinterpretation is rejected at compile time (Core.Scripting-017).</summary>
    [Fact]
    public void Rejects_Unsafe_As_at_compile()
    {
        // System.Runtime.CompilerServices.Unsafe.As<T>(object) bypasses CLR type checks
        // entirely, enabling type confusion and managed heap corruption without requiring
        // 'unsafe' context at the call site. It must be blocked. (Core.Scripting-017.)
        Should.Throw<ScriptSandboxViolationException>(() =>
            ScriptEvaluator<FakeScriptContext, int>.Compile(
                """
                var x = ctx.GetTag("X").Value;
                var s = System.Runtime.CompilerServices.Unsafe.As<string>(x);
                return 0;
                """));
    }

    /// <summary>Verifies that MethodImplAttribute (a benign CompilerServices type) is still usable (Core.Scripting-017).</summary>
    [Fact]
    public async Task Benign_CompilerServices_attribute_still_usable()
    {
        // System.Runtime.CompilerServices.MethodImplOptions is a harmless enum used
        // with [MethodImpl]. Local functions in scripts can bear it. Denying only
        // System.Runtime.CompilerServices.Unsafe (type-granular) must not block this.
        var evaluator = ScriptEvaluator<FakeScriptContext, int>.Compile(
            """
            [System.Runtime.CompilerServices.MethodImpl(
                System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
            static int Helper(int x) => x * 2;
            return Helper(21);
            """);
        var result = await evaluator.RunAsync(new FakeScriptContext(), TestContext.Current.CancellationToken);
        result.ShouldBe(42);
    }
}
