Editor/Projection/CSharp/Projectors/LifecycleSymbolProjector.cs
using System.Collections.Generic;
using Grains.RazorDesigner.Wiring;

namespace Grains.RazorDesigner.Projection.CSharp.Projectors;

public static class LifecycleSymbolProjector
{
    public static void Emit(
        LifecycleSymbol s,
        List<CSharpOp> ops,
        CSharpProjectorContext ctx,
        IReadOnlyList<NodeRefSymbol> nodeRefs )
    {
        var (declaredReturn, paramList, hookName, defaultsToAsync) = HookSignature( s.Hook );

        ctx.PushMethodScope();
        var bodyOps = new List<CSharpOp>();

        if ( s.Hook == LifecycleHook.OnAfterTreeRender )
        {
            foreach ( var nr in nodeRefs )
            {
                bodyOps.Add( new Statement(
                    $"{nr.Name} = Find<{nr.Type}>( \"[data-grd-node-id='{nr.TargetNodeId}']\" )" ) );
            }
        }

        // User body
        foreach ( var stmt in s.Body )
            ActionEmitter.Emit( stmt, bodyOps, ctx );

        var bodyDeducedAsync = ctx.PopMethodScope();

        var isAsync = bodyDeducedAsync || defaultsToAsync;
        var retType = ResolveAsyncReturnType( declaredReturn, isAsync );

        ops.Add( new MethodOpen(
            Visibility: "protected",
            IsOverride: true,
            IsAsync: isAsync,
            ReturnType: retType,
            Name: hookName,
            ParameterList: paramList ) );
        ops.AddRange( bodyOps );
        ops.Add( new MethodClose() );
    }

    private static (string ReturnType, string ParamList, string HookName, bool DefaultsToAsync)
        HookSignature( LifecycleHook hook )
    {
        switch ( hook )
        {
            case LifecycleHook.OnParametersSet:
                return ("void", "", "OnParametersSet", false);
            case LifecycleHook.OnParametersSetAsync:
                return ("System.Threading.Tasks.Task", "", "OnParametersSetAsync", true);
            case LifecycleHook.OnAfterTreeRender:
                return ("void", "bool firstTime", "OnAfterTreeRender", false);
            case LifecycleHook.OnUpdate:
                return ("void", "", "OnUpdate", false);
            case LifecycleHook.BuildHash:
                return ("int", "", "BuildHash", false);
            default:
                throw new System.Diagnostics.UnreachableException(
                    $"LifecycleSymbolProjector.HookSignature: unhandled LifecycleHook variant '{hook}'. " +
                    "Add an arm here whenever a new LifecycleHook value is added." );
        }
    }

    private static string ResolveAsyncReturnType( string declaredType, bool isAsync )
    {
        if ( !isAsync ) return declaredType;
        if ( declaredType == "void" ) return "void";
        if ( declaredType == "Task" ) return "System.Threading.Tasks.Task";
        return declaredType;
    }
}