using System;
using System.Collections.Generic;
using System.Reflection;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.Events;
using MCPForUnity.Runtime.Helpers;

namespace MCPForUnity.Editor.Helpers
{
    /// <summary>
    /// Low-level component operations extracted from ManageGameObject and ManageComponents.
    /// Provides pure C# operations without JSON parsing or response formatting.
    /// </summary>
    public static class ComponentOps
    {
        /// <summary>
        /// Adds a component to a GameObject with Undo support.
        /// </summary>
        /// <param name="target">The target GameObject</param>
        /// <param name="componentType">The type of component to add</param>
        /// <param name="error">Error message if operation fails</param>
        /// <returns>The added component, or null if failed</returns>
        public static Component AddComponent(GameObject target, Type componentType, out string error)
        {
            error = null;

            if (target == null)
            {
                error = "Target GameObject is null.";
                return null;
            }

            if (componentType == null || !typeof(Component).IsAssignableFrom(componentType))
            {
                error = $"Type '{componentType?.Name ?? "null"}' is not a valid Component type.";
                return null;
            }

            // Prevent adding duplicate Transform
            if (componentType == typeof(Transform))
            {
                error = "Cannot add another Transform component.";
                return null;
            }

            // Check for 2D/3D physics conflicts
            string conflictError = CheckPhysicsConflict(target, componentType);
            if (conflictError != null)
            {
                error = conflictError;
                return null;
            }

            // Produce a clearer error when this component already exists and cannot be duplicated.
            Component existingComponent = target.GetComponent(componentType);
            if (existingComponent != null && !AllowsMultiple(target, componentType))
            {
                error = $"Component '{componentType.Name}' already exists on '{target.name}' and this type does not allow multiple instances.";
                return null;
            }

            try
            {
                Component newComponent = Undo.AddComponent(target, componentType);
                if (newComponent == null)
                {
                    if (target.GetComponent(componentType) != null && !AllowsMultiple(target, componentType))
                    {
                        error = $"Component '{componentType.Name}' already exists on '{target.name}' and this type does not allow multiple instances.";
                    }
                    else
                    {
                        error = $"Failed to add component '{componentType.Name}' to '{target.name}'. Unity may restrict this component on the current target.";
                    }
                    return null;
                }

                // Apply default values for specific component types
                ApplyDefaultValues(newComponent);

                return newComponent;
            }
            catch (Exception ex)
            {
                error = $"Error adding component '{componentType.Name}': {ex.Message}";
                return null;
            }
        }

        /// <summary>
        /// Removes a component from a GameObject with Undo support.
        /// </summary>
        /// <param name="target">The target GameObject</param>
        /// <param name="componentType">The type of component to remove</param>
        /// <param name="error">Error message if operation fails</param>
        /// <returns>True if component was removed successfully</returns>
        public static bool RemoveComponent(GameObject target, Type componentType, out string error)
        {
            error = null;

            if (target == null)
            {
                error = "Target GameObject is null.";
                return false;
            }

            if (componentType == null)
            {
                error = "Component type is null.";
                return false;
            }

            // Prevent removing Transform
            if (componentType == typeof(Transform))
            {
                error = "Cannot remove Transform component.";
                return false;
            }

            Component component = target.GetComponent(componentType);
            if (component == null)
            {
                error = $"Component '{componentType.Name}' not found on '{target.name}'.";
                return false;
            }

            try
            {
                Undo.DestroyObjectImmediate(component);
                return true;
            }
            catch (Exception ex)
            {
                error = $"Error removing component '{componentType.Name}': {ex.Message}";
                return false;
            }
        }

        /// <summary>
        /// Sets a property value on a component using reflection.
        /// </summary>
        /// <param name="component">The target component</param>
        /// <param name="propertyName">The property or field name</param>
        /// <param name="value">The value to set (JToken)</param>
        /// <param name="error">Error message if operation fails</param>
        /// <returns>True if property was set successfully</returns>
        public static bool SetProperty(Component component, string propertyName, JToken value, out string error)
        {
            error = null;

            if (component == null)
            {
                error = "Component is null.";
                return false;
            }

            if (string.IsNullOrEmpty(propertyName))
            {
                error = "Property name is null or empty.";
                return false;
            }

            Type type = component.GetType();
            BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
            string normalizedName = ParamCoercion.NormalizePropertyName(propertyName);

            // UnityEventBase-derived types must be set via SerializedProperty, not reflection.
            // Reflection creates a disconnected object that Unity's serialization layer doesn't track,
            // causing m_PersistentCalls to be empty when the scene is saved.
            Type memberType = ResolveMemberType(type, propertyName, normalizedName);
            if (memberType != null && typeof(UnityEventBase).IsAssignableFrom(memberType))
            {
                return SetViaSerializedProperty(component, propertyName, normalizedName, value, out error);
            }

            // Try reflection first (property, field, then non-public serialized field)
            if (TrySetViaReflection(component, type, propertyName, normalizedName, flags, value, out error))
                return true;

            // Reflection failed — fall back to SerializedProperty which handles arrays,
            // custom serialization (e.g. UdonSharp), and types reflection can't convert.
            string reflectionError = error;
            if (SetViaSerializedProperty(component, propertyName, normalizedName, value, out error))
                return true;

            // Both paths failed. If reflection found the member but couldn't convert,
            // report that (more useful than the SerializedProperty error).
            // If reflection didn't find it at all, report the SerializedProperty error.
            if (reflectionError != null && !reflectionError.Contains("not found"))
                error = reflectionError;

            return false;
        }

        private static bool TrySetViaReflection(object component, Type type, string propertyName, string normalizedName, BindingFlags flags, JToken value, out string error)
        {
            error = null;

            // Skip reflection for UnityEngine.Object types with JObject values
            // so SerializedProperty can resolve guid/spriteName/fileID forms.
            bool isJObjectValue = value != null && value.Type == JTokenType.Object;

            // Try property first
            PropertyInfo propInfo = type.GetProperty(propertyName, flags)
                                 ?? type.GetProperty(normalizedName, flags);
            if (propInfo != null && propInfo.CanWrite)
            {
                if (isJObjectValue && typeof(UnityEngine.Object).IsAssignableFrom(propInfo.PropertyType))
                {
                    // Let SerializedProperty path handle complex object references.
                    return false;
                }

                try
                {
                    object convertedValue = PropertyConversion.ConvertToType(value, propInfo.PropertyType);
                    if (convertedValue == null && value.Type != JTokenType.Null)
                    {
                        error = $"Failed to convert value for property '{propertyName}' to type '{propInfo.PropertyType.Name}'.";
                        return false;
                    }
                    propInfo.SetValue(component, convertedValue);
                    return true;
                }
                catch (Exception ex)
                {
                    error = $"Failed to set property '{propertyName}': {ex.Message}";
                    return false;
                }
            }

            // Try field
            FieldInfo fieldInfo = type.GetField(propertyName, flags)
                               ?? type.GetField(normalizedName, flags);
            if (fieldInfo != null && !fieldInfo.IsInitOnly)
            {
                if (isJObjectValue && typeof(UnityEngine.Object).IsAssignableFrom(fieldInfo.FieldType))
                {
                    // Let SerializedProperty path handle complex object references.
                    return false;
                }

                try
                {
                    object convertedValue = PropertyConversion.ConvertToType(value, fieldInfo.FieldType);
                    if (convertedValue == null && value.Type != JTokenType.Null)
                    {
                        error = $"Failed to convert value for field '{propertyName}' to type '{fieldInfo.FieldType.Name}'.";
                        return false;
                    }
                    fieldInfo.SetValue(component, convertedValue);
                    return true;
                }
                catch (Exception ex)
                {
                    error = $"Failed to set field '{propertyName}': {ex.Message}";
                    return false;
                }
            }

            // Try non-public serialized fields — traverse inheritance hierarchy
            fieldInfo = FindSerializedFieldInHierarchy(type, propertyName)
                     ?? FindSerializedFieldInHierarchy(type, normalizedName);
            if (fieldInfo != null)
            {
                if (isJObjectValue && typeof(UnityEngine.Object).IsAssignableFrom(fieldInfo.FieldType))
                {
                    // Let SerializedProperty path handle complex object references.
                    return false;
                }

                try
                {
                    object convertedValue = PropertyConversion.ConvertToType(value, fieldInfo.FieldType);
                    if (convertedValue == null && value.Type != JTokenType.Null)
                    {
                        error = $"Failed to convert value for serialized field '{propertyName}' to type '{fieldInfo.FieldType.Name}'.";
                        return false;
                    }
                    fieldInfo.SetValue(component, convertedValue);
                    return true;
                }
                catch (Exception ex)
                {
                    error = $"Failed to set serialized field '{propertyName}': {ex.Message}";
                    return false;
                }
            }

            error = $"Property or field '{propertyName}' not found on component '{type.Name}'.";
            return false;
        }

        /// <summary>
        /// Gets all public properties and fields from a component type.
        /// </summary>
        public static List<string> GetAccessibleMembers(Type componentType)
        {
            var members = new List<string>();
            if (componentType == null) return members;

            BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;

            foreach (var prop in componentType.GetProperties(flags))
            {
                if (prop.CanWrite && prop.GetSetMethod() != null)
                {
                    members.Add(prop.Name);
                }
            }

            foreach (var field in componentType.GetFields(flags))
            {
                if (!field.IsInitOnly)
                {
                    members.Add(field.Name);
                }
            }

            // Include private [SerializeField] fields - traverse inheritance hierarchy
            // Type.GetFields with NonPublic only returns fields declared directly on that type,
            // so we need to walk up the chain to find inherited private serialized fields
            var seenFieldNames = new HashSet<string>(members); // Avoid duplicates with public fields
            Type currentType = componentType;
            while (currentType != null && currentType != typeof(object))
            {
                foreach (var field in currentType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly))
                {
                    if (field.GetCustomAttribute<SerializeField>() != null && !seenFieldNames.Contains(field.Name))
                    {
                        members.Add(field.Name);
                        seenFieldNames.Add(field.Name);
                    }
                }
                currentType = currentType.BaseType;
            }

            members.Sort();
            return members;
        }

        // --- Private Helpers ---

        /// <summary>
        /// Searches for a non-public [SerializeField] field through the entire inheritance hierarchy.
        /// Type.GetField() with NonPublic only returns fields declared directly on that type,
        /// so this method walks up the chain to find inherited private serialized fields.
        /// </summary>
        internal static FieldInfo FindSerializedFieldInHierarchy(Type type, string fieldName)
        {
            if (type == null || string.IsNullOrEmpty(fieldName))
                return null;

            BindingFlags privateFlags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
            Type currentType = type;

            // Walk up the inheritance chain
            while (currentType != null && currentType != typeof(object))
            {
                // Search for the field on this specific type (case-insensitive)
                foreach (var field in currentType.GetFields(privateFlags))
                {
                    if (string.Equals(field.Name, fieldName, StringComparison.OrdinalIgnoreCase) &&
                        field.GetCustomAttribute<SerializeField>() != null)
                    {
                        return field;
                    }
                }
                currentType = currentType.BaseType;
            }

            return null;
        }

        private static string CheckPhysicsConflict(GameObject target, Type componentType)
        {
            bool isAdding2DPhysics =
                typeof(Rigidbody2D).IsAssignableFrom(componentType) ||
                typeof(Collider2D).IsAssignableFrom(componentType);

            bool isAdding3DPhysics =
                typeof(Rigidbody).IsAssignableFrom(componentType) ||
                typeof(Collider).IsAssignableFrom(componentType);

            if (isAdding2DPhysics)
            {
                if (target.GetComponent<Rigidbody>() != null || target.GetComponent<Collider>() != null)
                {
                    return $"Cannot add 2D physics component '{componentType.Name}' because the GameObject '{target.name}' already has a 3D Rigidbody or Collider.";
                }
            }
            else if (isAdding3DPhysics)
            {
                if (target.GetComponent<Rigidbody2D>() != null || target.GetComponent<Collider2D>() != null)
                {
                    return $"Cannot add 3D physics component '{componentType.Name}' because the GameObject '{target.name}' already has a 2D Rigidbody or Collider.";
                }
            }

            return null;
        }

        private static void ApplyDefaultValues(Component component)
        {
            // Default newly added Lights to Directional
            if (component is Light light)
            {
                light.type = LightType.Directional;
            }
        }

        private static bool AllowsMultiple(GameObject target, Type componentType)
        {
            if (target == null || componentType == null)
            {
                return false;
            }

            if (Attribute.IsDefined(componentType, typeof(DisallowMultipleComponent), inherit: true))
            {
                return false;
            }

            return true;
        }

        // --- UnityEvent SerializedProperty support ---

        private static Type ResolveMemberType(Type componentType, string propertyName, string normalizedName)
        {
            BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;

            PropertyInfo propInfo = componentType.GetProperty(propertyName, flags)
                                 ?? componentType.GetProperty(normalizedName, flags);
            if (propInfo != null)
                return propInfo.PropertyType;

            FieldInfo fieldInfo = componentType.GetField(propertyName, flags)
                               ?? componentType.GetField(normalizedName, flags);
            if (fieldInfo != null)
                return fieldInfo.FieldType;

            fieldInfo = FindSerializedFieldInHierarchy(componentType, propertyName)
                     ?? FindSerializedFieldInHierarchy(componentType, normalizedName);
            if (fieldInfo != null)
                return fieldInfo.FieldType;

            return null;
        }

        private static bool SetViaSerializedProperty(Component component, string propertyName, string normalizedName, JToken value, out string error)
        {
            error = null;
            using var so = new SerializedObject(component);

            SerializedProperty prop = so.FindProperty(propertyName)
                                   ?? so.FindProperty(normalizedName);
            if (prop == null)
            {
                error = $"SerializedProperty '{propertyName}' not found on component '{component.GetType().Name}'.";
                return false;
            }

            if (!SetSerializedPropertyRecursive(prop, value, out error, 0))
                return false;

            so.ApplyModifiedProperties();

            // Readback verification for ObjectReference — these can silently fail
            if (prop.propertyType == SerializedPropertyType.ObjectReference
                && value != null
                && !(value is JValue jv && jv.Type == JTokenType.Null))
            {
                so.Update();
                var verifyProp = so.FindProperty(propertyName)
                              ?? so.FindProperty(normalizedName);
                if (verifyProp != null
                    && verifyProp.propertyType == SerializedPropertyType.ObjectReference
                    && verifyProp.objectReferenceValue == null)
                {
                    error = $"Property '{propertyName}' was set but the object reference did not persist. " +
                            "Check that the referenced object exists and is the correct type.";
                    return false;
                }
            }

            return true;
        }

        private static bool SetSerializedPropertyRecursive(SerializedProperty prop, JToken value, out string error, int depth)
        {
            error = null;
            const int MaxDepth = 20;
            if (depth > MaxDepth)
            {
                error = $"Maximum recursion depth ({MaxDepth}) exceeded.";
                return false;
            }

            try
            {
                // Array + JArray
                if (prop.isArray && prop.propertyType != SerializedPropertyType.String && value is JArray jArray)
                {
                    prop.arraySize = jArray.Count;
                    prop.serializedObject.ApplyModifiedProperties();
                    prop.serializedObject.Update();

                    for (int i = 0; i < jArray.Count; i++)
                    {
                        var element = prop.GetArrayElementAtIndex(i);
                        if (!SetSerializedPropertyRecursive(element, jArray[i], out error, depth + 1))
                            return false;
                    }
                    return true;
                }

                // Generic (struct/class) + JObject
                if (prop.propertyType == SerializedPropertyType.Generic && !prop.isArray && value is JObject jObj)
                {
                    foreach (var kvp in jObj)
                    {
                        var child = FindPropertyRelativeFuzzy(prop, kvp.Key);
                        if (child == null)
                        {
                            error = $"Sub-property '{kvp.Key}' not found under '{prop.propertyPath}'.";
                            return false;
                        }
                        if (!SetSerializedPropertyRecursive(child, kvp.Value, out error, depth + 1))
                            return false;
                    }
                    return true;
                }

                // ObjectReference
                if (prop.propertyType == SerializedPropertyType.ObjectReference)
                    return SetObjectReference(prop, value, out error);

                // Leaf types
                switch (prop.propertyType)
                {
                    case SerializedPropertyType.Integer:
                        if (value == null || value.Type == JTokenType.Null
                            || (value.Type != JTokenType.Integer && value.Type != JTokenType.Float
                                && !long.TryParse(value.ToString(), out _)))
                        {
                            error = "Expected integer value.";
                            return false;
                        }
                        if (prop.type == "long")
                            prop.longValue = ParamCoercion.CoerceLong(value, 0);
                        else
                            prop.intValue = ParamCoercion.CoerceInt(value, 0);
                        return true;

                    case SerializedPropertyType.Boolean:
                        if (value == null || value.Type == JTokenType.Null)
                        {
                            error = "Expected boolean value.";
                            return false;
                        }
                        prop.boolValue = ParamCoercion.CoerceBool(value, false);
                        return true;

                    case SerializedPropertyType.Float:
                        float floatVal = ParamCoercion.CoerceFloat(value, float.NaN);
                        if (float.IsNaN(floatVal))
                        {
                            error = "Expected float value.";
                            return false;
                        }
                        prop.floatValue = floatVal;
                        return true;

                    case SerializedPropertyType.String:
                        prop.stringValue = value == null || value.Type == JTokenType.Null ? string.Empty : value.ToString();
                        return true;

                    case SerializedPropertyType.Enum:
                        return SetEnum(prop, value, out error);

                    default:
                        error = $"Unsupported SerializedPropertyType: {prop.propertyType} at '{prop.propertyPath}'.";
                        return false;
                }
            }
            catch (Exception ex)
            {
                error = $"Error setting '{prop.propertyPath}': {ex.Message}";
                return false;
            }
        }

        internal static bool SetObjectReference(SerializedProperty prop, JToken value, out string error)
        {
            error = null;

            if (value == null || value.Type == JTokenType.Null)
            {
                prop.objectReferenceValue = null;
                return true;
            }

            if (value.Type == JTokenType.Integer)
            {
                int id = value.Value<int>();
                var resolved = GameObjectLookup.ResolveInstanceID(id);
                if (resolved == null)
                {
                    error = $"No object found with instanceID {id}.";
                    return false;
                }
                return AssignObjectReference(prop, resolved, null, out error);
            }

            if (value is JObject jObj)
            {
                // Optional component type filter — e.g. {"instanceID": 123, "component": "Button"}
                string componentFilter = jObj["component"]?.ToString();

                var idToken = jObj["instanceID"];
                if (idToken != null)
                {
                    int id = ParamCoercion.CoerceInt(idToken, 0);
                    var resolved = GameObjectLookup.ResolveInstanceID(id);
                    if (resolved == null)
                    {
                        error = $"No object found with instanceID {id}.";
                        return false;
                    }
                    return AssignObjectReference(prop, resolved, componentFilter, out error);
                }

                var guidToken = jObj["guid"];
                if (guidToken != null)
                {
                    string path = AssetDatabase.GUIDToAssetPath(guidToken.ToString());
                    if (string.IsNullOrEmpty(path))
                    {
                        error = $"No asset found for GUID '{guidToken}'.";
                        return false;
                    }

                    var spriteNameToken = jObj["spriteName"];
                    if (spriteNameToken != null)
                    {
                        string spriteName = spriteNameToken.ToString();
                        var allAssets = AssetDatabase.LoadAllAssetsAtPath(path);
                        var originalRef = prop.objectReferenceValue;
                        foreach (var asset in allAssets)
                        {
                            if (asset is Sprite sprite && sprite.name == spriteName)
                            {
                                prop.objectReferenceValue = sprite;
                                if (prop.objectReferenceValue != null)
                                    return true;
                                // Unity rejected the type — restore and report
                                prop.objectReferenceValue = originalRef;
                                error = $"Sprite '{spriteName}' found but is not compatible with the property type.";
                                return false;
                            }
                        }

                        error = $"Sprite '{spriteName}' not found in atlas '{path}'.";
                        return false;
                    }

                    var fileIdToken = jObj["fileID"];
                    if (fileIdToken != null)
                    {
                        long targetFileId = fileIdToken.Value<long>();
                        if (targetFileId != 0)
                        {
                            var allAssets = AssetDatabase.LoadAllAssetsAtPath(path);
                            var originalRef = prop.objectReferenceValue;
                            foreach (var asset in allAssets)
                            {
                                if (asset is Sprite sprite)
                                {
                                    long spriteFileId = GetSpriteFileId(sprite);
                                    if (spriteFileId == targetFileId)
                                    {
                                        prop.objectReferenceValue = sprite;
                                        if (prop.objectReferenceValue != null)
                                            return true;
                                        prop.objectReferenceValue = originalRef;
                                        error = $"Sprite with fileID '{targetFileId}' found but is not compatible with the property type.";
                                        return false;
                                    }
                                }
                            }
                        }

                        error = $"Sprite with fileID '{targetFileId}' not found in atlas '{path}'.";
                        return false;
                    }

                    var loaded = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path);
                    return AssignObjectReference(prop, loaded, componentFilter, out error);
                }

                var pathToken = jObj["path"];
                if (pathToken != null)
                {
                    string sanitized = AssetPathUtility.SanitizeAssetPath(pathToken.ToString());
                    var resolved = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(sanitized);
                    if (resolved == null)
                    {
                        error = $"No asset found at path '{pathToken}'.";
                        return false;
                    }
                    return AssignObjectReference(prop, resolved, componentFilter, out error);
                }

                var nameToken = jObj["name"];
                if (nameToken != null)
                {
                    return ResolveSceneObjectByName(prop, nameToken.ToString(), componentFilter, out error);
                }

                error = "Object reference must contain 'instanceID', 'guid', 'path', or 'name'.";
                return false;
            }

            if (value.Type == JTokenType.String)
            {
                string strVal = value.ToString();

                // Try as instanceID if the string is purely numeric
                if (int.TryParse(strVal, out int parsedId))
                {
                    var resolved = GameObjectLookup.ResolveInstanceID(parsedId);
                    if (resolved != null)
                        return AssignObjectReference(prop, resolved, null, out error);
                    // Not a valid instanceID — fall through to path/name resolution
                }

                if (strVal.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase) || strVal.Contains("/"))
                {
                    string sanitized = AssetPathUtility.SanitizeAssetPath(strVal);
                    var resolved = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(sanitized);
                    if (resolved == null)
                    {
                        error = $"No asset found at path '{strVal}'.";
                        return false;
                    }
                    return AssignObjectReference(prop, resolved, null, out error);
                }

                // Try as asset GUID (32-char hex string)
                if (strVal.Length == 32 && IsHexString(strVal))
                {
                    string assetPath = AssetDatabase.GUIDToAssetPath(strVal);
                    if (!string.IsNullOrEmpty(assetPath))
                    {
                        var resolved = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetPath);
                        if (resolved != null)
                            return AssignObjectReference(prop, resolved, null, out error);
                    }
                }

                // Fall back to scene hierarchy lookup by name.
                return ResolveSceneObjectByName(prop, strVal, null, out error);
            }

            error = $"Unsupported object reference format: {value.Type}.";
            return false;
        }

        /// <summary>
        /// Assigns a resolved object to a SerializedProperty, with automatic component fallback.
        /// If the resolved object is a GameObject but the property expects a Component type,
        /// searches the GameObject's components for a compatible one.
        /// Optionally filters by component type name (e.g. "Button", "Rigidbody").
        /// </summary>
        private static bool AssignObjectReference(SerializedProperty prop, UnityEngine.Object resolved, string componentFilter, out string error)
        {
            error = null;
            if (resolved == null)
            {
                error = "Resolved object is null.";
                return false;
            }

            // If a component filter is specified and the resolved object is a GameObject,
            // find the specific component by type name.
            if (!string.IsNullOrEmpty(componentFilter) && resolved is GameObject filterGo)
            {
                var components = filterGo.GetComponents<Component>();
                foreach (var comp in components)
                {
                    if (comp == null) continue;
                    if (string.Equals(comp.GetType().Name, componentFilter, StringComparison.OrdinalIgnoreCase) ||
                        string.Equals(comp.GetType().FullName, componentFilter, StringComparison.OrdinalIgnoreCase))
                    {
                        prop.objectReferenceValue = comp;
                        if (prop.objectReferenceValue != null)
                            return true;
                    }
                }
                error = $"Component '{componentFilter}' not found on GameObject '{filterGo.name}'.";
                return false;
            }

            // Try direct assignment first
            prop.objectReferenceValue = resolved;
            if (prop.objectReferenceValue != null)
                return true;

            // Sub-asset fallback: e.g., Texture2D → Sprite
            string subAssetPath = AssetDatabase.GetAssetPath(resolved);
            if (!string.IsNullOrEmpty(subAssetPath))
            {
                var subAssets = AssetDatabase.LoadAllAssetsAtPath(subAssetPath);
                UnityEngine.Object match = null;
                int matchCount = 0;
                foreach (var sub in subAssets)
                {
                    if (sub == null || sub == resolved) continue;
                    prop.objectReferenceValue = sub;
                    if (prop.objectReferenceValue != null)
                    {
                        match = sub;
                        matchCount++;
                        if (matchCount > 1) break;
                    }
                }

                if (matchCount == 1)
                {
                    prop.objectReferenceValue = match;
                    return true;
                }

                // Clean up: probing may have left the property dirty
                prop.objectReferenceValue = null;

                if (matchCount > 1)
                {
                    error = $"Multiple compatible sub-assets found in '{subAssetPath}'. " +
                            "Use {\"guid\": \"...\", \"spriteName\": \"<name>\"} or " +
                            "{\"guid\": \"...\", \"fileID\": <id>} for precise selection.";
                    return false;
                }
            }

            // If the resolved object is a GameObject but the property expects a Component,
            // try each component on the GameObject until one is accepted.
            if (resolved is GameObject go)
            {
                var components = go.GetComponents<Component>();
                foreach (var comp in components)
                {
                    if (comp == null) continue;
                    prop.objectReferenceValue = comp;
                    if (prop.objectReferenceValue != null)
                        return true;
                }
                error = $"GameObject '{go.name}' found but no compatible component for the property type.";
                return false;
            }

            error = $"Object '{resolved.name}' (type: {resolved.GetType().Name}) is not compatible with the property type.";
            return false;
        }

        /// <summary>
        /// Resolves a scene GameObject by name and assigns it (or a component on it)
        /// to a SerializedProperty. Uses GameObjectLookup for robust search
        /// including inactive objects and prefab stage support.
        /// </summary>
        private static bool ResolveSceneObjectByName(SerializedProperty prop, string name, string componentFilter, out string error)
        {
            error = null;
            if (string.IsNullOrWhiteSpace(name))
            {
                error = "Cannot resolve object reference from empty name.";
                return false;
            }

            var ids = GameObjectLookup.SearchGameObjects(
                GameObjectLookup.SearchMethod.ByName, name, includeInactive: true, maxResults: 1);

            if (ids.Count == 0)
            {
                error = $"No GameObject named '{name}' found in scene.";
                return false;
            }

            var go = GameObjectLookup.FindById(ids[0]);
            if (go == null)
            {
                error = $"GameObject '{name}' found but could not be resolved.";
                return false;
            }

            return AssignObjectReference(prop, go, componentFilter, out error);
        }

        /// <summary>
        /// Finds a child SerializedProperty by name, falling back to underscore-insensitive matching.
        /// The batch_execute transport can strip underscores from JSON keys
        /// (e.g. m_PersistentCalls → mPersistentCalls), so we iterate immediate children
        /// and compare with underscores removed.
        /// </summary>
        private static SerializedProperty FindPropertyRelativeFuzzy(SerializedProperty parent, string key)
        {
            var child = parent.FindPropertyRelative(key);
            if (child != null) return child;

            string normalizedKey = key.Replace("_", "").ToLowerInvariant();

            var end = parent.GetEndProperty();
            var iter = parent.Copy();
            if (!iter.Next(true)) return null;

            while (!SerializedProperty.EqualContents(iter, end))
            {
                if (iter.depth == parent.depth + 1)
                {
                    string normalizedName = iter.name.Replace("_", "").ToLowerInvariant();
                    if (normalizedName == normalizedKey)
                        return parent.FindPropertyRelative(iter.name);
                }
                if (!iter.Next(false))
                    break;
            }

            return null;
        }

        private static bool SetEnum(SerializedProperty prop, JToken value, out string error)
        {
            error = null;
            var names = prop.enumNames;
            if (names == null || names.Length == 0)
            {
                error = "Enum has no names.";
                return false;
            }

            if (value.Type == JTokenType.Integer)
            {
                int idx = value.Value<int>();
                if (idx < 0 || idx >= names.Length)
                {
                    error = $"Enum index out of range: {idx}.";
                    return false;
                }
                prop.enumValueIndex = idx;
                return true;
            }

            string s = value.ToString();
            for (int i = 0; i < names.Length; i++)
            {
                if (string.Equals(names[i], s, StringComparison.OrdinalIgnoreCase))
                {
                    prop.enumValueIndex = i;
                    return true;
                }
            }
            error = $"Unknown enum name '{s}'.";
            return false;
        }

        private static long GetSpriteFileId(Sprite sprite)
        {
            if (sprite == null)
                return 0;

            try
            {
                var globalId = GlobalObjectId.GetGlobalObjectIdSlow(sprite);
                return unchecked((long)globalId.targetObjectId);
            }
            catch (Exception ex)
            {
                McpLog.Warn($"Failed to get fileID for sprite '{sprite.name}' (instanceID={sprite.GetInstanceIDCompat()}): {ex.Message}");
                return 0;
            }
        }

        private static bool IsHexString(string str)
        {
            foreach (char c in str)
            {
                if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')))
                    return false;
            }
            return true;
        }
    }
}

