using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using Unity.Pipeline.Commands;
using Unity.Pipeline.Editor.Authoring;
using Unity.Pipeline.Editor.Commands.GameObjects;
using Unity.Pipeline.Models;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Unity.Pipeline.Editor.Commands.Scripts
{
///
/// Read and write serialized ([SerializeField] / public) fields on a component or asset through
/// Unity's model (CLI-195).
///
/// Why SerializedObject and not plain reflection: it honours Unity's serialization rules (private
/// [SerializeField] fields, property drawers, prefab overrides), marks the object dirty correctly,
/// and integrates with Undo. Writes are wrapped in an so an
/// agent's change reverts as one step.
///
/// Field addressing uses Unity's native SerializedProperty path syntax, so array elements are
/// reachable directly: "myArray.Array.data[2]" sets the third element; "myArray.Array.size" sets
/// the array length. Nested fields use dotted paths ("settings.speed").
///
public static class SerializedFieldCommands
{
[CliCommand("set_serialized_field",
"Set a serialized field on a component/asset. Supports primitives, enums, Vector/Color/Rect/Bounds, " +
"object references (value = an ObjectRef: asset by guid/fileId/path or scene object by instanceId/hierarchyPath), " +
"and array elements via 'name.Array.data[i]' (or 'name.Array.size' to resize).")]
public static AuthoringResult SetSerializedField(
[CliArg("target", "Reference to the component or asset to modify (globalId/path/guid/instanceId/hierarchyPath). May be a GameObject when 'component' is given.", Required = true)] ObjectRef target,
[CliArg("field", "SerializedProperty path, e.g. 'speed', 'settings.speed', or 'waypoints.Array.data[0]'.", Required = true)] string field,
[CliArg("value", "JSON value to assign. For object references pass an ObjectRef object (or null to clear). For enums pass the value name.", Required = true)] JToken value,
[CliArg("component", "Component type name on the target GameObject (e.g. 'Rigidbody'). Use when 'target' is a GameObject; omit when 'target' is already a component handle.")] string component = null)
{
if (target == null || target.IsEmpty)
throw new ArgumentException("set_serialized_field 'target' is required.");
if (string.IsNullOrWhiteSpace(field))
throw new ArgumentException("set_serialized_field 'field' is required.");
var obj = ResolveSerializable(target, component);
using (new AuthoringUndoScope($"Set {field}"))
{
// RegisterCompleteObjectUndo captures the pre-change state for revert; the
// SerializedObject below then records the change for prefab/asset dirtying.
Undo.RegisterCompleteObjectUndo(obj, $"Set {field}");
var so = new SerializedObject(obj);
var prop = so.FindProperty(field);
if (prop == null)
throw new ArgumentException(
$"Field '{field}' was not found on '{obj.GetType().Name}'. " +
"Use a SerializedProperty path (e.g. 'speed', 'settings.speed', 'items.Array.data[0]').");
SerializedPropertyConverter.SetValue(prop, value);
so.ApplyModifiedProperties();
EditorUtility.SetDirty(obj);
}
var result = ObjectResolver.Describe(obj) ?? new AuthoringResult { Type = obj.GetType().Name };
return result;
}
[CliCommand("get_serialized_fields",
"Read serialized fields of a component/asset. Returns each top-level field's name, type and value " +
"(object references are returned as re-usable handles). Pass 'field' to read a single SerializedProperty path.")]
public static object GetSerializedFields(
[CliArg("target", "Reference to the component or asset to read (globalId/path/guid/instanceId/hierarchyPath). May be a GameObject when 'component' is given.", Required = true)] ObjectRef target,
[CliArg("field", "Optional single SerializedProperty path to read (e.g. 'speed' or 'items.Array.data[0]'). Omit to read all top-level fields.")] string field = null,
[CliArg("component", "Component type name on the target GameObject (e.g. 'Rigidbody'). Use when 'target' is a GameObject; omit when 'target' is already a component handle.")] string component = null)
{
if (target == null || target.IsEmpty)
throw new ArgumentException("get_serialized_fields 'target' is required.");
var obj = ResolveSerializable(target, component);
var so = new SerializedObject(obj);
if (!string.IsNullOrWhiteSpace(field))
{
var prop = so.FindProperty(field);
if (prop == null)
throw new ArgumentException($"Field '{field}' was not found on '{obj.GetType().Name}'.");
return new
{
type = obj.GetType().Name,
fields = new[] { DescribeProperty(prop) }
};
}
var fields = new List