using System;
using System.Linq;
using UnityEngine;
using UnityEditor;
using LLM.Editor.Helper;
using Newtonsoft.Json.Linq;
using JetBrains.Annotations;

namespace LLM.Editor.Commands
{
    [Serializable]
    public class SetComponentValueParams
    {
        public string subjectIdentifier;
        public string componentName;
        public string memberName;
        public JToken value; // Changed to JToken to handle both simple values and objects
    }

    /// <summary>
    /// The final action command in an analysis workflow. It takes a calculated value
    /// from the LLM and applies it to a specific property on a component.
    /// Now supports setting simple values AND object references.
    /// </summary>
    [UsedImplicitly]
    public class SetComponentValueCommand : ICommand
    {
        [Serializable]
        private class ObjectReferenceValue
        {
            public string identifier;
            public string childPath; // Optional
        }
        
        private readonly SetComponentValueParams _params;

        public SetComponentValueCommand(string jsonParams)
        {
            _params = jsonParams?.FromJson<SetComponentValueParams>();
        }

        public CommandOutcome Execute(Data.CommandContext context)
        {
            if (_params == null || string.IsNullOrEmpty(_params.subjectIdentifier) || string.IsNullOrEmpty(_params.componentName) || string.IsNullOrEmpty(_params.memberName))
            {
                Debug.LogError("[SetComponentValueCommand] Invalid parameters. Subject, component, and member names are required.");
                return CommandOutcome.Error;
            }

            var targetObject = ResolveIdentifier(_params.subjectIdentifier);
            if (!targetObject)
            {
                Debug.LogError($"[SetComponentValueCommand] Could not find subject GameObject '{_params.subjectIdentifier}'.");
                return CommandOutcome.Error;
            }

            var targetComponent = targetObject.GetComponent(_params.componentName);
            if (!targetComponent)
            {
                Debug.LogError($"[SetComponentValueCommand] Could not find component '{_params.componentName}' on subject '{_params.subjectIdentifier}'.");
                return CommandOutcome.Error;
            }

            var serializedObject = new SerializedObject(targetComponent);
            var serializedProperty = serializedObject.FindProperty(_params.memberName);

            if (serializedProperty == null)
            {
                Debug.LogError($"[SetComponentValueCommand] Could not find serialized property or field '{_params.memberName}' on component '{_params.componentName}'. Make sure it is public or has a [SerializeField] attribute.");
                return CommandOutcome.Error;
            }
            
            serializedObject.Update();

            // Set value based on the property type
            switch (serializedProperty.propertyType)
            {
                case SerializedPropertyType.ObjectReference:
                    if (!SetObjectReferenceValue(serializedProperty, targetObject))
                    {
                        return CommandOutcome.Error;
                    }
                    break;
                case SerializedPropertyType.Float:
                    serializedProperty.floatValue = _params.value.Value<float>();
                    break;
                case SerializedPropertyType.Integer:
                    serializedProperty.intValue = _params.value.Value<int>();
                    break;
                case SerializedPropertyType.Boolean:
                    serializedProperty.boolValue = _params.value.Value<bool>();
                    break;
                case SerializedPropertyType.String:
                    serializedProperty.stringValue = _params.value.Value<string>();
                    break;
                default:
                    Debug.LogError($"[SetComponentValueCommand] The property type '{serializedProperty.propertyType}' is not currently supported for setting values.");
                    return CommandOutcome.Error;
            }
            
            serializedObject.ApplyModifiedProperties();

            Debug.Log($"[SetComponentValueCommand] Successfully set '{_params.memberName}' on '{_params.componentName}'.");
            return CommandOutcome.Success;
        }

        private bool SetObjectReferenceValue(SerializedProperty property, GameObject contextObject)
        {
            var referenceData = _params.value.ToObject<ObjectReferenceValue>();
            if (referenceData == null)
            {
                // Handle case where value is null to clear the reference
                if (_params.value.Type == JTokenType.Null)
                {
                    property.objectReferenceValue = null;
                    return true;
                }
                Debug.LogError("[SetComponentValueCommand] The 'value' for an ObjectReference must be an object with an 'identifier' or be null.");
                return false;
            }

            // The object to assign can be identified relative to the object being modified,
            // or it can be a completely different object identified by its own GUID/InstanceID.
            var baseObject = ResolveIdentifier(referenceData.identifier) ?? contextObject;
            if (!baseObject)
            {
                Debug.LogError($"[SetComponentValueCommand] Could not resolve the base identifier '{referenceData.identifier}' for the object reference.");
                return false;
            }

            if (!baseObject && !string.IsNullOrEmpty(referenceData.childPath))
            {
                 Debug.LogError($"[SetComponentValueCommand] Cannot use 'childPath' because the base object '{baseObject.name}' is not a GameObject.");
                 return false;
            }

            UnityEngine.Object objectToAssign = baseObject;
            if (baseObject && !string.IsNullOrEmpty(referenceData.childPath))
            {
                var childTransform = baseObject.transform.Find(referenceData.childPath);
                if (!childTransform)
                {
                    Debug.LogError($"[SetComponentValueCommand] Could not find child '{referenceData.childPath}' on object '{baseObject.name}'.");
                    return false;
                }
                objectToAssign = childTransform.gameObject; // Default to assigning the GameObject
                
                // If the property is looking for a specific component type (like Transform), get that instead.
                var componentType = GetTypeFromProperty(property);
                if (componentType != null)
                {
                    var component = childTransform.GetComponent(componentType);
                    if (component)
                    {
                        objectToAssign = component;
                    }
                }
            }
            
            property.objectReferenceValue = objectToAssign;
            return true;
        }

        private static GameObject ResolveIdentifier(string identifier)
        {
            if (string.IsNullOrEmpty(identifier)) return null;
            if (int.TryParse(identifier, out var instanceId))
            {
                return EditorUtility.InstanceIDToObject(instanceId) as GameObject;
            }
            var path = AssetDatabase.GUIDToAssetPath(identifier);
            return !string.IsNullOrEmpty(path) ? AssetDatabase.LoadAssetAtPath<GameObject>(path) : null;
        }
        
        private static Type GetTypeFromProperty(SerializedProperty property)
        {
            // Extracts the type from a property string like "PPtr<Transform>"
            var typeString = property.type;
            var startIndex = typeString.IndexOf('<');
            var endIndex = typeString.IndexOf('>');
            if (startIndex == -1 || endIndex == -1) return null;
            var managedTypeName = typeString.Substring(startIndex + 1, endIndex - startIndex - 1).Trim();
            return GetTypeByName(managedTypeName);
        }

        private static Type GetTypeByName(string typeName)
        {
            return AppDomain.CurrentDomain.GetAssemblies().Select(assembly => assembly.GetType(typeName)).FirstOrDefault(type => type != null);
        }
    }
}
