Editor/ProjectionGoldensMenu.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Editor;
using Grains.RazorDesigner.Contracts;
using Grains.RazorDesigner.Projection.Tests;

namespace Grains.RazorDesigner;

public static class ProjectionGoldensMenu
{
    private const string LogPrefix = "[Grains.RazorDesigner]";

    // Dev-only Editor menu entries — hidden in production via RAZORDESIGNER_DEBUG.
    // See Editor/Common/RazorDesignerDebug.cs.
#if RAZORDESIGNER_DEBUG

    [Menu( "Editor", "Razor Designer/Run projection goldens", "fact_check" )]
    public static void RunProjectionGoldens()
    {
        List<GoldenRunner.GoldenResult> results;
        try
        {
            results = GoldenRunner.RunAll();
        }
        catch ( Exception ex )
        {
            Log.Error( $"{LogPrefix} GoldenRunner.RunAll threw: {ex.GetType().Name}: {ex.Message}" );
            EditorUtility.DisplayDialog(
                "Projection Goldens — Error",
                $"RunAll failed with {ex.GetType().Name}:\n{ex.Message}",
                "Okay", "⚠️" );
            return;
        }

        // Tally results
        int pass  = 0;
        int total = results.Count;
        var diffs = new List<string>();

        foreach ( var r in results )
        {
            if ( r.Pass )
            {
                pass++;
                Log.Info( $"{LogPrefix} Goldens: PASS [{r.Kind}]" );
            }
            else
            {
                Log.Warning( $"{LogPrefix} Goldens: FAIL [{r.Kind}]: {r.Message}" );
                if ( diffs.Count < 5 )
                    diffs.Add( $"[{r.Kind}] {r.Message}" );
            }
        }

        Log.Info( $"{LogPrefix} Goldens: {pass}/{total} passed" );

        // Build modal text
        var sb = new StringBuilder();
        sb.AppendLine( $"{pass}/{total} passed" );

        if ( diffs.Count > 0 )
        {
            sb.AppendLine();
            sb.AppendLine( "First failing diff(s):" );
            foreach ( var d in diffs )
            {
                sb.AppendLine( $"  {d}" );
            }
        }

        var icon  = pass == total ? "✅" : "❌";
        var title = pass == total
            ? $"Projection Goldens — {pass}/{total} PASSED"
            : $"Projection Goldens — {pass}/{total} FAILED";

        EditorUtility.DisplayDialog( title, sb.ToString().TrimEnd(), "Okay", icon );
    }


    [Menu( "Editor", "Razor Designer/Dump Contract Table", "table_view" )]
    public static void DumpContractTable()
    {
        Log.Info( $"{LogPrefix} === Dump Contract Table ===" );
        try
        {
            var table = ContractScanner.Table;
            int count = 0;

            foreach ( var c in table.All )
            {
                count++;
                var slotNames   = string.Join( ",", c.Slots.Select( s => s.Name ) );
                var fieldNames  = string.Join( ", ", c.PayloadFields.Select( f =>
                    $"{f.Name}:{f.ClrType.Name}" +
                    ( string.IsNullOrEmpty( f.Group ) ? "" : $"@{f.Group}" ) +
                    ( f.IsOverrideGate ? "(gate)" : "" )
                ) );

                Log.Info( $"{LogPrefix} contract {c.Kind}: " +
                    $"payload={c.PayloadType.Name} " +
                    $"tag='{c.LibraryTag}' " +
                    $"container={c.IsContainer} " +
                    $"icon='{c.InspectorIcon}' " +
                    $"slots=[{slotNames}] " +
                    $"preview={c.PreviewStrategy} " +
                    $"fields={c.PayloadFields.Count}: {fieldNames}" );
            }

            Log.Info( $"{LogPrefix} === Contract Table dump complete: {count} contracts ===" );
            EditorUtility.DisplayDialog(
                "Contract Table Dump",
                $"{count} contracts dumped to the editor console.\n\n" +
                "Expected: 13 contracts, one per ControlType.\n" +
                "Check console for per-kind payload-field counts and names.",
                "Okay", "table_view" );
        }
        catch ( Exception ex )
        {
            Log.Error( $"{LogPrefix} DumpContractTable threw: {ex.GetType().Name}: {ex.Message}" );
            EditorUtility.DisplayDialog(
                "Contract Table Dump — Error",
                $"ContractScanner.Table threw {ex.GetType().Name}:\n{ex.Message}",
                "Okay", "⚠️" );
        }
    }


    [Menu( "Editor", "Razor Designer/Update projection goldens (autoupdate)", "autorenew" )]
    public static void UpdateProjectionGoldens()
    {
        // Confirm before overwriting — this is an intentional-change workflow.
        EditorUtility.DisplayDialog(
            "Update Projection Goldens",
            "This will overwrite all golden 'expected' blocks with current projector output.\n\nProceed?",
            "Cancel",
            "Update All",
            () =>
            {
                try
                {
                    GoldenRunner.WriteAllGoldens( overwrite: true );
                    Log.Info( $"{LogPrefix} Goldens: all expected blocks updated." );
                    EditorUtility.DisplayDialog(
                        "Projection Goldens Updated",
                        "All golden expected blocks have been rewritten from current projector output.\n\nRun 'Run projection goldens' to verify.",
                        "Okay", "✅" );
                }
                catch ( Exception ex )
                {
                    Log.Error( $"{LogPrefix} GoldenRunner.WriteAllGoldens threw: {ex.GetType().Name}: {ex.Message}" );
                    EditorUtility.DisplayDialog(
                        "Projection Goldens — Update Error",
                        $"WriteAllGoldens failed:\n{ex.Message}",
                        "Okay", "⚠️" );
                }
            },
            "❓" );
    }


    [Menu( "Editor", "Razor Designer/Run CSharp goldens", "code" )]
    public static void RunCSharpGoldens()
    {
        List<CSharpGoldenRunner.CSharpGoldenResult> results;
        try
        {
            results = CSharpGoldenRunner.RunAll();
        }
        catch ( Exception ex )
        {
            Log.Error( $"{LogPrefix} CSharpGoldenRunner.RunAll threw: {ex.GetType().Name}: {ex.Message}" );
            EditorUtility.DisplayDialog(
                "CSharp Goldens — Error",
                $"RunAll failed with {ex.GetType().Name}:\n{ex.Message}",
                "Okay", "⚠️" );
            return;
        }

        int pass  = 0;
        int total = results.Count;
        var diffs = new List<string>();

        foreach ( var r in results )
        {
            if ( r.Pass )
            {
                pass++;
                Log.Info( $"{LogPrefix} CSharpGoldens: PASS [{r.Name}]" );
            }
            else
            {
                Log.Warning( $"{LogPrefix} CSharpGoldens: FAIL [{r.Name}]: {r.Message}" );
                if ( diffs.Count < 5 )
                    diffs.Add( $"[{r.Name}] {r.Message}" );
            }
        }

        Log.Info( $"{LogPrefix} CSharpGoldens: {pass}/{total} passed" );

        var sb = new StringBuilder();
        sb.AppendLine( $"{pass}/{total} passed" );
        if ( diffs.Count > 0 )
        {
            sb.AppendLine();
            sb.AppendLine( "First failing diff(s):" );
            foreach ( var d in diffs ) sb.AppendLine( $"  {d}" );
        }

        var icon  = pass == total ? "✅" : "❌";
        var title = pass == total
            ? $"CSharp Goldens — {pass}/{total} PASSED"
            : $"CSharp Goldens — {pass}/{total} FAILED";

        EditorUtility.DisplayDialog( title, sb.ToString().TrimEnd(), "Okay", icon );
    }

    [Menu( "Editor", "Razor Designer/Update CSharp goldens (autoupdate)", "autorenew" )]
    public static void UpdateCSharpGoldens()
    {
        EditorUtility.DisplayDialog(
            "Update CSharp Goldens",
            "This will overwrite all CSharp golden 'expected' blocks with current projector output.\n\nProceed?",
            "Cancel",
            "Update All",
            () =>
            {
                try
                {
                    CSharpGoldenRunner.WriteAllGoldens( overwrite: true );
                    Log.Info( $"{LogPrefix} CSharpGoldens: all expected blocks updated." );
                    EditorUtility.DisplayDialog(
                        "CSharp Goldens Updated",
                        "All expected blocks rewritten from current projector output.\n\nRun 'Run CSharp goldens' to verify.",
                        "Okay", "✅" );
                }
                catch ( Exception ex )
                {
                    Log.Error( $"{LogPrefix} CSharpGoldenRunner.WriteAllGoldens threw: {ex.GetType().Name}: {ex.Message}" );
                    EditorUtility.DisplayDialog(
                        "CSharp Goldens — Update Error",
                        $"WriteAllGoldens failed:\n{ex.Message}",
                        "Okay", "⚠️" );
                }
            },
            "❓" );
    }


    [Menu( "Editor", "Razor Designer/Run round-trip tests", "verified" )]
    public static void RunRoundTripTests()
    {
        var results = RoundTripRunner.RunAll();
        var passed  = results.FindAll( r => r.Pass ).Count;
        var failed  = results.Count - passed;
        Log.Info( $"{LogPrefix} Round-trip: {passed} pass, {failed} fail (of {results.Count})" );
    }

    [Menu( "Editor", "Razor Designer/Re-canonicalize round-trip fixtures", "brush" )]
    public static void RecanonicalizeRoundTripFixtures()
    {
        var root = Sandbox.Project.Current?.RootDirectory?.FullName;
        if ( string.IsNullOrEmpty( root ) )
        {
            Log.Warning( $"{LogPrefix} Re-canonicalize: Project.Current is null." );
            return;
        }
        var dir = System.IO.Path.Combine( root, "Editor", "Projection", "Tests", "Goldens" );
        foreach ( var path in System.IO.Directory.GetFiles( dir, "*.roundtrip.json" ) )
        {
            try
            {
                var original = System.IO.File.ReadAllText( path );
                var env      = System.Text.Json.JsonSerializer.Deserialize<Grains.RazorDesigner.Serialization.IR.IRDocumentEnvelope>(
                    original, Grains.RazorDesigner.Serialization.IR.DesignerIRJson.Options );
                var canonical = System.Text.Json.JsonSerializer.Serialize(
                    env, Grains.RazorDesigner.Serialization.IR.DesignerIRJson.Options );
                if ( canonical.Contains( '\r' ) )
                    canonical = canonical.Replace( "\r\n", "\n" ).Replace( "\r", "\n" );
                System.IO.File.WriteAllText( path, canonical );
                Log.Info( $"{LogPrefix} Re-canonicalized {System.IO.Path.GetFileName( path )}" );
            }
            catch ( System.Exception ex )
            {
                Log.Warning( $"{LogPrefix} Re-canonicalize failed for {path}: {ex.Message}" );
            }
        }
    }

#endif // RAZORDESIGNER_DEBUG
}