Editor/Core/RuntimeSceneAdapterContract.cs
using System;
using System.Text.Json.Nodes;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace TeamCreate;

public sealed record RuntimeSessionDescriptor
{
    public RuntimeSessionKind Kind { get; init; }

    public object SessionToken { get; init; }

    public object ParentSessionToken { get; init; }

    public bool IsValid { get; init; }

    public bool IsDestroyed { get; init; }

    public string SourceScenePath { get; init; }
}

public static class RuntimeSessionGuard
{
    public static RuntimeSessionDescriptor EnsureRuntimeSession(RuntimeSessionDescriptor session, object expectedSourceSession, string expectedSourcePath)
    {
        if ((object)session == null || session.SessionToken == null || !session.IsValid || session.IsDestroyed)
        {
            throw new InvalidOperationException("Runtime session is missing or destroyed.");
        }
        if (session.Kind != RuntimeSessionKind.Game)
        {
            throw new InvalidOperationException("Runtime scene adapter accepts only GameEditorSession.");
        }
        if (expectedSourceSession == null)
        {
            throw new InvalidOperationException("Expected source session is missing.");
        }
        if (!RuntimeStateRules.IsSafeScenePath(expectedSourcePath))
        {
            throw new InvalidOperationException("Configured source path is not a safe scenes/... path.");
        }
        if (!RuntimeStateRules.IsSafeScenePath(session.SourceScenePath))
        {
            throw new InvalidOperationException("Runtime session source path is not a safe scenes/... path.");
        }
        if (session.ParentSessionToken != expectedSourceSession)
        {
            throw new InvalidOperationException("Runtime session parent is not the expected source session.");
        }
        if (!string.Equals(CanonicalPath(session.SourceScenePath), CanonicalPath(expectedSourcePath), StringComparison.Ordinal))
        {
            throw new InvalidOperationException("Runtime session source path does not match the configured source path.");
        }
        return session;
    }

    private static string CanonicalPath(string path)
    {
        return path.Replace('\\', '/').ToLowerInvariant();
    }
}

public static class RuntimeSceneCapture
{
    public static ObjectState CaptureSerializedObject(JsonNode serialized, string parent)
    {
        if (serialized == null)
        {
            throw new InvalidOperationException("Runtime state shape is invalid: serialized object is missing.");
        }
        return RuntimeSceneSerialization.ProjectObject(serialized, parent);
    }

    public static void EnsureRuntimeObjectEntry(object runtimeEntry, bool isRoot)
    {
        if (runtimeEntry == null)
        {
            throw new InvalidOperationException("Runtime state shape is invalid: runtime object entry is null (" + (isRoot ? "root" : "child") + ").");
        }
    }

    public static ObjectState CaptureObject(object runtimeEntry, Guid expectedRuntimeId, JsonNode serialized, string parent, bool isRoot)
    {
        EnsureRuntimeObjectEntry(runtimeEntry, isRoot);
        if (expectedRuntimeId == Guid.Empty)
        {
            throw new InvalidOperationException("Runtime object identity is invalid: runtime object id is missing or invalid.");
        }
        ObjectState objectState = CaptureSerializedObject(serialized, parent);
        if (!string.Equals(expectedRuntimeId.ToString("D"), objectState.Id, StringComparison.Ordinal))
        {
            throw new InvalidOperationException($"Runtime object identity is invalid: runtime object id {expectedRuntimeId:D} does not match serialized __guid.");
        }
        return objectState;
    }
}

public static class RuntimeSceneSerialization
{
    public static ObjectState ProjectObject(JsonNode serialized, string parent)
    {
        if (serialized == null)
        {
            throw new InvalidOperationException("Runtime state shape is invalid: serialized object is missing.");
        }
        JsonNode jsonNode;
        try
        {
            jsonNode = JsonNode.Parse(serialized.ToJsonString());
        }
        catch (Exception innerException)
        {
            throw new InvalidOperationException("Runtime state shape is invalid: typed JSON could not be normalized.", innerException);
        }
        if (!(jsonNode is JsonObject jsonObject))
        {
            throw new InvalidOperationException("Runtime state shape is invalid: normalized serialization must be a JSON object.");
        }
        if (jsonObject.Any((KeyValuePair<string, JsonNode> field) => field.Key.Contains("Prefab", StringComparison.OrdinalIgnoreCase)))
        {
            throw new InvalidOperationException("Runtime state is not allowlisted: prefab data");
        }
        JsonObject jsonObject2 = new JsonObject();
        if (!TryGetString(jsonObject, "__guid", out var value) || !Guid.TryParse(value, out var result))
        {
            throw new InvalidOperationException("Runtime object identity is invalid: serialized object __guid is missing or invalid.");
        }
        jsonObject2["__guid"] = result.ToString("D");
        string[] array = new string[6] { "Name", "Position", "Rotation", "Scale", "Enabled", "Tags" };
        foreach (string propertyName in array)
        {
            if (jsonObject.TryGetPropertyValue(propertyName, out JsonNode jsonNode2))
            {
                jsonObject2[propertyName] = jsonNode2?.DeepClone();
            }
        }
        if (jsonObject.TryGetPropertyValue("Components", out JsonNode jsonNode3))
        {
            if (!(jsonNode3 is JsonArray jsonArray))
            {
                throw new InvalidOperationException("Runtime state shape is invalid: GameObject field 'Components' must be a JSON array");
            }
            JsonArray jsonArray2 = new JsonArray();
            foreach (JsonNode item in jsonArray)
            {
                if (!(item is JsonObject jsonObject3))
                {
                    throw new InvalidOperationException("Runtime state shape is invalid: Components entries must be JSON objects");
                }
                if (jsonObject3.Any((KeyValuePair<string, JsonNode> field) => field.Key.Contains("Prefab", StringComparison.OrdinalIgnoreCase)))
                {
                    throw new InvalidOperationException("Runtime state is not allowlisted: prefab data");
                }
                if (!TryGetString(jsonObject3, "__type", out var value2) || !RuntimeStateRules.IsAllowedComponentType(value2))
                {
                    throw new InvalidOperationException("Runtime component type is not allowlisted: " + value2);
                }
                if (!TryGetString(jsonObject3, "__guid", out var value3) || !Guid.TryParse(value3, out var result2))
                {
                    throw new InvalidOperationException("Runtime object identity is invalid: component __guid is missing or invalid");
                }
                JsonObject jsonObject4 = new JsonObject
                {
                    ["__type"] = value2,
                    ["__guid"] = result2.ToString("D")
                };
                foreach (KeyValuePair<string, JsonNode> item2 in jsonObject3)
                {
                    string key = item2.Key;
                    bool flag = ((key == "__type" || key == "__guid") ? true : false);
                    if (!flag && RuntimeStateRules.IsAllowedComponentField(value2, item2.Key))
                    {
                        jsonObject4[item2.Key] = item2.Value?.DeepClone();
                    }
                }
                jsonArray2.Add(jsonObject4);
            }
            jsonObject2["Components"] = jsonArray2;
        }
        ObjectState objectState = new ObjectState(result.ToString("D"), parent, jsonObject2);
        RuntimeStateRules.ValidateObject(objectState);
        return objectState;
    }

    private static bool TryGetString(JsonObject data, string key, out string value)
    {
        value = null;
        JsonNode jsonNode;
        return data.TryGetPropertyValue(key, out jsonNode) && jsonNode is JsonValue jsonValue && jsonValue.TryGetValue<string>(out value);
    }
}

public enum RuntimeSceneOperationKind
{
    Create,
    Update,
    Delete
}

public enum RuntimeSceneApplyStatus
{
    Unsupported,
    Ready
}

public sealed record RuntimeSceneOperation(string Id, RuntimeSceneOperationKind Kind, ObjectState Before, ObjectState After);

public sealed class RuntimeSceneApplyPlan
{
    public string ScenePath { get; }

    public RuntimeSceneApplyStatus Status { get; }

    public bool MutationSupported => Status == RuntimeSceneApplyStatus.Ready;

    public IReadOnlyList<RuntimeSceneOperation> Operations { get; }

    public string Reason { get; }

    public RuntimeSceneApplyPlan(string scenePath, RuntimeSceneApplyStatus status, IEnumerable<RuntimeSceneOperation> operations, string reason)
    {
        ScenePath = scenePath;
        Status = status;
        Operations = operations?.ToArray() ?? Array.Empty<RuntimeSceneOperation>();
        Reason = reason;
    }
}

public static class RuntimeSceneApplyPlanner
{
    public const string UnsupportedReason = "Runtime scene mutation is disabled until a tested runtime undo/apply strategy is proven.";

    public static RuntimeSceneApplyPlan Plan(string scenePath, IReadOnlyDictionary<string, ObjectState> before, IReadOnlyDictionary<string, ObjectState> target)
    {
        Dictionary<string, ObjectState> current = ValidateAndCanonicalize(scenePath, before);
        Dictionary<string, ObjectState> desired = ValidateAndCanonicalize(scenePath, target);
        RuntimeSceneOperation[] operations = (from operation in current.Keys.Union(desired.Keys, StringComparer.Ordinal).OrderBy((string id) => id, StringComparer.Ordinal).Select((string id) =>
            {
                current.TryGetValue(id, out var value);
                desired.TryGetValue(id, out var value2);
                RuntimeSceneOperationKind kind = (((object)value != null) ? (((object)value2 != null) ? RuntimeSceneOperationKind.Update : RuntimeSceneOperationKind.Delete) : RuntimeSceneOperationKind.Create);
                return new RuntimeSceneOperation(id, kind, Clone(value), Clone(value2));
            })
            where operation.Kind != RuntimeSceneOperationKind.Update || !StateRules.Equal(operation.Before, operation.After)
            select operation).ToArray();
        return new RuntimeSceneApplyPlan(scenePath, RuntimeSceneApplyStatus.Unsupported, operations, "Runtime scene mutation is disabled until a tested runtime undo/apply strategy is proven.");
    }

    private static Dictionary<string, ObjectState> ValidateAndCanonicalize(string scenePath, IReadOnlyDictionary<string, ObjectState> objects)
    {
        RuntimeStateRules.ValidateSnapshot(scenePath, objects);
        Dictionary<string, ObjectState> dictionary = new Dictionary<string, ObjectState>(StringComparer.Ordinal);
        foreach (KeyValuePair<string, ObjectState> @object in objects)
        {
            string text = Guid.Parse(@object.Key).ToString("D");
            string parent = (string.IsNullOrEmpty(@object.Value.Parent) ? @object.Value.Parent : Guid.Parse(@object.Value.Parent).ToString("D"));
            JsonObject jsonObject = (JsonObject)@object.Value.Data.DeepClone();
            jsonObject["__guid"] = text;
            if (jsonObject["Components"] is JsonArray jsonArray)
            {
                foreach (JsonNode item in jsonArray)
                {
                    if (item is JsonObject jsonObject2 && Guid.TryParse(jsonObject2["__guid"]?.GetValue<string>(), out var result))
                    {
                        jsonObject2["__guid"] = result.ToString("D");
                    }
                }
            }
            if (!dictionary.TryAdd(text, new ObjectState(text, parent, jsonObject)))
            {
                throw new InvalidOperationException("Runtime object identity is invalid: snapshot contains equivalent duplicate object id " + text);
            }
        }
        RuntimeStateRules.ValidateSnapshot(scenePath, dictionary);
        return dictionary;
    }

    private static ObjectState Clone(ObjectState state)
    {
        return ((object)state == null) ? null : state with
        {
            Data = (JsonObject)state.Data.DeepClone()
        };
    }
}

public sealed class RuntimeSceneTargetOnlyBoundary
{
    private readonly SourceSessionCallCounter sourceCalls;

    private int planCalls;

    public static RuntimeSceneTargetOnlyBoundary Shared { get; } = new RuntimeSceneTargetOnlyBoundary();

    public int SourceCalls => sourceCalls.TotalCalls;

    public int PlanCalls => Volatile.Read(in planCalls);

    public RuntimeSceneTargetOnlyBoundary(SourceSessionCallCounter sourceCalls)
    {
        this.sourceCalls = sourceCalls ?? throw new ArgumentNullException("sourceCalls");
    }

    public RuntimeSceneTargetOnlyBoundary()
        : this(new SourceSessionCallCounter())
    {
    }

    public RuntimeSceneApplyPlan Plan(string scenePath, IReadOnlyDictionary<string, ObjectState> before, IReadOnlyDictionary<string, ObjectState> target)
    {
        Interlocked.Increment(ref planCalls);
        return RuntimeSceneApplyPlanner.Plan(scenePath, before, target);
    }

    public RuntimeSceneApplyPlan PlanForAdapter(string scenePath, IReadOnlyDictionary<string, ObjectState> before, IReadOnlyDictionary<string, ObjectState> target)
    {
        return Plan(scenePath, before, target);
    }
}

public sealed class SourceSessionCallCounter
{
    public int SaveCalls { get; private set; }

    public int ReloadCalls { get; private set; }

    public int DirtyCalls { get; private set; }

    public int UndoCalls { get; private set; }

    public int TotalCalls => SaveCalls + ReloadCalls + DirtyCalls + UndoCalls;

    public void RecordSave()
    {
        SaveCalls++;
    }

    public void RecordReload()
    {
        ReloadCalls++;
    }

    public void RecordDirty()
    {
        DirtyCalls++;
    }

    public void RecordUndo()
    {
        UndoCalls++;
    }
}