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;

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


namespace AIRuntime.Editor
{
    [Serializable]
    internal sealed class EditorVector3Summary
    {
        public float X;

        public float Y;

        public float Z;


        internal static EditorVector3Summary FromVector3(
            Vector3 value)
        {
            return new EditorVector3Summary
            {
                X =
                    value.x,

                Y =
                    value.y,

                Z =
                    value.z
            };
        }
    }


    [Serializable]
    internal sealed class EditorTransformSummary
    {
        public EditorVector3Summary LocalPosition;

        public EditorVector3Summary LocalEulerAngles;

        public EditorVector3Summary LocalScale;

        public EditorVector3Summary WorldPosition;

        public EditorVector3Summary WorldEulerAngles;

        public EditorVector3Summary LossyScale;
    }


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

        public string GlobalObjectId;

        public string Type;

        public bool IsMissing;

        public bool HasEnabledState;

        public bool Enabled;
    }


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

        public string GlobalObjectId;

        public string Name;

        public string HierarchyPath;

        public string ScenePath;

        public bool ActiveSelf;

        public bool ActiveInHierarchy;

        public string Tag;

        public int Layer;

        public string LayerName;

        public bool IsStatic;

        public int ChildCount;

        public int ComponentCount;

        public int MissingComponentCount;

        public bool HasMissingComponents;

        public string PrefabInstanceStatus;

        public string PrefabAssetType;

        public EditorTransformSummary Transform;

        public List<EditorGameObjectComponentSummary> Components =
            new List<EditorGameObjectComponentSummary>();
    }


    [Serializable]
    public sealed class InspectGameObjectArguments :
        EditorObjectTargetArguments
    {
        [DefaultValue(true)]
        [Description("Include component IDs, types, missing scripts, and enabled states.")]
        public bool IncludeComponents { get; set; } =
            true;
    }


    [Serializable]
    internal sealed class InspectGameObjectResult
    {
        public EditorGameObjectSummary GameObject;

        public string ResponseInstruction =
            "This is authoritative live Editor state. MissingComponentCount applies only to this GameObject. Use component InstanceId values for component inspection or serialized-property tools.";
    }


    public sealed class InspectGameObjectCapability :
        CapabilityBase<InspectGameObjectArguments>
    {
        public override string Name =>
            "inspect_game_object";


        public override string Description =>
            "Inspects one selected or identified Unity GameObject, including transform, activation, tag, layer, children, prefab state, components, enabled state, and missing scripts.";


        public override CapabilityPermission Permission =>
            CapabilityPermission.ReadOnly;


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

            InspectGameObjectArguments request =
                arguments ??
                new InspectGameObjectArguments();

            GameObject gameObject =
                EditorCapabilityUtility
                    .ResolveGameObject(
                        request.ObjectInstanceId,
                        request.GlobalObjectId);

            return Task.FromResult<object>(
                new InspectGameObjectResult
                {
                    GameObject =
                        DescribeGameObject(
                            gameObject,
                            request.IncludeComponents)
                });
        }


        internal static EditorGameObjectSummary DescribeGameObject(
            GameObject gameObject,
            bool includeComponents)
        {
            Component[] components =
                gameObject.GetComponents<Component>();

            EditorGameObjectSummary result =
                new EditorGameObjectSummary
                {
                    InstanceId =
                        gameObject.GetInstanceID(),

                    GlobalObjectId =
                        EditorCapabilityUtility
                            .GetGlobalObjectId(
                                gameObject),

                    Name =
                        gameObject.name,

                    HierarchyPath =
                        EditorCapabilityUtility
                            .GetHierarchyPath(
                                gameObject),

                    ScenePath =
                        EditorCapabilityUtility
                            .GetScenePath(
                                gameObject),

                    ActiveSelf =
                        gameObject.activeSelf,

                    ActiveInHierarchy =
                        gameObject.activeInHierarchy,

                    Tag =
                        gameObject.tag,

                    Layer =
                        gameObject.layer,

                    LayerName =
                        LayerMask.LayerToName(
                            gameObject.layer),

                    IsStatic =
                        gameObject.isStatic,

                    ChildCount =
                        gameObject.transform.childCount,

                    ComponentCount =
                        components.Length,

                    MissingComponentCount =
                        components.Count(
                            component =>
                                component ==
                                null),

                    PrefabInstanceStatus =
                        PrefabUtility
                            .GetPrefabInstanceStatus(
                                gameObject)
                            .ToString(),

                    PrefabAssetType =
                        PrefabUtility
                            .GetPrefabAssetType(
                                gameObject)
                            .ToString(),

                    Transform =
                        new EditorTransformSummary
                        {
                            LocalPosition =
                                EditorVector3Summary.FromVector3(
                                    gameObject.transform.localPosition),

                            LocalEulerAngles =
                                EditorVector3Summary.FromVector3(
                                    gameObject.transform.localEulerAngles),

                            LocalScale =
                                EditorVector3Summary.FromVector3(
                                    gameObject.transform.localScale),

                            WorldPosition =
                                EditorVector3Summary.FromVector3(
                                    gameObject.transform.position),

                            WorldEulerAngles =
                                EditorVector3Summary.FromVector3(
                                    gameObject.transform.eulerAngles),

                            LossyScale =
                                EditorVector3Summary.FromVector3(
                                    gameObject.transform.lossyScale)
                        }
                };

            result.HasMissingComponents =
                result.MissingComponentCount >
                0;

            if (!includeComponents)
            {
                return result;
            }

            foreach (
                Component component
                in components)
            {
                if (component ==
                    null)
                {
                    result.Components.Add(
                        new EditorGameObjectComponentSummary
                        {
                            Type =
                                "[Missing Script]",

                            IsMissing =
                                true
                        });

                    continue;
                }

                bool hasEnabledState =
                    EditorCapabilityUtility
                        .TryGetEnabled(
                            component,
                            out bool enabled);

                result.Components.Add(
                    new EditorGameObjectComponentSummary
                    {
                        InstanceId =
                            component.GetInstanceID(),

                        GlobalObjectId =
                            EditorCapabilityUtility
                                .GetGlobalObjectId(
                                    component),

                        Type =
                            component.GetType().FullName,

                        HasEnabledState =
                            hasEnabledState,

                        Enabled =
                            enabled
                    });
            }

            return result;
        }
    }


    [Serializable]
    public sealed class GetHierarchyTreeArguments
    {
        [Description("Optional loaded scene path or scene name. Omit to include all loaded scenes.")]
        public string ScenePath { get; set; }

        [Description("Optional root GameObject instance ID. Omit to use scene root objects.")]
        public int RootObjectInstanceId { get; set; }

        [DefaultValue(true)]
        [Description("Include inactive GameObjects.")]
        public bool IncludeInactive { get; set; } =
            true;

        [DefaultValue(false)]
        [Description("Include component type names on every hierarchy node.")]
        public bool IncludeComponents { get; set; }

        [DefaultValue(8)]
        [CapabilityRange(0, 32)]
        [Description("Maximum child depth below each returned root.")]
        public int MaxDepth { get; set; } =
            8;

        [DefaultValue(500)]
        [CapabilityRange(1, 5000)]
        [Description("Maximum total GameObjects returned.")]
        public int MaxObjects { get; set; } =
            500;
    }


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

        public string GlobalObjectId;

        public string Name;

        public string HierarchyPath;

        public string ScenePath;

        public bool ActiveSelf;

        public bool ActiveInHierarchy;

        public int ChildCount;

        public bool DepthLimited;

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

        public List<EditorHierarchyNode> Children =
            new List<EditorHierarchyNode>();
    }


    [Serializable]
    internal sealed class GetHierarchyTreeResult
    {
        public int ObjectCount;

        public bool Truncated;

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

        public List<EditorHierarchyNode> Roots =
            new List<EditorHierarchyNode>();

        public string ResponseInstruction =
            "Roots and Children are the authoritative loaded hierarchy. Truncated=true or DepthLimited=true means additional objects exist and should not be claimed absent.";
    }


    public sealed class GetHierarchyTreeCapability :
        CapabilityBase<GetHierarchyTreeArguments>,
        ICapabilityObservationSemantics
    {
        public override string Name =>
            "get_hierarchy_tree";


        public override string Description =>
            "Returns a bounded tree of loaded Unity scene GameObjects with stable identifiers, hierarchy paths, activation state, and optional component types.";


        public override CapabilityPermission Permission =>
            CapabilityPermission.ReadOnly;


        public CapabilityObservationBehavior ObservationBehavior =>
            CapabilityObservationBehavior.StableUntilMutation;


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

            GetHierarchyTreeArguments request =
                arguments ??
                new GetHierarchyTreeArguments();

            GetHierarchyTreeResult result =
                new GetHierarchyTreeResult();

            List<GameObject> roots =
                new List<GameObject>();

            if (request.RootObjectInstanceId !=
                0)
            {
                roots.Add(
                    EditorCapabilityUtility
                        .ResolveGameObject(
                            request.RootObjectInstanceId,
                            null,
                            false));
            }
            else
            {
                foreach (
                    Scene scene
                    in EnumerateLoadedScenes(
                        request.ScenePath))
                {
                    result.ScenePaths.Add(
                        NormalizeScenePath(
                            scene));

                    roots.AddRange(
                        scene.GetRootGameObjects());
                }
            }

            int count =
                0;

            foreach (
                GameObject root
                in roots)
            {
                if (count >=
                    request.MaxObjects)
                {
                    result.Truncated =
                        true;

                    break;
                }

                EditorHierarchyNode node =
                    BuildHierarchyNode(
                        root,
                        0,
                        request,
                        context,
                        ref count,
                        ref result.Truncated);

                if (node !=
                    null)
                {
                    result.Roots.Add(
                        node);
                }
            }

            result.ObjectCount =
                count;

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


        private static EditorHierarchyNode BuildHierarchyNode(
            GameObject gameObject,
            int depth,
            GetHierarchyTreeArguments request,
            CapabilityContext context,
            ref int count,
            ref bool truncated)
        {
            if (gameObject ==
                    null ||
                (!request.IncludeInactive &&
                 !gameObject.activeInHierarchy))
            {
                return null;
            }

            if (count >=
                request.MaxObjects)
            {
                truncated =
                    true;

                return null;
            }

            context.CancellationToken
                .ThrowIfCancellationRequested();

            count++;

            EditorHierarchyNode node =
                new EditorHierarchyNode
                {
                    InstanceId =
                        gameObject.GetInstanceID(),

                    GlobalObjectId =
                        EditorCapabilityUtility
                            .GetGlobalObjectId(
                                gameObject),

                    Name =
                        gameObject.name,

                    HierarchyPath =
                        EditorCapabilityUtility
                            .GetHierarchyPath(
                                gameObject),

                    ScenePath =
                        EditorCapabilityUtility
                            .GetScenePath(
                                gameObject),

                    ActiveSelf =
                        gameObject.activeSelf,

                    ActiveInHierarchy =
                        gameObject.activeInHierarchy,

                    ChildCount =
                        gameObject.transform.childCount,

                    DepthLimited =
                        depth >=
                            request.MaxDepth &&
                        gameObject.transform.childCount >
                            0
                };

            if (request.IncludeComponents)
            {
                node.Components =
                    gameObject
                        .GetComponents<Component>()
                        .Select(
                            component =>
                                component ==
                                    null
                                    ? "[Missing Script]"
                                    : component
                                        .GetType()
                                        .FullName)
                        .ToList();
            }

            if (depth >=
                request.MaxDepth)
            {
                return node;
            }

            for (int childIndex = 0;
                 childIndex <
                 gameObject.transform.childCount;
                 childIndex++)
            {
                if (count >=
                    request.MaxObjects)
                {
                    truncated =
                        true;

                    break;
                }

                Transform child =
                    gameObject.transform.GetChild(
                        childIndex);

                EditorHierarchyNode childNode =
                    BuildHierarchyNode(
                        child.gameObject,
                        depth + 1,
                        request,
                        context,
                        ref count,
                        ref truncated);

                if (childNode !=
                    null)
                {
                    node.Children.Add(
                        childNode);
                }
            }

            return node;
        }


        internal static IEnumerable<Scene> EnumerateLoadedScenes(
            string requestedScene)
        {
            string normalizedRequest =
                EditorCapabilityUtility
                    .NormalizePath(
                        requestedScene);

            if (!string.IsNullOrWhiteSpace(
                    normalizedRequest))
            {
                Scene requested =
                    ResolveLoadedEditorScene(
                        requestedScene);

                if (!requested.IsValid())
                {
                    throw new InvalidOperationException(
                        $"Scene '{requestedScene}' is not loaded in the Unity Editor.");
                }

                yield return requested;
                yield break;
            }

            for (int index = 0;
                 index <
                 SceneManager.sceneCount;
                 index++)
            {
                Scene scene =
                    SceneManager.GetSceneAt(
                        index);

                if (!scene.IsValid() ||
                    !scene.isLoaded)
                {
                    continue;
                }

                yield return scene;
            }
        }


        internal static Scene ResolveLoadedEditorScene(
            string requestedScene)
        {
            if (string.IsNullOrWhiteSpace(
                    requestedScene))
            {
                return SceneManager.GetActiveScene();
            }

            string normalizedRequest =
                EditorCapabilityUtility.NormalizePath(
                    requestedScene);

            for (int index = 0;
                 index <
                 SceneManager.sceneCount;
                 index++)
            {
                Scene scene =
                    SceneManager.GetSceneAt(
                        index);

                if (MatchesScene(
                        scene,
                        normalizedRequest))
                {
                    return scene;
                }
            }

            foreach (
                GameObject gameObject
                in Resources.FindObjectsOfTypeAll<GameObject>())
            {
                if (gameObject ==
                    null)
                {
                    continue;
                }

                Scene scene =
                    gameObject.scene;

                if (!EditorSceneManager.IsPreviewScene(
                        scene) ||
                    !MatchesScene(
                        scene,
                        normalizedRequest))
                {
                    continue;
                }

                return scene;
            }

            return default;
        }


        private static bool MatchesScene(
            Scene scene,
            string normalizedRequest)
        {
            return
                scene.IsValid() &&
                scene.isLoaded &&
                (string.Equals(
                     normalizedRequest,
                     NormalizeScenePath(
                         scene),
                     StringComparison.OrdinalIgnoreCase) ||
                 string.Equals(
                     normalizedRequest,
                     scene.name,
                     StringComparison.OrdinalIgnoreCase));
        }


        internal static string NormalizeScenePath(
            Scene scene)
        {
            return EditorCapabilityUtility
                .NormalizePath(
                    string.IsNullOrWhiteSpace(
                        scene.path)
                        ? scene.name
                        : scene.path);
        }
    }


    [Serializable]
    public sealed class FindGameObjectsArguments
    {
        [Description("Optional object name or name fragment.")]
        public string Name { get; set; }

        [DefaultValue(false)]
        [Description("Require an exact name match instead of a case-insensitive substring match.")]
        public bool ExactName { get; set; }

        [Description("Optional component type name that each result must contain.")]
        public string ComponentType { get; set; }

        [Description("Optional exact Unity tag.")]
        public string Tag { get; set; }

        [Description("Optional exact Unity layer name.")]
        public string LayerName { get; set; }

        [Description("Optional loaded scene path or scene name.")]
        public string ScenePath { get; set; }

        [DefaultValue(false)]
        [Description("Require at least one missing script component.")]
        public bool RequireMissingScripts { get; set; }

        [DefaultValue(true)]
        [Description("Include inactive GameObjects.")]
        public bool IncludeInactive { get; set; } =
            true;

        [DefaultValue(100)]
        [CapabilityRange(1, 1000)]
        [Description("Maximum number of matches to return.")]
        public int MaxResults { get; set; } =
            100;
    }


    [Serializable]
    internal sealed class FindGameObjectsResult
    {
        public int MatchCount;

        public bool Truncated;

        public string RequestedLayerName;

        public bool LayerExists = true;

        public string Warning;

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

        public string ResponseInstruction =
            "Objects contains only live loaded GameObjects matching every supplied filter. Truncated=true means more matches exist.";
    }


    public sealed class FindGameObjectsCapability :
        CapabilityBase<FindGameObjectsArguments>, ICapabilityObservationSemantics
    {
        public CapabilityObservationBehavior ObservationBehavior => EditorApplication.isPlaying
            ? CapabilityObservationBehavior.Volatile : CapabilityObservationBehavior.StableUntilMutation;

        public override string Name =>
            "find_game_objects";


        public override string Description =>
            "Finds loaded scene GameObjects by name, component type, tag, layer, scene, inactivity, or missing-script state and returns identifiers for follow-up tools.";


        public override CapabilityPermission Permission =>
            CapabilityPermission.ReadOnly;


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

            FindGameObjectsArguments request =
                arguments ??
                new FindGameObjectsArguments();

            int requestedLayer =
                -1;

            if (!string.IsNullOrWhiteSpace(
                    request.LayerName))
            {
                requestedLayer =
                    LayerMask.NameToLayer(
                        request.LayerName.Trim());

                if (requestedLayer <
                    0)
                {
                    return Task.FromResult<object>(
                        new FindGameObjectsResult
                        {
                            MatchCount = 0,
                            RequestedLayerName = request.LayerName.Trim(),
                            LayerExists = false,
                            Warning =
                                $"Unity layer '{request.LayerName.Trim()}' does not exist. No objects can match every supplied filter. If this value was intended as a GameObject name, retry using the name filter instead of layerName."
                        });
                }
            }

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

            bool truncated =
                false;

            foreach (
                Scene scene
                in GetHierarchyTreeCapability
                    .EnumerateLoadedScenes(
                        request.ScenePath))
            {
                foreach (
                    GameObject root
                    in scene.GetRootGameObjects())
                {
                    Transform[] transforms =
                        root.GetComponentsInChildren<Transform>(
                            true);

                    foreach (
                        Transform transform
                        in transforms)
                    {
                        context.CancellationToken
                            .ThrowIfCancellationRequested();

                        GameObject gameObject =
                            transform.gameObject;

                        if (!request.IncludeInactive &&
                            !gameObject.activeInHierarchy)
                        {
                            continue;
                        }

                        if (!Matches(
                                gameObject,
                                request,
                                requestedLayer))
                        {
                            continue;
                        }

                        if (matches.Count >=
                            request.MaxResults)
                        {
                            truncated =
                                true;

                            break;
                        }

                        matches.Add(
                            gameObject);
                    }

                    if (truncated)
                        break;
                }

                if (truncated)
                    break;
            }

            List<EditorGameObjectSummary> summaries =
                matches
                    .OrderBy(
                        gameObject =>
                            EditorCapabilityUtility
                                .GetScenePath(
                                    gameObject),
                        StringComparer.Ordinal)
                    .ThenBy(
                        gameObject =>
                            EditorCapabilityUtility
                                .GetHierarchyPath(
                                    gameObject),
                        StringComparer.Ordinal)
                    .Select(
                        gameObject =>
                            InspectGameObjectCapability
                                .DescribeGameObject(
                                    gameObject,
                                    false))
                    .ToList();

            return Task.FromResult<object>(
                new FindGameObjectsResult
                {
                    MatchCount =
                        summaries.Count,

                    Truncated =
                        truncated,

                    RequestedLayerName =
                        string.IsNullOrWhiteSpace(request.LayerName)
                            ? string.Empty
                            : request.LayerName.Trim(),

                    LayerExists =
                        true,

                    Objects =
                        summaries
                });
        }


        private static bool Matches(
            GameObject gameObject,
            FindGameObjectsArguments request,
            int requestedLayer)
        {
            if (!string.IsNullOrWhiteSpace(
                    request.Name))
            {
                StringComparison comparison =
                    StringComparison.OrdinalIgnoreCase;

                bool nameMatches =
                    request.ExactName
                        ? string.Equals(
                            gameObject.name,
                            request.Name,
                            comparison)
                        : gameObject.name.IndexOf(
                              request.Name,
                              comparison) >=
                          0;

                if (!nameMatches)
                    return false;
            }

            if (!string.IsNullOrWhiteSpace(
                    request.Tag) &&
                !string.Equals(
                    gameObject.tag,
                    request.Tag,
                    StringComparison.Ordinal))
            {
                return false;
            }

            if (requestedLayer >=
                    0 &&
                gameObject.layer !=
                    requestedLayer)
            {
                return false;
            }

            Component[] components =
                null;

            if (!string.IsNullOrWhiteSpace(
                    request.ComponentType) ||
                request.RequireMissingScripts)
            {
                components =
                    gameObject.GetComponents<Component>();
            }

            if (!string.IsNullOrWhiteSpace(
                    request.ComponentType) &&
                !components.Any(
                    component =>
                        component !=
                            null &&
                        (string.Equals(
                             component.GetType().FullName,
                             request.ComponentType,
                             StringComparison.Ordinal) ||
                         string.Equals(
                             component.GetType().Name,
                             request.ComponentType,
                             StringComparison.Ordinal))))
            {
                return false;
            }

            if (request.RequireMissingScripts &&
                !components.Any(
                    component =>
                        component ==
                        null))
            {
                return false;
            }

            return true;
        }
    }


    [Serializable]
    public sealed class InspectSceneArguments
    {
        [Description("Optional scene asset path or loaded scene name. Omit to inspect the active scene.")]
        public string ScenePath { get; set; }

        [DefaultValue(true)]
        [Description("Include summaries of loaded root GameObjects.")]
        public bool IncludeRootObjects { get; set; } =
            true;
    }


    [Serializable]
    internal sealed class InspectSceneResult
    {
        public string RequestedPath;

        public string Name;

        public string Path;

        public string Guid;

        public bool Exists;

        public bool PathCorrected;

        public string Warning;

        public bool Loaded;

        public bool Active;

        public bool Dirty;

        public int BuildIndex;

        public int RootCount;

        public int GameObjectCount;

        public int ActiveGameObjectCount;

        public int InactiveGameObjectCount;

        public int MissingScriptCount;

        public List<EditorGameObjectSummary> RootObjects =
            new List<EditorGameObjectSummary>();

        public bool RootObjectsIncluded;

        public string ResponseInstruction =
            "When PathCorrected=true, use Path exactly for every later scene tool call. Loaded=false means only scene-asset metadata was inspected; call create_editor_scene with Path and openIfExists=true before authoring objects. Dirty=true means unsaved scene changes exist. RootObjectsIncluded=false means root summaries were intentionally omitted; an empty RootObjects array then does not mean the scene has no roots.";
    }


    public sealed class InspectSceneCapability :
        CapabilityBase<InspectSceneArguments>
    {
        public override string Name =>
            "inspect_scene";


        public override string Description =>
            "Inspects the active, loaded, or project scene: path, GUID, load and dirty state, Build Settings index, hierarchy counts, missing scripts, and optional root summaries.";


        public override CapabilityPermission Permission =>
            CapabilityPermission.ReadOnly;


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

            InspectSceneArguments request =
                arguments ??
                new InspectSceneArguments();

            string requestedPath =
                EditorCapabilityUtility
                    .NormalizePath(
                        request.ScenePath);

            string resolvedPath =
                EditorSceneAuthoringUtility
                    .ResolveUniqueExistingSceneAssetPath(
                        requestedPath) ??
                requestedPath;

            bool pathCorrected =
                !string.IsNullOrWhiteSpace(
                    requestedPath) &&
                !string.Equals(
                    requestedPath,
                    resolvedPath,
                    StringComparison.OrdinalIgnoreCase);

            Scene scene =
                ResolveScene(
                    resolvedPath);

            if (!scene.IsValid())
            {
                string assetPath =
                    EditorCapabilityUtility
                        .NormalizePath(
                            resolvedPath);

                SceneAsset sceneAsset =
                    AssetDatabase.LoadAssetAtPath<SceneAsset>(
                        assetPath);

                if (sceneAsset ==
                    null)
                {
                    throw new InvalidOperationException(
                        $"Scene '{request.ScenePath}' is neither loaded nor a valid scene asset.");
                }

                return Task.FromResult<object>(
                    new InspectSceneResult
                    {
                        RequestedPath =
                            requestedPath,

                        Name =
                            sceneAsset.name,

                        Path =
                            assetPath,

                        Guid =
                            AssetDatabase.AssetPathToGUID(
                                assetPath),

                        Exists =
                            true,

                        PathCorrected =
                            pathCorrected,

                        Warning =
                            pathCorrected
                                ? $"Requested scene path '{requestedPath}' did not exist. Resolved the unique matching scene asset '{assetPath}'."
                                : string.Empty,

                        Loaded =
                            false,

                        BuildIndex =
                            SceneUtility.GetBuildIndexByScenePath(
                                assetPath)
                    });
            }

            string scenePath =
                GetHierarchyTreeCapability
                    .NormalizeScenePath(
                        scene);

            InspectSceneResult result =
                new InspectSceneResult
                {
                    RequestedPath =
                        requestedPath,

                    Name =
                        scene.name,

                    Path =
                        scenePath,

                    Guid =
                        string.IsNullOrWhiteSpace(
                            scene.path)
                            ? string.Empty
                            : AssetDatabase.AssetPathToGUID(
                                scene.path),

                    Exists =
                        true,

                    PathCorrected =
                        pathCorrected,

                    Warning =
                        pathCorrected
                            ? $"Requested scene path '{requestedPath}' did not exist. Resolved the unique matching scene '{scenePath}'."
                            : string.Empty,

                    Loaded =
                        scene.isLoaded,

                    Active =
                        scene ==
                        SceneManager.GetActiveScene(),

                    Dirty =
                        scene.isDirty,

                    BuildIndex =
                        scene.buildIndex,

                    RootObjectsIncluded =
                        request.IncludeRootObjects
                };

            GameObject[] roots =
                scene.GetRootGameObjects();

            result.RootCount =
                roots.Length;

            foreach (
                GameObject root
                in roots)
            {
                context.CancellationToken
                    .ThrowIfCancellationRequested();

                Transform[] transforms =
                    root.GetComponentsInChildren<Transform>(
                        true);

                foreach (
                    Transform transform
                    in transforms)
                {
                    result.GameObjectCount++;

                    if (transform.gameObject.activeInHierarchy)
                    {
                        result.ActiveGameObjectCount++;
                    }
                    else
                    {
                        result.InactiveGameObjectCount++;
                    }

                    result.MissingScriptCount +=
                        GameObjectUtility
                            .GetMonoBehavioursWithMissingScriptCount(
                                transform.gameObject);
                }

                if (request.IncludeRootObjects)
                {
                    result.RootObjects.Add(
                        InspectGameObjectCapability
                            .DescribeGameObject(
                                root,
                                false));
                }
            }

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


        private static Scene ResolveScene(
            string requestedScene)
        {
            return GetHierarchyTreeCapability
                .ResolveLoadedEditorScene(
                    requestedScene);
        }
    }


    [Serializable]
    public sealed class InspectPrefabArguments :
        EditorObjectTargetArguments
    {
        [Description("Optional prefab asset path. When supplied, it takes precedence over object identifiers and selection.")]
        public string AssetPath { get; set; }

        [DefaultValue(true)]
        [Description("Include serialized property modifications for a prefab instance.")]
        public bool IncludePropertyOverrides { get; set; } =
            true;

        [DefaultValue(200)]
        [CapabilityRange(1, 1000)]
        [Description("Maximum property overrides to return.")]
        public int MaxOverrides { get; set; } =
            200;
    }


    [Serializable]
    internal sealed class EditorPrefabPropertyOverride
    {
        public string TargetName;

        public string TargetType;

        public string PropertyPath;

        public string Value;

        public int ObjectReferenceInstanceId;

        public string ObjectReferenceAssetPath;
    }


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

        public string GlobalObjectId;

        public string Name;

        public string AssetPath;

        public bool IsPartOfPrefabAsset;

        public bool IsPartOfPrefabInstance;

        public bool SourceMissing;

        public string PrefabAssetType;

        public string PrefabInstanceStatus;

        public int InstanceRootId;

        public string InstanceRootPath;

        public string SourceAssetPath;

        public bool HasOverrides;

        public int PropertyOverrideCount;

        public bool OverridesTruncated;

        public List<EditorPrefabPropertyOverride> PropertyOverrides =
            new List<EditorPrefabPropertyOverride>();

        public string ResponseInstruction =
            "PropertyOverrides describes scene-instance differences from the prefab source. Applying or reverting overrides requires a separate destructive capability; this inspection does not modify the prefab.";
    }


    public sealed class InspectPrefabCapability :
        CapabilityBase<InspectPrefabArguments>
    {
        public override string Name =>
            "inspect_prefab";


        public override string Description =>
            "Inspects a selected prefab asset or scene instance, including source path, asset and instance status, missing source, instance root, and bounded property overrides.";


        public override CapabilityPermission Permission =>
            CapabilityPermission.ReadOnly;


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

            InspectPrefabArguments request =
                arguments ??
                new InspectPrefabArguments();

            Object target;

            if (!string.IsNullOrWhiteSpace(
                    request.AssetPath))
            {
                target =
                    AssetDatabase.LoadMainAssetAtPath(
                        EditorCapabilityUtility
                            .NormalizePath(
                                request.AssetPath));

                if (target ==
                    null)
                {
                    throw new InvalidOperationException(
                        $"No prefab asset exists at '{request.AssetPath}'.");
                }
            }
            else
            {
                target =
                    EditorCapabilityUtility
                        .ResolveObject(
                            request.ObjectInstanceId,
                            request.GlobalObjectId);
            }

            Component component =
                target as Component;

            GameObject gameObject =
                target as GameObject;

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

            if (gameObject ==
                null)
            {
                throw new InvalidOperationException(
                    $"Unity object '{target.name}' is not a prefab GameObject or component.");
            }

            bool isAsset =
                PrefabUtility.IsPartOfPrefabAsset(
                    gameObject);

            bool isInstance =
                PrefabUtility.IsPartOfPrefabInstance(
                    gameObject);

            if (!isAsset &&
                !isInstance)
            {
                throw new InvalidOperationException(
                    $"GameObject '{gameObject.name}' is not part of a prefab asset or prefab instance.");
            }

            GameObject instanceRoot =
                isInstance
                    ? PrefabUtility
                        .GetOutermostPrefabInstanceRoot(
                            gameObject)
                    : null;

            string sourcePath =
                isAsset
                    ? AssetDatabase.GetAssetPath(
                        gameObject)
                    : PrefabUtility
                        .GetPrefabAssetPathOfNearestInstanceRoot(
                            gameObject);

            InspectPrefabResult result =
                new InspectPrefabResult
                {
                    ObjectInstanceId =
                        gameObject.GetInstanceID(),

                    GlobalObjectId =
                        EditorCapabilityUtility
                            .GetGlobalObjectId(
                                gameObject),

                    Name =
                        gameObject.name,

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

                    IsPartOfPrefabAsset =
                        isAsset,

                    IsPartOfPrefabInstance =
                        isInstance,

                    SourceMissing =
                        isInstance &&
                        PrefabUtility.IsPrefabAssetMissing(
                            gameObject),

                    PrefabAssetType =
                        PrefabUtility
                            .GetPrefabAssetType(
                                gameObject)
                            .ToString(),

                    PrefabInstanceStatus =
                        PrefabUtility
                            .GetPrefabInstanceStatus(
                                gameObject)
                            .ToString(),

                    InstanceRootId =
                        instanceRoot !=
                            null
                            ? instanceRoot.GetInstanceID()
                            : 0,

                    InstanceRootPath =
                        EditorCapabilityUtility
                            .GetHierarchyPath(
                                instanceRoot),

                    SourceAssetPath =
                        EditorCapabilityUtility
                            .NormalizePath(
                                sourcePath),

                    HasOverrides =
                        instanceRoot !=
                            null &&
                        PrefabUtility
                            .HasPrefabInstanceAnyOverrides(
                                instanceRoot,
                                false)
                };

            if (!request.IncludePropertyOverrides ||
                instanceRoot ==
                    null)
            {
                return Task.FromResult<object>(
                    result);
            }

            PropertyModification[] modifications =
                PrefabUtility.GetPropertyModifications(
                    instanceRoot) ??
                Array.Empty<PropertyModification>();

            result.PropertyOverrideCount =
                modifications.Length;

            result.OverridesTruncated =
                modifications.Length >
                request.MaxOverrides;

            foreach (
                PropertyModification modification
                in modifications.Take(
                    request.MaxOverrides))
            {
                Object referenced =
                    modification.objectReference;

                result.PropertyOverrides.Add(
                    new EditorPrefabPropertyOverride
                    {
                        TargetName =
                            modification.target !=
                                null
                                ? modification.target.name
                                : string.Empty,

                        TargetType =
                            modification.target !=
                                null
                                ? modification.target
                                    .GetType()
                                    .FullName
                                : string.Empty,

                        PropertyPath =
                            modification.propertyPath,

                        Value =
                            modification.value,

                        ObjectReferenceInstanceId =
                            referenced !=
                                null
                                ? referenced.GetInstanceID()
                                : 0,

                        ObjectReferenceAssetPath =
                            referenced !=
                                null
                                ? EditorCapabilityUtility
                                    .NormalizePath(
                                        AssetDatabase.GetAssetPath(
                                            referenced))
                                : string.Empty
                    });
            }

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