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 UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;

using Object =
    UnityEngine.Object;


namespace AIRuntime.Editor
{
    [Serializable]
    public sealed class GetEditorSelectionArguments
    {
        [DefaultValue(true)]
        [Description("Include component type names for selected GameObjects and Components.")]
        public bool IncludeComponents { get; set; } =
            true;
    }


    [Serializable]
    internal sealed class EditorSelectionResult
    {
        public int Count;

        public int ActiveObjectInstanceId;

        public List<EditorSelectionObject> Objects =
            new List<EditorSelectionObject>();

        public string ResponseInstruction =
            "Describe only the selection returned by this capability. IsFolder=true identifies a Project-window folder; use FolderPath exactly as returned for any follow-up filesystem tool. For GameObjects, report missing scripts when HasMissingComponents is true. MissingComponentCount=0 means the GameObject is clean. Component and missing-script checks do not apply to folders or other non-GameObject assets. An empty Objects array means nothing is selected.";
    }


    [Serializable]
    internal sealed class EditorSelectionObject
    {
        public int InstanceId;

        public int GameObjectInstanceId;

        public string GlobalObjectId;

        public string Name;

        public string Type;

        public string AssetPath;

        public string HierarchyPath;

        public string ScenePath;

        public bool IsAsset;

        public bool IsFolder;

        public string FolderPath;

        public string ParentFolderPath;

        public bool IsGameObject;

        public bool IsComponent;

        public int ComponentCount;

        public bool HasMissingComponents;

        public int MissingComponentCount;

        public List<string> Components =
            new List<string>();

        public List<EditorSelectionComponent> ComponentDetails =
            new List<EditorSelectionComponent>();
    }


    [Serializable]
    internal sealed class EditorSelectionComponent
    {
        public int InstanceId;

        public string Type;

        public bool IsMissing;
    }


    /// <summary>
    /// Exposes the user's current Unity Editor selection to an agent.
    /// This capability lives in the Editor assembly so the runtime
    /// remains independent of UnityEditor.
    /// </summary>
    public sealed class GetEditorSelectionCapability :
        CapabilityBase<GetEditorSelectionArguments>
    {
        public override string Name =>
            "get_editor_selection";


        public override string Description =>
            "Returns the objects currently selected by the user in the Unity Editor, including Project-window folders, assets, GameObjects, object types, hierarchy paths, scene paths, asset paths, and optionally component types.";


        public override CapabilityPermission Permission =>
            CapabilityPermission.ReadOnly;


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

            Object[] selectedObjects =
                CollectSelectedObjects();

            Object activeObject =
                Selection.activeObject ??
                selectedObjects.FirstOrDefault();

            EditorSelectionResult result =
                new EditorSelectionResult
                {
                    Count =
                        selectedObjects.Length,

                    ActiveObjectInstanceId =
                        activeObject != null
                            ? activeObject.GetInstanceID()
                            : 0
                };


            foreach (
                Object selectedObject
                in selectedObjects)
            {
                if (selectedObject == null)
                    continue;


                result.Objects.Add(
                    DescribeObject(
                        selectedObject,
                        arguments?.IncludeComponents ??
                        true));
            }


            result.Count =
                result.Objects.Count;

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

        private static Object[] CollectSelectedObjects()
        {
            List<Object> selected =
                (Selection.objects ??
                 Array.Empty<Object>())
                .Where(
                    value =>
                        value != null)
                .ToList();

            HashSet<int> instanceIds =
                new HashSet<int>(
                    selected.Select(
                        value =>
                            value.GetInstanceID()));

            // Project-window folder selections are exposed through assetGUIDs
            // even when Selection.objects is empty in some Unity layouts.
            foreach (
                string guid
                in Selection.assetGUIDs ??
                   Array.Empty<string>())
            {
                if (string.IsNullOrWhiteSpace(
                        guid))
                {
                    continue;
                }

                string assetPath =
                    NormalizePath(
                        AssetDatabase.GUIDToAssetPath(
                            guid));

                if (string.IsNullOrWhiteSpace(
                        assetPath) ||
                    !AssetDatabase.IsValidFolder(
                        assetPath))
                {
                    continue;
                }

                Object folder =
                    AssetDatabase.LoadMainAssetAtPath(
                        assetPath);

                if (folder !=
                        null &&
                    instanceIds.Add(
                        folder.GetInstanceID()))
                {
                    selected.Add(
                        folder);
                }
            }

            return selected.ToArray();
        }


        private static EditorSelectionObject DescribeObject(
            Object selectedObject,
            bool includeComponents)
        {
            Component component =
                selectedObject as Component;

            GameObject gameObject =
                selectedObject as GameObject ??
                component?.gameObject;

            string assetPath =
                AssetDatabase.GetAssetPath(
                    selectedObject);


            if (string.IsNullOrWhiteSpace(
                    assetPath) &&
                gameObject != null)
            {
                assetPath =
                    AssetDatabase.GetAssetPath(
                        gameObject);
            }

            string normalizedAssetPath =
                NormalizePath(
                    assetPath);

            bool isFolder =
                !string.IsNullOrWhiteSpace(
                    normalizedAssetPath) &&
                AssetDatabase.IsValidFolder(
                    normalizedAssetPath);

            EditorSelectionObject description =
                new EditorSelectionObject
                {
                    InstanceId =
                        selectedObject.GetInstanceID(),

                    GameObjectInstanceId =
                        gameObject != null
                            ? gameObject.GetInstanceID()
                            : 0,

                    GlobalObjectId =
                        GlobalObjectId
                            .GetGlobalObjectIdSlow(
                                selectedObject)
                            .ToString(),

                    Name =
                        selectedObject.name,

                    Type =
                        selectedObject.GetType().FullName,

                    AssetPath =
                        normalizedAssetPath,

                    HierarchyPath =
                        GetHierarchyPath(
                            gameObject),

                    ScenePath =
                        GetScenePath(
                            gameObject),

                    IsAsset =
                        !string.IsNullOrWhiteSpace(
                            assetPath),

                    IsFolder =
                        isFolder,

                    FolderPath =
                        isFolder
                            ? normalizedAssetPath
                            : string.Empty,

                    ParentFolderPath =
                        isFolder
                            ? GetParentAssetPath(
                                normalizedAssetPath)
                            : string.Empty,

                    IsGameObject =
                        selectedObject is GameObject,

                    IsComponent =
                        component != null
                };


            if (gameObject != null)
            {
                Component[] components =
                    gameObject.GetComponents<Component>();

                description.ComponentCount =
                    components.Length;

                description.MissingComponentCount =
                    components.Count(
                        value =>
                            value == null);

                description.HasMissingComponents =
                    description.MissingComponentCount >
                    0;


                if (includeComponents)
                {
                    description.Components =
                        components
                            .Select(
                                value =>
                                    value == null
                                        ? "[Missing Script]"
                                        : value.GetType().FullName)
                            .ToList();

                    description.ComponentDetails =
                        components
                            .Select(
                                value =>
                                    new EditorSelectionComponent
                                    {
                                        InstanceId =
                                            value != null
                                                ? value.GetInstanceID()
                                                : 0,

                                        Type =
                                            value != null
                                                ? value.GetType().FullName
                                                : "[Missing Script]",

                                        IsMissing =
                                            value == null
                                    })
                            .ToList();
                }
            }


            return description;
        }

        private static string GetParentAssetPath(
            string assetPath)
        {
            int separatorIndex =
                assetPath.LastIndexOf(
                    '/');

            return separatorIndex >
                   0
                ? assetPath.Substring(
                    0,
                    separatorIndex)
                : string.Empty;
        }


        private static string GetHierarchyPath(
            GameObject gameObject)
        {
            if (gameObject == null)
                return string.Empty;


            Stack<string> names =
                new Stack<string>();

            Transform current =
                gameObject.transform;


            while (current != null)
            {
                names.Push(
                    current.name);

                current =
                    current.parent;
            }


            return string.Join(
                "/",
                names);
        }


        private static string GetScenePath(
            GameObject gameObject)
        {
            if (gameObject == null)
                return string.Empty;


            Scene scene =
                gameObject.scene;


            if (!scene.IsValid())
                return string.Empty;


            return string.IsNullOrWhiteSpace(
                scene.path)
                ? scene.name
                : NormalizePath(
                    scene.path);
        }


        private static string NormalizePath(
            string path)
        {
            return string.IsNullOrWhiteSpace(
                path)
                ? string.Empty
                : path.Replace(
                    '\\',
                    '/');
        }
    }


    [Serializable]
    public sealed class CreateEditorSelectionArguments
    {
        [Description("Exact Unity object instance IDs to select. Obtain live scene-object IDs from get_editor_selection, find_game_objects, inspect_game_object, or get_hierarchy_tree.")]
        public List<int> InstanceIds { get; set; } =
            new List<int>();

        [Description("Exact Unity asset or Project-window folder paths to select, for example Assets/Runtime/Docs or Assets/Scenes/Main.unity.")]
        public List<string> AssetPaths { get; set; } =
            new List<string>();

        [DefaultValue(false)]
        [Description("Keep the current Unity selection and add the requested targets instead of replacing it.")]
        public bool Additive { get; set; }

        [DefaultValue(false)]
        [Description("Clear the current Unity selection. Cannot be combined with InstanceIds, AssetPaths, or Additive.")]
        public bool ClearSelection { get; set; }
    }


    [Serializable]
    internal sealed class CreateEditorSelectionResult
    {
        public bool Succeeded;

        public bool Cleared;

        public bool Additive;

        public int Count;

        public int ActiveObjectInstanceId;

        public List<CreatedEditorSelectionObject> Objects =
            new List<CreatedEditorSelectionObject>();

        public string Message;

        public string ResponseInstruction =
            "Report only the selection returned here. A successful result means the Unity Editor selection changed, not that any scene or asset data was modified. Call get_editor_selection afterward when detailed object or component information is required.";
    }


    [Serializable]
    internal sealed class CreatedEditorSelectionObject
    {
        public int InstanceId;

        public string Name;

        public string Type;

        public string AssetPath;

        public bool IsFolder;

        public bool IsGameObject;
    }


    /// <summary>
    /// Replaces, extends, or clears the user's Unity Editor selection using
    /// exact live-object identities and asset paths.
    /// </summary>
    public sealed class CreateEditorSelectionCapability :
        CapabilityBase<CreateEditorSelectionArguments>
    {
        public override string Name =>
            "create_editor_selection";


        public override string Description =>
            "Creates the Unity Editor selection from exact object instance IDs and/or asset paths. Supports replacing, adding to, or clearing the current selection. Resolve every target before changing the selection.";


        public override CapabilityPermission Permission =>
            CapabilityPermission.Write;


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

            arguments =
                arguments ??
                new CreateEditorSelectionArguments();

            List<int> instanceIds =
                arguments.InstanceIds ??
                new List<int>();

            List<string> assetPaths =
                arguments.AssetPaths ??
                new List<string>();

            bool hasTargets =
                instanceIds.Count > 0 ||
                assetPaths.Count > 0;

            if (arguments.ClearSelection)
            {
                if (hasTargets ||
                    arguments.Additive)
                {
                    throw new ArgumentException(
                        "ClearSelection cannot be combined with InstanceIds, AssetPaths, or Additive.");
                }

                Selection.objects =
                    Array.Empty<Object>();

                return Task.FromResult<object>(
                    new CreateEditorSelectionResult
                    {
                        Succeeded =
                            true,

                        Cleared =
                            true,

                        Count =
                            0,

                        Message =
                            "Unity Editor selection cleared successfully."
                    });
            }

            if (!hasTargets)
            {
                throw new ArgumentException(
                    "At least one InstanceId or AssetPath is required unless ClearSelection is true.");
            }

            List<Object> requested =
                new List<Object>();

            HashSet<int> requestedIds =
                new HashSet<int>();

            foreach (int instanceId in instanceIds)
            {
                context.CancellationToken
                    .ThrowIfCancellationRequested();

                if (instanceId == 0)
                {
                    throw new ArgumentException(
                        "InstanceIds cannot contain zero.");
                }

                Object target =
                    EditorUtility.InstanceIDToObject(
                        instanceId);

                if (target == null)
                {
                    throw new InvalidOperationException(
                        $"No live Unity object exists with instance ID {instanceId}. Obtain a fresh identity before retrying.");
                }

                AddUnique(
                    requested,
                    requestedIds,
                    target);
            }

            foreach (string assetPath in assetPaths)
            {
                context.CancellationToken
                    .ThrowIfCancellationRequested();

                string normalizedPath =
                    NormalizeAssetPath(
                        assetPath);

                Object target =
                    AssetDatabase.LoadMainAssetAtPath(
                        normalizedPath);

                if (target == null)
                {
                    throw new InvalidOperationException(
                        $"No Unity asset or Project-window folder exists at path '{normalizedPath}'.");
                }

                AddUnique(
                    requested,
                    requestedIds,
                    target);
            }

            List<Object> finalSelection =
                new List<Object>();

            HashSet<int> finalIds =
                new HashSet<int>();

            if (arguments.Additive)
            {
                foreach (
                    Object selectedObject
                    in Selection.objects ??
                       Array.Empty<Object>())
                {
                    AddUnique(
                        finalSelection,
                        finalIds,
                        selectedObject);
                }
            }

            foreach (Object target in requested)
            {
                AddUnique(
                    finalSelection,
                    finalIds,
                    target);
            }

            Selection.objects =
                finalSelection.ToArray();

            CreateEditorSelectionResult result =
                new CreateEditorSelectionResult
                {
                    Succeeded =
                        true,

                    Additive =
                        arguments.Additive,

                    Count =
                        finalSelection.Count,

                    ActiveObjectInstanceId =
                        Selection.activeObject != null
                            ? Selection.activeObject.GetInstanceID()
                            : 0,

                    Message =
                        $"Unity Editor selection now contains {finalSelection.Count} object(s)."
                };

            foreach (Object selectedObject in finalSelection)
            {
                string path =
                    NormalizeAssetPath(
                        AssetDatabase.GetAssetPath(
                            selectedObject));

                result.Objects.Add(
                    new CreatedEditorSelectionObject
                    {
                        InstanceId =
                            selectedObject.GetInstanceID(),

                        Name =
                            selectedObject.name,

                        Type =
                            selectedObject.GetType().FullName,

                        AssetPath =
                            path,

                        IsFolder =
                            !string.IsNullOrWhiteSpace(path) &&
                            AssetDatabase.IsValidFolder(path),

                        IsGameObject =
                            selectedObject is GameObject
                    });
            }

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


        private static void AddUnique(
            List<Object> objects,
            HashSet<int> instanceIds,
            Object target)
        {
            if (target == null)
                return;

            int instanceId =
                target.GetInstanceID();

            if (instanceIds.Add(
                    instanceId))
            {
                objects.Add(
                    target);
            }
        }


        private static string NormalizeAssetPath(
            string assetPath)
        {
            return string.IsNullOrWhiteSpace(
                assetPath)
                ? string.Empty
                : assetPath.Trim()
                    .Replace(
                        '\\',
                        '/');
        }
    }


    [Serializable]
    public sealed class AddEditorComponentArguments
    {
        [Required]
        [Description("Exact instance ID of the target GameObject, obtained from a fresh get_editor_selection result.")]
        public int GameObjectInstanceId { get; set; }

        [Required]
        [Description("Component type to add. Prefer the full type name, for example UnityEngine.BoxCollider.")]
        public string ComponentType { get; set; }
    }


    [Serializable]
    public sealed class RemoveEditorComponentArguments
    {
        [Description("Exact ComponentDetails[].InstanceId to remove. Use this when it is non-zero. Do not pass ComponentCount or a list index.")]
        public int ComponentInstanceId { get; set; }

        [Description("GameObjectInstanceId returned by get_editor_selection. Required when removing a missing script or targeting by ComponentType.")]
        public int GameObjectInstanceId { get; set; }

        [Description("Exact component type returned by ComponentDetails[].Type. Use this with GameObjectInstanceId only when ComponentInstanceId is unavailable.")]
        public string ComponentType { get; set; }

        [DefaultValue(false)]
        [Description("Set true to remove every [Missing Script] entry from the target GameObject. Requires GameObjectInstanceId; missing scripts do not have component instance IDs.")]
        public bool RemoveMissingScripts { get; set; }

        [DefaultValue(false)]
        [Description("Set true only when the user explicitly asks to remove missing components from all selected objects. Every currently selected GameObject with missing scripts will be cleaned.")]
        public bool RemoveFromAllSelected { get; set; }
    }


    [Serializable]
    public sealed class EditorComponentMutationResult
    {
        public bool Succeeded;
        public string Action;
        public int GameObjectInstanceId;
        public string GameObjectName;
        public string HierarchyPath;
        public int ComponentInstanceId;
        public string ComponentType;
        public string Message;

        public int AffectedCount;

        public List<string> AffectedGameObjects =
            new List<string>();

        public bool VerificationSucceeded;

        public int RemainingMissingComponentCount;

        public List<string> GameObjectsWithRemainingMissingScripts =
            new List<string>();

        public string ResponseInstruction =
            "Report the action, GameObjectName, HierarchyPath, and ComponentType exactly as returned. Do not claim that any other object was changed.";
    }


    /// <summary>
    /// Adds a component to an exact Unity Editor GameObject target.
    /// </summary>
    public sealed class AddEditorComponentCapability :
        CapabilityBase<AddEditorComponentArguments>
    {
        public override string Name =>
            "add_editor_component";


        public override string Description =>
            "Adds a component to an exact Unity Editor GameObject. First call get_editor_selection, then pass its GameObject InstanceId and the exact component type. The operation is Undo-enabled.";


        public override CapabilityPermission Permission =>
            CapabilityPermission.Write;


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

            GameObject gameObject =
                ResolveGameObject(
                    arguments?.GameObjectInstanceId ??
                    0);

            Type componentType =
                ResolveComponentType(
                    arguments?.ComponentType);


            if (typeof(Transform).IsAssignableFrom(
                    componentType))
            {
                throw new InvalidOperationException(
                    "Transform components cannot be added with this capability.");
            }


            if (componentType.IsAbstract ||
                componentType.ContainsGenericParameters)
            {
                throw new InvalidOperationException(
                    $"Component type '{componentType.FullName}' cannot be instantiated.");
            }


            if (Attribute.IsDefined(
                    componentType,
                    typeof(DisallowMultipleComponent),
                    inherit: true) &&
                gameObject.GetComponent(
                    componentType) !=
                null)
            {
                throw new InvalidOperationException(
                    $"GameObject '{gameObject.name}' already has a component of type '{componentType.FullName}', which does not allow duplicates.");
            }


            Component component =
                Undo.AddComponent(
                    gameObject,
                    componentType);


            if (component == null)
            {
                throw new InvalidOperationException(
                    $"Unity could not add component type '{componentType.FullName}' to '{gameObject.name}'.");
            }


            MarkChanged(
                gameObject);

            return Task.FromResult<object>(
                CreateResult(
                    "Added",
                    gameObject,
                    component,
                    $"Component '{componentType.FullName}' was added successfully."));
        }


        private static Type ResolveComponentType(
            string requestedType)
        {
            if (string.IsNullOrWhiteSpace(
                    requestedType))
            {
                throw new ArgumentException(
                    "ComponentType cannot be empty.",
                    nameof(requestedType));
            }


            string normalized =
                requestedType.Trim();

            string shortTypeName =
                normalized.Contains(".")
                    ? normalized.Substring(
                        normalized.LastIndexOf('.') + 1)
                    : normalized;

            List<Type> componentTypes =
                TypeCache
                    .GetTypesDerivedFrom<Component>()
                    .Where(
                        type =>
                            type != null)
                    .ToList();

            List<Type> matches =
                componentTypes
                    .Where(
                        type =>
                            string.Equals(
                                type.FullName,
                                normalized,
                                StringComparison.Ordinal) ||
                            string.Equals(
                                type.AssemblyQualifiedName,
                                normalized,
                                StringComparison.Ordinal))
                    .ToList();


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


            matches =
                componentTypes
                    .Where(
                        type =>
                            string.Equals(
                                type.Name,
                                shortTypeName,
                                StringComparison.Ordinal))
                    .ToList();


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


            if (matches.Count >
                1)
            {
                throw new InvalidOperationException(
                    $"Component type '{normalized}' is ambiguous. Use one of: {string.Join(", ", matches.Select(type => type.FullName))}");
            }


            List<string> matchingScriptPaths =
                AssetDatabase.FindAssets(
                        shortTypeName + " t:MonoScript",
                        new[] { "Assets" })
                    .Select(
                        AssetDatabase.GUIDToAssetPath)
                    .Where(
                        path =>
                            string.Equals(
                                System.IO.Path.GetFileNameWithoutExtension(path),
                                shortTypeName,
                                StringComparison.OrdinalIgnoreCase))
                    .ToList();

            if (matchingScriptPaths.Count > 0)
            {
                string compilationState =
                    EditorApplication.isCompiling
                        ? "Unity is still compiling. Wait for compilation and the domain reload before retrying."
                        : "Unity did not load a Component class from the script. Check get_compilation_errors and verify that the namespace, class name, file name, and MonoBehaviour base type match.";

                throw new InvalidOperationException(
                    $"Component type '{normalized}' is not currently loadable. Matching script asset(s): {string.Join(", ", matchingScriptPaths)}. {compilationState}");
            }

            throw new InvalidOperationException(
                $"Component type '{normalized}' was not found, and no matching '{shortTypeName}.cs' script asset exists under Assets. Complete the prerequisite script-creation task first, allow Unity to compile it, then retry with its exact namespace-qualified type name.");
        }


        internal static GameObject ResolveGameObject(
            int instanceId)
        {
            if (instanceId ==
                0)
            {
                throw new ArgumentException(
                    "GameObjectInstanceId must come from a fresh get_editor_selection result.",
                    nameof(instanceId));
            }


            GameObject gameObject =
                EditorUtility.InstanceIDToObject(
                    instanceId) as GameObject;


            if (gameObject == null)
            {
                throw new InvalidOperationException(
                    $"No live GameObject exists with instance ID {instanceId}. Refresh the selection before retrying.");
            }


            return gameObject;
        }


        internal static EditorComponentMutationResult CreateResult(
            string action,
            GameObject gameObject,
            Component component,
            string message)
        {
            return new EditorComponentMutationResult
            {
                Succeeded =
                    true,
                Action =
                    action,
                GameObjectInstanceId =
                    gameObject.GetInstanceID(),
                GameObjectName =
                    gameObject.name,
                HierarchyPath =
                    GetHierarchyPath(
                        gameObject),
                ComponentInstanceId =
                    component.GetInstanceID(),
                ComponentType =
                    component.GetType().FullName,
                AffectedCount =
                    1,
                Message =
                    message
            };
        }


        internal static void MarkChanged(
            GameObject gameObject)
        {
            EditorUtility.SetDirty(
                gameObject);


            if (gameObject.scene.IsValid())
            {
                EditorSceneManager.MarkSceneDirty(
                    gameObject.scene);
            }
        }


        private static string GetHierarchyPath(
            GameObject gameObject)
        {
            Stack<string> names =
                new Stack<string>();

            Transform current =
                gameObject.transform;


            while (current != null)
            {
                names.Push(
                    current.name);

                current =
                    current.parent;
            }


            return string.Join(
                "/",
                names);
        }
    }


    /// <summary>
    /// Removes one exact component from a Unity Editor GameObject.
    /// </summary>
    public sealed class RemoveEditorComponentCapability :
        CapabilityBase<RemoveEditorComponentArguments>
    {
        public override string Name =>
            "remove_editor_component";


        public override string Description =>
            "Removes a Unity component from a freshly inspected selection. Prefer ComponentDetails[].InstanceId for a normal component. For [Missing Script], set removeMissingScripts=true. When the user explicitly requests cleanup of all selected objects, also set removeFromAllSelected=true; otherwise provide one Objects[].GameObjectInstanceId. ComponentCount and list positions are never instance IDs. Transform removal is blocked. The operation is Undo-enabled.";


        public override CapabilityPermission Permission =>
            CapabilityPermission.Destructive;


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

            int instanceId =
                arguments?.ComponentInstanceId ??
                0;


            if (arguments != null &&
                (arguments.RemoveMissingScripts ||
                 IsMissingScriptType(
                     arguments.ComponentType)))
            {
                return Task.FromResult<object>(
                    arguments.RemoveFromAllSelected
                        ? RemoveMissingScriptsFromAllSelected()
                        : RemoveMissingScripts(
                            arguments.GameObjectInstanceId));
            }


            Component component =
                instanceId !=
                0
                    ? EditorUtility.InstanceIDToObject(
                        instanceId) as Component
                    : ResolveComponentByType(
                        arguments?.GameObjectInstanceId ??
                        0,
                        arguments?.ComponentType);


            if (component == null)
            {
                throw new InvalidOperationException(
                    $"No live component exists with instance ID {instanceId}. ComponentCount and component list positions are not instance IDs; call get_editor_selection again and use ComponentDetails[].InstanceId.");
            }


            if (component is Transform)
            {
                throw new InvalidOperationException(
                    "Transform components cannot be removed.");
            }


            GameObject gameObject =
                component.gameObject;

            EditorComponentMutationResult result =
                AddEditorComponentCapability.CreateResult(
                    "Removed",
                    gameObject,
                    component,
                    $"Component '{component.GetType().FullName}' was removed successfully.");

            Undo.DestroyObjectImmediate(
                component);

            AddEditorComponentCapability.MarkChanged(
                gameObject);

            bool removalVerified =
                EditorUtility.InstanceIDToObject(
                    instanceId) ==
                null &&
                gameObject
                    .GetComponents<Component>()
                    .Where(
                        remaining =>
                            remaining !=
                            null)
                    .All(
                        remaining =>
                            remaining.GetInstanceID() !=
                            instanceId);

            if (!removalVerified)
            {
                throw new InvalidOperationException(
                    $"Unity attempted to remove component instance ID {instanceId}, but a fresh component scan could not verify its absence.");
            }

            result.AffectedGameObjects =
                new List<string>
                {
                    gameObject.name
                };

            result.VerificationSucceeded =
                true;

            result.Message =
                $"Component '{result.ComponentType}' was removed successfully, and a fresh component scan confirmed that instance ID {instanceId} is absent.";

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


        private static EditorComponentMutationResult RemoveMissingScripts(
            int gameObjectInstanceId)
        {
            GameObject gameObject =
                ResolveMissingScriptTarget(
                    gameObjectInstanceId);

            int missingCount =
                GameObjectUtility
                    .GetMonoBehavioursWithMissingScriptCount(
                        gameObject);


            if (missingCount <=
                0)
            {
                throw new InvalidOperationException(
                    $"GameObject '{gameObject.name}' has no missing scripts to remove.");
            }


            Undo.RegisterCompleteObjectUndo(
                gameObject,
                "Remove Missing Scripts");

            int removedCount =
                GameObjectUtility
                    .RemoveMonoBehavioursWithMissingScript(
                        gameObject);

            AddEditorComponentCapability.MarkChanged(
                gameObject);

            int remainingCount =
                GameObjectUtility
                    .GetMonoBehavioursWithMissingScriptCount(
                        gameObject);


            if (remainingCount >
                0)
            {
                throw new InvalidOperationException(
                    $"Unity removed {removedCount} missing script component(s), but '{gameObject.name}' still has {remainingCount}. The cleanup was not verified.");
            }


            return new EditorComponentMutationResult
            {
                Succeeded =
                    true,
                Action =
                    "Removed",
                GameObjectInstanceId =
                    gameObject.GetInstanceID(),
                GameObjectName =
                    gameObject.name,
                HierarchyPath =
                    GetHierarchyPath(
                        gameObject),
                ComponentInstanceId =
                    0,
                ComponentType =
                    "[Missing Script]",
                AffectedCount =
                    removedCount,
                AffectedGameObjects =
                    new List<string>
                    {
                        gameObject.name
                    },
                VerificationSucceeded =
                    true,
                RemainingMissingComponentCount =
                    0,
                Message =
                    $"{removedCount} missing script component(s) were removed and a fresh Unity check confirmed zero remaining."
            };
        }


        private static EditorComponentMutationResult
            RemoveMissingScriptsFromAllSelected()
        {
            GameObject[] targets =
                GetSelectedMissingScriptTargets();


            if (targets.Length ==
                0)
            {
                return new EditorComponentMutationResult
                {
                    Succeeded =
                        true,
                    Action =
                        "Verified",
                    ComponentType =
                        "[Missing Script]",
                    AffectedCount =
                        0,
                    VerificationSucceeded =
                        true,
                    RemainingMissingComponentCount =
                        0,
                    Message =
                        "The selected GameObjects are already clean; a fresh Unity check confirmed zero missing scripts."
                };
            }


            int removedCount =
                0;

            List<string> affectedGameObjects =
                new List<string>();


            foreach (
                GameObject target
                in targets)
            {
                Undo.RegisterCompleteObjectUndo(
                    target,
                    "Remove Missing Scripts");

                removedCount +=
                    GameObjectUtility
                        .RemoveMonoBehavioursWithMissingScript(
                            target);

                AddEditorComponentCapability.MarkChanged(
                    target);

                affectedGameObjects.Add(
                    target.name);
            }


            List<string> remainingObjects =
                targets
                    .Where(
                        target =>
                            GameObjectUtility
                                .GetMonoBehavioursWithMissingScriptCount(
                                    target) >
                            0)
                    .Select(
                        target =>
                            target.name)
                    .ToList();

            int remainingCount =
                targets.Sum(
                    target =>
                        GameObjectUtility
                            .GetMonoBehavioursWithMissingScriptCount(
                                target));


            if (remainingCount >
                0)
            {
                throw new InvalidOperationException(
                    $"Unity removed {removedCount} missing script component(s), but {remainingCount} remain on: {string.Join(", ", remainingObjects)}. The cleanup was not verified.");
            }


            return new EditorComponentMutationResult
            {
                Succeeded =
                    true,
                Action =
                    "Removed",
                GameObjectInstanceId =
                    0,
                GameObjectName =
                    string.Join(
                        ", ",
                        affectedGameObjects),
                HierarchyPath =
                    string.Empty,
                ComponentInstanceId =
                    0,
                ComponentType =
                    "[Missing Script]",
                AffectedCount =
                    removedCount,
                AffectedGameObjects =
                    affectedGameObjects,
                VerificationSucceeded =
                    true,
                RemainingMissingComponentCount =
                    0,
                GameObjectsWithRemainingMissingScripts =
                    remainingObjects,
                Message =
                    $"{removedCount} missing script component(s) were removed from {affectedGameObjects.Count} selected GameObject(s), and a fresh Unity check confirmed zero remaining."
            };
        }


        private static bool IsMissingScriptType(
            string componentType)
        {
            if (string.IsNullOrWhiteSpace(
                    componentType))
            {
                return false;
            }


            string normalized =
                componentType
                    .Trim()
                    .Trim(
                        '[',
                        ']')
                    .Trim();

            return string.Equals(
                normalized,
                "Missing Script",
                StringComparison.OrdinalIgnoreCase);
        }


        private static GameObject ResolveMissingScriptTarget(
            int requestedInstanceId)
        {
            GameObject requested =
                requestedInstanceId !=
                0
                    ? EditorUtility.InstanceIDToObject(
                        requestedInstanceId) as GameObject
                    : null;

            GameObject[] selectedGameObjects =
                Selection.gameObjects ??
                Array.Empty<GameObject>();


            if (requested != null &&
                selectedGameObjects.Contains(
                    requested) &&
                GameObjectUtility
                    .GetMonoBehavioursWithMissingScriptCount(
                        requested) >
                0)
            {
                return requested;
            }


            GameObject[] candidates =
                GetSelectedMissingScriptTargets();


            if (candidates.Length ==
                1)
            {
                return candidates[0];
            }


            if (candidates.Length ==
                0)
            {
                throw new InvalidOperationException(
                    "None of the currently selected GameObjects contains a missing script. Refresh the selection before retrying.");
            }


            throw new InvalidOperationException(
                $"The current selection contains {candidates.Length} GameObjects with missing scripts. Select exactly one target before retrying: {string.Join(", ", candidates.Select(gameObject => gameObject.name))}");
        }


        private static GameObject[] GetSelectedMissingScriptTargets()
        {
            return (Selection.gameObjects ??
                    Array.Empty<GameObject>())
                .Where(
                    gameObject =>
                        gameObject != null &&
                        GameObjectUtility
                            .GetMonoBehavioursWithMissingScriptCount(
                                gameObject) >
                        0)
                .Distinct()
                .ToArray();
        }


        private static Component ResolveComponentByType(
            int gameObjectInstanceId,
            string requestedType)
        {
            if (string.IsNullOrWhiteSpace(
                    requestedType))
            {
                throw new ArgumentException(
                    "Provide ComponentInstanceId, or provide GameObjectInstanceId with ComponentType. To remove [Missing Script], set RemoveMissingScripts=true.",
                    nameof(requestedType));
            }


            GameObject gameObject =
                AddEditorComponentCapability.ResolveGameObject(
                    gameObjectInstanceId);

            string normalized =
                requestedType.Trim();

            Component[] matches =
                gameObject
                    .GetComponents<Component>()
                    .Where(
                        component =>
                            component != null &&
                            (string.Equals(
                                 component.GetType().FullName,
                                 normalized,
                                 StringComparison.Ordinal) ||
                             string.Equals(
                                 component.GetType().Name,
                                 normalized,
                                 StringComparison.Ordinal)))
                    .ToArray();


            if (matches.Length ==
                1)
            {
                return matches[0];
            }


            if (matches.Length >
                1)
            {
                throw new InvalidOperationException(
                    $"GameObject '{gameObject.name}' has {matches.Length} components matching '{normalized}'. Use the exact ComponentDetails[].InstanceId.");
            }


            throw new InvalidOperationException(
                $"GameObject '{gameObject.name}' does not have a component matching '{normalized}'. Refresh the selection before retrying.");
        }


        private static string GetHierarchyPath(
            GameObject gameObject)
        {
            Stack<string> names =
                new Stack<string>();

            Transform current =
                gameObject.transform;


            while (current != null)
            {
                names.Push(
                    current.name);

                current =
                    current.parent;
            }


            return string.Join(
                "/",
                names);
        }
    }
}
