using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using AIRuntime.Runtime.Capabilities;
using AIRuntime.Runtime.Capabilities.Attributes;
using AIRuntime.Runtime.Capabilities.Permissions;

using Newtonsoft.Json.Linq;

using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;

using Object =
    UnityEngine.Object;

using CapabilityRange =
    AIRuntime.Runtime.Capabilities.Attributes.RangeAttribute;


namespace AIRuntime.Editor
{
    [Serializable]
    public sealed class GetSerializedPropertiesArguments :
        EditorSerializedTargetArguments
    {
        [DefaultValue(false)]
        [Description("Include hidden serialized fields as well as visible Inspector fields.")]
        public bool IncludeHidden { get; set; }

        [DefaultValue(256)]
        [CapabilityRange(1, 2048)]
        [Description("Maximum number of properties to return.")]
        public int MaxProperties { get; set; } =
            256;
    }


    [Serializable]
    internal sealed class GetSerializedPropertiesResult
    {
        public int ObjectInstanceId;

        public string GlobalObjectId;

        public string ObjectName;

        public string ObjectType;

        public string AssetPath;

        public string ScenePath;

        public int PropertyCount;

        public bool Truncated;

        public List<EditorSerializedPropertyRecord> Properties =
            new List<EditorSerializedPropertyRecord>();

        public string ResponseInstruction =
            "Property Path is the authoritative serialized path for set_serialized_property. Copy the returned Value as expectedValueJson before modifying a property. Truncated=true means the result hit maxProperties.";
    }


    public sealed class GetSerializedPropertiesCapability :
        CapabilityBase<GetSerializedPropertiesArguments>,
        ICapabilityObservationSemantics
    {
        public override string Name =>
            "get_serialized_properties";


        public override string Description =>
            "Reads visible or hidden serialized Unity properties from a selected object, component, GameObject, or asset. Returns authoritative property paths and JSON values for safe follow-up edits.";


        public override CapabilityPermission Permission =>
            CapabilityPermission.ReadOnly;


        public CapabilityObservationBehavior ObservationBehavior =>
            CapabilityObservationBehavior.StableUntilMutation;


        protected override Task<object> ExecuteAsync(
            GetSerializedPropertiesArguments arguments,
            CapabilityContext context)
        {
            context.CancellationToken
                .ThrowIfCancellationRequested();

            GetSerializedPropertiesArguments request =
                arguments ??
                new GetSerializedPropertiesArguments();

            Object target =
                EditorCapabilityUtility
                    .ResolveSerializedTarget(
                        request);

            bool truncated;

            List<EditorSerializedPropertyRecord> properties =
                EditorCapabilityUtility
                    .ReadSerializedProperties(
                        target,
                        request.IncludeHidden,
                        request.MaxProperties,
                        out truncated);

            Component component =
                target as Component;

            GameObject gameObject =
                target as GameObject;

            if (gameObject ==
                    null &&
                component !=
                    null)
            {
                gameObject =
                    component.gameObject;
            }

            GetSerializedPropertiesResult result =
                new GetSerializedPropertiesResult
                {
                    ObjectInstanceId =
                        target.GetInstanceID(),

                    GlobalObjectId =
                        EditorCapabilityUtility
                            .GetGlobalObjectId(
                                target),

                    ObjectName =
                        target.name,

                    ObjectType =
                        target.GetType().FullName,

                    AssetPath =
                        EditorCapabilityUtility
                            .NormalizePath(
                                AssetDatabase.GetAssetPath(
                                    target)),

                    ScenePath =
                        EditorCapabilityUtility
                            .GetScenePath(
                                gameObject),

                    PropertyCount =
                        properties.Count,

                    Truncated =
                        truncated,

                    Properties =
                        properties
                };

            return Task.FromResult<object>(
                result);
        }
    }


    [Serializable]
    public sealed class SerializedObjectReferenceInput
    {
        [Description("Exact live Unity instance ID returned by an Editor inspection capability. Prefer AssetPath for persistent assets.")]
        public int InstanceId { get; set; }

        [Description("Complete persistent GlobalObjectId returned by an Editor inspection capability.")]
        public string GlobalObjectId { get; set; }

        [Description("Exact persistent Unity asset path returned by an asset creation or inspection capability.")]
        public string AssetPath { get; set; }
    }


    [Serializable]
    public sealed class SetSerializedPropertyArguments :
        EditorSerializedTargetArguments
    {
        [Required]
        [Description("Exact serialized property path returned by get_serialized_properties, for example m_IsTrigger.")]
        public string PropertyPath { get; set; }

        [Description("New non-object value encoded as JSON. Plain text is also accepted for string properties. Examples: true, 3.5, \"Player\", {\"x\":0,\"y\":1,\"z\":0}, null. For a non-null Unity ObjectReference use ObjectReference instead of nesting escaped JSON here. Unity Enum properties accept a semantic name such as \"ScaleWithScreenSize\", the complete {\"index\":1,\"name\":\"ScaleWithScreenSize\"} value returned by get_serialized_properties, or {\"index\":1}. Integer properties backed by a [Flags] enum require semantic names, for example {\"flags\":[\"FreezePositionZ\"]}; numeric bitmasks are rejected.")]
        public string ValueJson { get; set; }

        [Description("Structured non-null Unity object reference. Copy AssetPath, or the complete InstanceId and GlobalObjectId, from an authoritative tool result. Do not also pass ValueJson.")]
        public SerializedObjectReferenceInput ObjectReference { get; set; }

        [DefaultValue(false)]
        [Description("When true, compare expectedValueJson with the current value and reject a stale edit.")]
        public bool UseExpectedValue { get; set; }

        [Description("Expected current value copied from get_serialized_properties. Used only when useExpectedValue=true.")]
        public string ExpectedValueJson { get; set; }

        [DefaultValue(false)]
        [Description("Immediately save persistent asset changes to disk. Scene changes remain dirty and are not automatically saved.")]
        public bool SaveAssets { get; set; }
    }


    [Serializable]
    internal sealed class SetSerializedPropertyResult
    {
        public int ObjectInstanceId;

        public string GlobalObjectId;

        public string ObjectName;

        public string ObjectType;

        public string PropertyPath;

        public string PropertyType;

        public JToken PreviousValue;

        public JToken RequestedValue;

        public JToken CurrentValue;

        public bool Changed;

        public bool Verified;

        public bool UndoRecorded;

        public bool IsPrefabInstance;

        public bool SceneDirty;

        public bool AssetSaved;

        public string ResponseInstruction =
            "Changed=true and Verified=true confirm the property now matches the requested value. For future non-null Unity object assignments, pass the returned reference through the structured ObjectReference argument rather than encoding JSON inside ValueJson. Scene changes are Undo-backed but are not automatically saved. Never claim success when Verified=false.";
    }


    public sealed class SetSerializedPropertyCapability :
        CapabilityBase<SetSerializedPropertyArguments>
    {
        public override string Name =>
            "set_serialized_property";


        public override string Description =>
            "Safely changes one serialized Unity Inspector property using its exact property path or one unique top-level Inspector-name alias. Supports optimistic expected-value checks, Unity Undo, prefab override recording, and post-change verification.";


        public override CapabilityPermission Permission =>
            CapabilityPermission.Write;


        protected override Task<object> ExecuteAsync(
            SetSerializedPropertyArguments arguments,
            CapabilityContext context)
        {
            context.CancellationToken
                .ThrowIfCancellationRequested();

            if (arguments ==
                null)
            {
                throw new ArgumentNullException(
                    nameof(arguments));
            }

            if (arguments.ObjectReference != null &&
                arguments.ValueJson != null)
            {
                throw new ArgumentException(
                    "Pass either ObjectReference for a non-null Unity object assignment or ValueJson for another serialized value, not both.");
            }

            if (arguments.ObjectReference == null &&
                arguments.ValueJson == null)
            {
                throw new ArgumentException(
                    "ValueJson is required unless a structured ObjectReference is provided.");
            }

            Object target =
                EditorCapabilityUtility
                    .ResolveSerializedTarget(
                        arguments);

            SerializedObject serializedObject =
                new SerializedObject(
                    target);

            serializedObject.UpdateIfRequiredOrScript();

            SerializedProperty property =
                ResolveSerializedProperty(
                    serializedObject,
                    arguments.PropertyPath);

            if (property ==
                null)
            {
                throw new InvalidOperationException(
                    $"Serialized property '{arguments.PropertyPath}' was not found on {target.GetType().FullName} '{target.name}'. Call get_serialized_properties again.");
            }

            if (!property.editable ||
                string.Equals(
                    property.propertyPath,
                    "m_Script",
                    StringComparison.Ordinal))
            {
                throw new InvalidOperationException(
                    $"Serialized property '{property.propertyPath}' is read-only.");
            }

            JToken previousValue =
                EditorCapabilityUtility
                    .ReadSerializedValue(
                        property)
                    .DeepClone();

            if (arguments.UseExpectedValue)
            {
                JToken expected =
                    EditorCapabilityUtility
                        .ParseInputValue(
                            arguments.ExpectedValueJson);

                if (!JToken.DeepEquals(
                        previousValue,
                        expected))
                {
                    throw new InvalidOperationException(
                        $"Stale serialized-property edit rejected for '{property.propertyPath}'. Expected {expected}, but the current value is {previousValue}. Re-read the property before retrying.");
                }
            }

            JToken requestedValue =
                arguments.ObjectReference == null
                    ? EditorCapabilityUtility
                        .ParseInputValue(
                            arguments.ValueJson)
                    : JObject.FromObject(
                        arguments.ObjectReference);

            requestedValue =
                EditorCapabilityUtility
                    .NormalizeSemanticSerializedInput(
                        target,
                        property,
                        requestedValue);

            Undo.IncrementCurrentGroup();

            int undoGroup =
                Undo.GetCurrentGroup();

            Undo.SetCurrentGroupName(
                $"Set {target.name}.{property.displayName}");

            EditorCapabilityUtility
                .AssignSerializedValue(
                    property,
                    requestedValue);

            JToken normalizedRequestedValue =
                EditorCapabilityUtility
                    .ReadSerializedValue(
                        property)
                    .DeepClone();

            bool changed =
                serializedObject
                    .ApplyModifiedProperties();

            Component component =
                target as Component;

            GameObject gameObject =
                target as GameObject;

            if (gameObject ==
                    null &&
                component !=
                    null)
            {
                gameObject =
                    component.gameObject;
            }

            bool isPrefabInstance =
                PrefabUtility.IsPartOfPrefabInstance(
                    target);

            if (changed)
            {
                EditorUtility.SetDirty(
                    target);
            }

            if (changed &&
                isPrefabInstance)
            {
                PrefabUtility
                    .RecordPrefabInstancePropertyModifications(
                        target);
            }

            bool sceneDirty =
                false;

            if (changed &&
                gameObject !=
                    null &&
                gameObject.scene.IsValid())
            {
                EditorSceneManager.MarkSceneDirty(
                    gameObject.scene);

                sceneDirty =
                    gameObject.scene.isDirty;
            }

            bool assetSaved =
                false;

            if (changed &&
                arguments.SaveAssets &&
                AssetDatabase.Contains(
                    target))
            {
                AssetDatabase.SaveAssets();

                assetSaved =
                    true;
            }

            Undo.CollapseUndoOperations(
                undoGroup);

            SerializedObject verificationObject =
                new SerializedObject(
                    target);

            verificationObject.UpdateIfRequiredOrScript();

            SerializedProperty verificationProperty =
                verificationObject.FindProperty(
                    property.propertyPath);

            if (verificationProperty ==
                null)
            {
                throw new InvalidOperationException(
                    $"Property '{arguments.PropertyPath}' disappeared during verification.");
            }

            JToken currentValue =
                EditorCapabilityUtility
                    .ReadSerializedValue(
                        verificationProperty)
                    .DeepClone();

            bool verified =
                JToken.DeepEquals(
                    normalizedRequestedValue,
                    currentValue);

            return Task.FromResult<object>(
                new SetSerializedPropertyResult
                {
                    ObjectInstanceId =
                        target.GetInstanceID(),

                    GlobalObjectId =
                        EditorCapabilityUtility
                            .GetGlobalObjectId(
                                target),

                    ObjectName =
                        target.name,

                    ObjectType =
                        target.GetType().FullName,

                    PropertyPath =
                        property.propertyPath,

                    PropertyType =
                        verificationProperty
                            .propertyType
                            .ToString(),

                    PreviousValue =
                        previousValue,

                    RequestedValue =
                        normalizedRequestedValue,

                    CurrentValue =
                        currentValue,

                    Changed =
                        changed,

                    Verified =
                        verified,

                    UndoRecorded =
                        changed,

                    IsPrefabInstance =
                        isPrefabInstance,

                    SceneDirty =
                        sceneDirty,

                    AssetSaved =
                        assetSaved
                });
        }


        private static SerializedProperty ResolveSerializedProperty(
            SerializedObject serializedObject,
            string requestedPath)
        {
            SerializedProperty exact =
                serializedObject.FindProperty(
                    requestedPath);

            if (exact != null)
                return exact;

            string requestedAlias =
                NormalizeTopLevelPropertyAlias(
                    requestedPath);

            if (string.IsNullOrWhiteSpace(requestedAlias))
                return null;

            List<SerializedProperty> matches =
                new List<SerializedProperty>();

            SerializedProperty iterator =
                serializedObject.GetIterator();

            bool enterChildren = true;

            while (iterator.Next(enterChildren))
            {
                enterChildren = false;

                if (iterator.depth != 0 ||
                    iterator.propertyPath.IndexOf('.') >= 0 ||
                    string.Equals(
                        iterator.propertyPath,
                        "m_Script",
                        StringComparison.Ordinal))
                {
                    continue;
                }

                if (string.Equals(
                        NormalizeTopLevelPropertyAlias(
                            iterator.propertyPath),
                        requestedAlias,
                        StringComparison.Ordinal) ||
                    string.Equals(
                        NormalizeTopLevelPropertyAlias(
                            iterator.displayName),
                        requestedAlias,
                        StringComparison.Ordinal))
                {
                    matches.Add(
                        iterator.Copy());
                }
            }

            if (matches.Count == 1)
                return matches[0];

            if (matches.Count > 1)
            {
                throw new InvalidOperationException(
                    $"Serialized property alias '{requestedPath}' is ambiguous. Use one exact path from get_serialized_properties: {string.Join(", ", matches.Select(match => match.propertyPath))}");
            }

            return null;
        }


        private static string NormalizeTopLevelPropertyAlias(
            string value)
        {
            if (string.IsNullOrWhiteSpace(value))
                return string.Empty;

            string normalized =
                value.Trim();

            if (normalized.StartsWith(
                    "m_",
                    StringComparison.OrdinalIgnoreCase))
            {
                normalized =
                    normalized.Substring(2);
            }

            return new string(
                normalized
                    .Where(char.IsLetterOrDigit)
                    .Select(char.ToLowerInvariant)
                    .ToArray());
        }
    }
}
