using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

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

using Newtonsoft.Json.Linq;

using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
using UnityEngine.SceneManagement;


namespace AIRuntime.Editor
{
    internal interface IKanbanRunIdentity
    {
        string RunId { get; }
    }


    [Serializable]
    public sealed class ListCapabilitiesArguments
    {
        [Description("Optional permission name: ReadOnly, Write, Destructive, Process, or Network.")]
        public string Permission { get; set; }
    }


    public sealed class ListCapabilitiesCapability :
        CapabilityBase<ListCapabilitiesArguments>
    {
        public override string Name => "list_capabilities";
        public override string Description =>
            "Lists every capability plugin currently installed for Kanban execution, including permission and semantic description.";
        public override CapabilityPermission Permission => CapabilityPermission.ReadOnly;

        protected override Task<object> ExecuteAsync(
            ListCapabilitiesArguments arguments,
            CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            string requested = arguments?.Permission?.Trim();

            var plugins = AIRuntimeEditorPluginCatalog
                .GetCapabilityPlugins(AIRuntimeEditorPluginHost.Kanban)
                .Where(plugin =>
                    string.IsNullOrWhiteSpace(requested) ||
                    string.Equals(plugin.Permission.ToString(), requested,
                        StringComparison.OrdinalIgnoreCase))
                .Select(plugin =>
                {
                    ICapability capability = plugin.CreateCapability();
                    ICapabilityAuthorizationSemantics authorization =
                        capability as ICapabilityAuthorizationSemantics;
                    return new
                    {
                        plugin.Name,
                        plugin.Label,
                        Permission = plugin.Permission.ToString(),
                        Description = capability.Description,
                        RequiredPermissions = authorization?.RequiredPermissions?
                            .Select(value => value.ToString()).ToArray() ??
                            new[] { capability.Permission.ToString() },
                        EffectScopes = authorization?.EffectScopes?.ToArray() ??
                            Array.Empty<string>()
                    };
                })
                .ToList();

            return Task.FromResult<object>(new
            {
                Count = plugins.Count,
                Capabilities = plugins,
                ResponseInstruction =
                    "Choose the closest semantic capability. If none can perform the required operation, call request_capability with a complete contract."
            });
        }
    }


    [Serializable]
    public sealed class RequestCapabilityArguments
    {
        [Required] public string Name { get; set; }
        [Required] public string Purpose { get; set; }
        [Description("Required inputs and their authoritative sources.")]
        public List<string> Inputs { get; set; } = new List<string>();
        [Description("Expected persistent outputs or observable state changes.")]
        public List<string> Outputs { get; set; } = new List<string>();
        [Required]
        [Description("ReadOnly, Write, Destructive, Process, or Network.")]
        public string PermissionClass { get; set; }
        [Required] public string VerificationMethod { get; set; }
        [Description("Why no installed capability can perform the operation safely.")]
        public string ExistingCapabilityGap { get; set; }
    }


    public sealed class RequestCapabilityCapability :
        CapabilityBase<RequestCapabilityArguments>
    {
        public override string Name => "request_capability";
        public override string Description =>
            "Creates a structured missing-capability contract for the current Kanban card. It does not mutate product state.";
        public override CapabilityPermission Permission => CapabilityPermission.ReadOnly;

        protected override Task<object> ExecuteAsync(
            RequestCapabilityArguments arguments,
            CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (arguments == null || string.IsNullOrWhiteSpace(arguments.Name) ||
                string.IsNullOrWhiteSpace(arguments.Purpose) ||
                string.IsNullOrWhiteSpace(arguments.PermissionClass) ||
                string.IsNullOrWhiteSpace(arguments.VerificationMethod))
            {
                throw new ArgumentException(
                    "Name, Purpose, PermissionClass, and VerificationMethod are required.");
            }

            if (!Enum.TryParse(arguments.PermissionClass, true,
                    out CapabilityPermission permission))
            {
                throw new ArgumentException(
                    "PermissionClass must be ReadOnly, Write, Destructive, Process, or Network.");
            }

            string contract =
                $"name={arguments.Name.Trim()}; purpose={arguments.Purpose.Trim()}; " +
                $"permission={permission}; inputs={string.Join(" | ", arguments.Inputs ?? new List<string>())}; " +
                $"outputs={string.Join(" | ", arguments.Outputs ?? new List<string>())}; " +
                $"verification={arguments.VerificationMethod.Trim()}; " +
                $"gap={(arguments.ExistingCapabilityGap ?? string.Empty).Trim()}";

            return Task.FromResult<object>(new
            {
                Succeeded = true,
                CapabilityRequest = contract,
                ResponseInstruction =
                    "Stop product mutations and return exactly [CapabilityRequest] followed by CapabilityRequest. AIRuntime will durably pause this card."
            });
        }
    }


    [Serializable]
    public sealed class UpdateGameObjectArguments
    {
        [Description("Exact live GameObject instance ID.")]
        public int GameObjectInstanceId { get; set; }
        public string GlobalObjectId { get; set; }
        public string NewName { get; set; }
        [DefaultValue(false)] public bool ChangeParent { get; set; }
        [Description("New parent instance ID. Use zero with ChangeParent=true to make the object a root.")]
        public int ParentGameObjectInstanceId { get; set; }
        [DefaultValue(-1)] public int SiblingIndex { get; set; } = -1;
        [DefaultValue(false)] public bool SetActive { get; set; }
        [DefaultValue(true)] public bool Active { get; set; } = true;
        public string Tag { get; set; }
        public string LayerName { get; set; }
        [DefaultValue(false)] public bool SetStaticFlags { get; set; }
        public int StaticFlags { get; set; }
        [DefaultValue(true)] public bool SaveScene { get; set; } = true;
    }


    public sealed class UpdateGameObjectCapability :
        CapabilityBase<UpdateGameObjectArguments>
    {
        public override string Name => "update_game_object";
        public override string Description =>
            "Renames, reparents, reorders, activates, tags, layers, or changes static flags on an exact live GameObject, with Undo and scene persistence.";
        public override CapabilityPermission Permission => CapabilityPermission.Write;

        protected override Task<object> ExecuteAsync(
            UpdateGameObjectArguments arguments,
            CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (arguments == null) throw new ArgumentNullException(nameof(arguments));
            GameObject target = EditorCapabilityUtility.ResolveGameObject(
                arguments.GameObjectInstanceId, arguments.GlobalObjectId, true);
            if (arguments.SaveScene)
                EditorSceneAuthoringUtility.EnsureSceneCanBeSavedNonInteractively(target.scene);
            Undo.RecordObject(target, "Update GameObject");
            Undo.RecordObject(target.transform, "Update GameObject hierarchy");

            if (!string.IsNullOrWhiteSpace(arguments.NewName))
                target.name = arguments.NewName.Trim();

            if (arguments.ChangeParent)
            {
                Transform parent = null;
                if (arguments.ParentGameObjectInstanceId != 0)
                {
                    GameObject parentObject = EditorCapabilityUtility.ResolveGameObject(
                        arguments.ParentGameObjectInstanceId, null, false);
                    if (parentObject == target || parentObject.transform.IsChildOf(target.transform))
                        throw new InvalidOperationException("The requested parent would create a hierarchy cycle.");
                    if (parentObject.scene != target.scene)
                        throw new InvalidOperationException("Parent and child must belong to the same scene.");
                    parent = parentObject.transform;
                }
                Undo.SetTransformParent(target.transform, parent, "Reparent GameObject");
            }

            if (arguments.SiblingIndex >= 0)
                target.transform.SetSiblingIndex(arguments.SiblingIndex);

            if (arguments.SetActive)
                target.SetActive(arguments.Active);

            if (!string.IsNullOrWhiteSpace(arguments.Tag))
            {
                string tag = arguments.Tag.Trim();
                if (!ExtendedCapabilityUtility.GetProjectTags().Contains(tag))
                    throw new InvalidOperationException($"Unity tag '{tag}' does not exist. Call configure_project_settings first.");
                target.tag = tag;
            }

            if (!string.IsNullOrWhiteSpace(arguments.LayerName))
            {
                int layer = LayerMask.NameToLayer(arguments.LayerName.Trim());
                if (layer < 0)
                    throw new InvalidOperationException($"Unity layer '{arguments.LayerName.Trim()}' does not exist. Call configure_project_settings first.");
                target.layer = layer;
            }

            if (arguments.SetStaticFlags)
                GameObjectUtility.SetStaticEditorFlags(target,
                    (StaticEditorFlags)arguments.StaticFlags);

            EditorSceneManager.MarkSceneDirty(target.scene);
            bool saved = !arguments.SaveScene || EditorSceneManager.SaveScene(target.scene);

            return Task.FromResult<object>(new
            {
                Succeeded = true,
                GameObjectInstanceId = target.GetInstanceID(),
                Name = target.name,
                HierarchyPath = EditorCapabilityUtility.GetHierarchyPath(target),
                ParentInstanceId = target.transform.parent != null
                    ? target.transform.parent.gameObject.GetInstanceID() : 0,
                target.activeSelf,
                target.tag,
                Layer = LayerMask.LayerToName(target.layer),
                StaticFlags = (int)GameObjectUtility.GetStaticEditorFlags(target),
                SceneSaved = saved
            });
        }
    }


    [Serializable]
    public sealed class DuplicateGameObjectArguments
    {
        public int GameObjectInstanceId { get; set; }
        public string GlobalObjectId { get; set; }
        public string NewName { get; set; }
        public int ParentGameObjectInstanceId { get; set; }
        [DefaultValue(true)] public bool PreserveParent { get; set; } = true;
        [DefaultValue(true)] public bool SaveScene { get; set; } = true;
    }


    public sealed class DuplicateGameObjectCapability :
        CapabilityBase<DuplicateGameObjectArguments>
    {
        public override string Name => "duplicate_game_object";
        public override string Description =>
            "Duplicates an exact GameObject hierarchy through Unity object cloning, optionally under another parent, and returns the new identity.";
        public override CapabilityPermission Permission => CapabilityPermission.Write;

        protected override Task<object> ExecuteAsync(DuplicateGameObjectArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (arguments == null) throw new ArgumentNullException(nameof(arguments));
            GameObject source = EditorCapabilityUtility.ResolveGameObject(
                arguments.GameObjectInstanceId, arguments.GlobalObjectId, true);
            if (arguments.SaveScene)
                EditorSceneAuthoringUtility.EnsureSceneCanBeSavedNonInteractively(source.scene);
            GameObject clone = UnityEngine.Object.Instantiate(source);
            clone.name = string.IsNullOrWhiteSpace(arguments.NewName)
                ? source.name : arguments.NewName.Trim();
            SceneManager.MoveGameObjectToScene(clone, source.scene);
            Transform parent = arguments.PreserveParent ? source.transform.parent : null;
            if (arguments.ParentGameObjectInstanceId != 0)
                parent = EditorCapabilityUtility.ResolveGameObject(
                    arguments.ParentGameObjectInstanceId, null, false).transform;
            clone.transform.SetParent(parent, true);
            Undo.RegisterCreatedObjectUndo(clone, "Duplicate GameObject");
            EditorSceneManager.MarkSceneDirty(source.scene);
            bool saved = !arguments.SaveScene || EditorSceneManager.SaveScene(source.scene);
            return Task.FromResult<object>(new
            {
                Succeeded = true,
                SourceInstanceId = source.GetInstanceID(),
                GameObjectInstanceId = clone.GetInstanceID(),
                GlobalObjectId = EditorCapabilityUtility.GetGlobalObjectId(clone),
                clone.name,
                HierarchyPath = EditorCapabilityUtility.GetHierarchyPath(clone),
                SceneSaved = saved
            });
        }
    }


    [Serializable]
    public sealed class DestroyGameObjectArguments
    {
        public int GameObjectInstanceId { get; set; }
        public string GlobalObjectId { get; set; }
        [Required]
        [Description("Exact current hierarchy path used as a destructive confirmation.")]
        public string ExpectedHierarchyPath { get; set; }
        [DefaultValue(true)] public bool SaveScene { get; set; } = true;
    }


    public sealed class DestroyGameObjectCapability :
        CapabilityBase<DestroyGameObjectArguments>
    {
        public override string Name => "destroy_game_object";
        public override string Description =>
            "Deletes one exact GameObject hierarchy after verifying its current hierarchy path. The operation is Undo-backed and destructive.";
        public override CapabilityPermission Permission => CapabilityPermission.Destructive;

        protected override Task<object> ExecuteAsync(DestroyGameObjectArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (arguments == null) throw new ArgumentNullException(nameof(arguments));
            GameObject target = EditorCapabilityUtility.ResolveGameObject(
                arguments.GameObjectInstanceId, arguments.GlobalObjectId, false);
            string path = EditorCapabilityUtility.GetHierarchyPath(target);
            if (!string.Equals(path, arguments.ExpectedHierarchyPath?.Trim(), StringComparison.Ordinal))
                throw new InvalidOperationException($"Hierarchy confirmation mismatch. Current path is '{path}'.");
            Scene scene = target.scene;
            if (arguments.SaveScene)
                EditorSceneAuthoringUtility.EnsureSceneCanBeSavedNonInteractively(scene);
            int id = target.GetInstanceID();
            Undo.DestroyObjectImmediate(target);
            bool saved = !arguments.SaveScene || EditorSceneManager.SaveScene(scene);
            return Task.FromResult<object>(new
            {
                Succeeded = true,
                DeletedInstanceId = id,
                DeletedHierarchyPath = path,
                ScenePath = scene.path.Replace('\\', '/'),
                SceneSaved = saved
            });
        }
    }


    [Serializable]
    public sealed class InstantiatePrefabArguments
    {
        [Required] public string AssetPath { get; set; }
        [Description("Optional loaded target scene path or name.")]
        public string ScenePath { get; set; }
        public int ParentGameObjectInstanceId { get; set; }
        public string Name { get; set; }
        public EditorVector3Arguments Position { get; set; }
        public EditorVector3Arguments Rotation { get; set; }
        public EditorVector3Arguments Scale { get; set; }
        [DefaultValue(true)] public bool SaveScene { get; set; } = true;
    }


    public sealed class InstantiatePrefabCapability :
        CapabilityBase<InstantiatePrefabArguments>
    {
        public override string Name => "instantiate_prefab";
        public override string Description =>
            "Instantiates a real prefab asset into an exact loaded scene, optionally parents and transforms it, and returns its persistent instance identity.";
        public override CapabilityPermission Permission => CapabilityPermission.Write;

        protected override Task<object> ExecuteAsync(InstantiatePrefabArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (arguments == null) throw new ArgumentNullException(nameof(arguments));
            string path = ExtendedCapabilityUtility.ValidateAssetPath(arguments.AssetPath, ".prefab");
            GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
            if (prefab == null || !PrefabUtility.IsPartOfPrefabAsset(prefab))
                throw new InvalidOperationException($"No valid prefab exists at '{path}'.");
            Scene scene = EditorSceneAuthoringUtility.ResolveLoadedScene(arguments.ScenePath);
            if (arguments.SaveScene)
                EditorSceneAuthoringUtility.EnsureSceneCanBeSavedNonInteractively(scene);
            GameObject instance = PrefabUtility.InstantiatePrefab(prefab, scene) as GameObject;
            if (instance == null) throw new InvalidOperationException("Unity could not instantiate the prefab.");
            Undo.RegisterCreatedObjectUndo(instance, "Instantiate Prefab");
            if (!string.IsNullOrWhiteSpace(arguments.Name)) instance.name = arguments.Name.Trim();
            if (arguments.ParentGameObjectInstanceId != 0)
            {
                GameObject parent = EditorCapabilityUtility.ResolveGameObject(
                    arguments.ParentGameObjectInstanceId, null, false);
                if (parent.scene != scene) throw new InvalidOperationException("Parent belongs to another scene.");
                instance.transform.SetParent(parent.transform, false);
            }
            instance.transform.localPosition = arguments.Position?.ToVector3() ?? Vector3.zero;
            instance.transform.localEulerAngles = arguments.Rotation?.ToVector3() ?? Vector3.zero;
            instance.transform.localScale = arguments.Scale?.ToVector3() ?? Vector3.one;
            EditorSceneManager.MarkSceneDirty(scene);
            bool saved = !arguments.SaveScene || EditorSceneManager.SaveScene(scene);
            return Task.FromResult<object>(new
            {
                Succeeded = true,
                AssetPath = path,
                GameObjectInstanceId = instance.GetInstanceID(),
                GlobalObjectId = EditorCapabilityUtility.GetGlobalObjectId(instance),
                instance.name,
                HierarchyPath = EditorCapabilityUtility.GetHierarchyPath(instance),
                ScenePath = scene.path.Replace('\\', '/'),
                SceneSaved = saved
            });
        }
    }


    [Serializable]
    public sealed class ConfigureProjectSettingsArguments
    {
        public List<string> EnsureTags { get; set; } = new List<string>();
        public List<string> EnsureLayers { get; set; } = new List<string>();
        public List<string> EnsureSortingLayers { get; set; } = new List<string>();
        [Description("Exact Assets/.../.unity paths to enable in Build Settings.")]
        public List<string> BuildScenePaths { get; set; } = new List<string>();
        [DefaultValue(false)]
        [Description("Replace the current Build Settings scene list instead of merging the supplied scenes.")]
        public bool ReplaceBuildScenes { get; set; }
    }


    public sealed class ConfigureProjectSettingsCapability :
        CapabilityBase<ConfigureProjectSettingsArguments>
    {
        public override string Name => "configure_project_settings";
        public override string Description =>
            "Idempotently creates tags, layers, sorting layers, and configures Editor Build Settings scenes through Unity project APIs.";
        public override CapabilityPermission Permission => CapabilityPermission.Write;

        protected override Task<object> ExecuteAsync(ConfigureProjectSettingsArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            arguments = arguments ?? new ConfigureProjectSettingsArguments();
            UnityEngine.Object tagManager = AssetDatabase.LoadAllAssetsAtPath(
                "ProjectSettings/TagManager.asset").FirstOrDefault();
            if (tagManager == null) throw new InvalidOperationException("Unity TagManager asset could not be loaded.");
            SerializedObject serialized = new SerializedObject(tagManager);
            SerializedProperty tags = serialized.FindProperty("tags");
            SerializedProperty layers = serialized.FindProperty("layers");
            SerializedProperty sortingLayers = serialized.FindProperty("m_SortingLayers");
            List<string> addedTags = new List<string>();
            List<string> addedLayers = new List<string>();
            List<string> addedSortingLayers = new List<string>();

            foreach (string raw in ExtendedCapabilityUtility.CleanNames(arguments.EnsureTags))
            {
                if (Enumerable.Range(0, tags.arraySize).Any(i => tags.GetArrayElementAtIndex(i).stringValue == raw)) continue;
                tags.InsertArrayElementAtIndex(tags.arraySize);
                tags.GetArrayElementAtIndex(tags.arraySize - 1).stringValue = raw;
                addedTags.Add(raw);
            }

            foreach (string raw in ExtendedCapabilityUtility.CleanNames(arguments.EnsureLayers))
            {
                bool exists = Enumerable.Range(0, layers.arraySize)
                    .Any(i => layers.GetArrayElementAtIndex(i).stringValue == raw);
                if (exists) continue;
                int slot = Enumerable.Range(8, Math.Max(0, layers.arraySize - 8))
                    .FirstOrDefault(i => string.IsNullOrEmpty(layers.GetArrayElementAtIndex(i).stringValue));
                if (slot < 8) throw new InvalidOperationException($"No free user layer slot exists for '{raw}'.");
                layers.GetArrayElementAtIndex(slot).stringValue = raw;
                addedLayers.Add(raw);
            }

            foreach (string raw in ExtendedCapabilityUtility.CleanNames(arguments.EnsureSortingLayers))
            {
                bool exists = Enumerable.Range(0, sortingLayers.arraySize)
                    .Any(i => sortingLayers.GetArrayElementAtIndex(i)
                        .FindPropertyRelative("name").stringValue == raw);
                if (exists) continue;
                sortingLayers.InsertArrayElementAtIndex(sortingLayers.arraySize);
                SerializedProperty entry = sortingLayers.GetArrayElementAtIndex(sortingLayers.arraySize - 1);
                entry.FindPropertyRelative("name").stringValue = raw;
                SerializedProperty unique = entry.FindPropertyRelative("uniqueID");
                if (unique != null) unique.intValue = ExtendedCapabilityUtility.StablePositiveId(raw);
                addedSortingLayers.Add(raw);
            }

            serialized.ApplyModifiedPropertiesWithoutUndo();
            AssetDatabase.SaveAssets();

            List<EditorBuildSettingsScene> buildScenes = arguments.ReplaceBuildScenes
                ? new List<EditorBuildSettingsScene>()
                : EditorBuildSettings.scenes.ToList();
            foreach (string raw in arguments.BuildScenePaths ?? new List<string>())
            {
                string path = ExtendedCapabilityUtility.ValidateAssetPath(raw, ".unity");
                if (AssetDatabase.LoadAssetAtPath<SceneAsset>(path) == null)
                    throw new InvalidOperationException($"Build scene does not exist: '{path}'.");
                if (buildScenes.All(scene => !string.Equals(scene.path, path, StringComparison.OrdinalIgnoreCase)))
                    buildScenes.Add(new EditorBuildSettingsScene(path, true));
            }
            if ((arguments.BuildScenePaths ?? new List<string>()).Count > 0 || arguments.ReplaceBuildScenes)
                EditorBuildSettings.scenes = buildScenes.ToArray();

            return Task.FromResult<object>(new
            {
                Succeeded = true,
                AddedTags = addedTags,
                AddedLayers = addedLayers,
                AddedSortingLayers = addedSortingLayers,
                Tags = ExtendedCapabilityUtility.GetProjectTags(),
                Layers = Enumerable.Range(0, 32).Select(LayerMask.LayerToName).Where(name => !string.IsNullOrEmpty(name)).ToArray(),
                SortingLayers = SortingLayer.layers.Select(layer => layer.name).ToArray(),
                BuildScenes = EditorBuildSettings.scenes.Select(scene => new { scene.path, scene.enabled }).ToArray()
            });
        }
    }


    [Serializable]
    public sealed class SearchWorkspaceArguments
    {
        [DefaultValue("Assets")] public string Path { get; set; } = "Assets";
        [Required] public string Pattern { get; set; }
        [Description("File globs such as *.cs or *.asmdef.")]
        public List<string> FileGlobs { get; set; } = new List<string>();
        [DefaultValue(false)] public bool UseRegex { get; set; }
        [DefaultValue(false)] public bool CaseSensitive { get; set; }
        [DefaultValue(200)] public int MaxMatches { get; set; } = 200;
    }


    public sealed class SearchWorkspaceCapability :
        CapabilityBase<SearchWorkspaceArguments>
    {
        public override string Name => "search_workspace";
        public override string Description =>
            "Searches contained workspace text files recursively with optional globs, exact line numbers, bounded matches, and literal or regular-expression matching.";
        public override CapabilityPermission Permission => CapabilityPermission.ReadOnly;

        protected override Task<object> ExecuteAsync(SearchWorkspaceArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (arguments == null || string.IsNullOrWhiteSpace(arguments.Pattern))
                throw new ArgumentException("Pattern is required.");
            string root = ExtendedCapabilityUtility.ResolveWorkspacePath(arguments.Path, true);
            int max = Math.Max(1, Math.Min(5000, arguments.MaxMatches));
            string[] globs = (arguments.FileGlobs ?? new List<string>())
                .Where(value => !string.IsNullOrWhiteSpace(value)).ToArray();
            Regex matcher = arguments.UseRegex
                ? new Regex(arguments.Pattern, arguments.CaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase)
                : null;
            StringComparison comparison = arguments.CaseSensitive
                ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
            var matches = new List<object>();
            bool truncated = false;

            foreach (string file in Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories))
            {
                context.CancellationToken.ThrowIfCancellationRequested();
                if (globs.Length > 0 && !globs.Any(glob => ExtendedCapabilityUtility.GlobMatches(Path.GetFileName(file), glob))) continue;
                string[] lines;
                try { lines = File.ReadAllLines(file); }
                catch (IOException) { continue; }
                catch (UnauthorizedAccessException) { continue; }
                for (int index = 0; index < lines.Length; index++)
                {
                    bool found = matcher != null ? matcher.IsMatch(lines[index]) :
                        lines[index].IndexOf(arguments.Pattern, comparison) >= 0;
                    if (!found) continue;
                    if (matches.Count >= max) { truncated = true; break; }
                    matches.Add(new
                    {
                        Path = ExtendedCapabilityUtility.ToWorkspaceRelative(file),
                        Line = index + 1,
                        Text = lines[index]
                    });
                }
                if (truncated) break;
            }

            return Task.FromResult<object>(new
            {
                MatchCount = matches.Count,
                Truncated = truncated,
                Matches = matches
            });
        }
    }


    [Serializable]
    public sealed class ReplaceTextArguments
    {
        [Required] public string Path { get; set; }
        [Required] public string OldText { get; set; }
        [Required] public string NewText { get; set; }
        [DefaultValue(1)] public int ExpectedOccurrences { get; set; } = 1;
        [Description("Optional lowercase or uppercase SHA-256 of the complete current file.")]
        public string ExpectedSha256 { get; set; }
    }


    public sealed class ReplaceTextCapability :
        CapabilityBase<ReplaceTextArguments>
    {
        public override string Name => "replace_text";
        public override string Description =>
            "Atomically replaces an exact text fragment in a contained workspace file with occurrence-count and optional SHA-256 drift protection.";
        public override CapabilityPermission Permission => CapabilityPermission.Write;

        protected override Task<object> ExecuteAsync(ReplaceTextArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (arguments == null || string.IsNullOrEmpty(arguments.OldText) || arguments.NewText == null)
                throw new ArgumentException("Path, non-empty OldText, and NewText are required.");
            string full = ExtendedCapabilityUtility.ResolveWorkspacePath(arguments.Path, false);
            string content = File.ReadAllText(full, Encoding.UTF8);
            string beforeHash = ExtendedCapabilityUtility.Sha256(content);
            if (!string.IsNullOrWhiteSpace(arguments.ExpectedSha256) &&
                !string.Equals(beforeHash, arguments.ExpectedSha256.Trim(), StringComparison.OrdinalIgnoreCase))
                throw new InvalidOperationException("File hash changed; replacement was not applied.");
            int occurrences = ExtendedCapabilityUtility.CountOccurrences(content, arguments.OldText);
            if (occurrences != arguments.ExpectedOccurrences)
                throw new InvalidOperationException(
                    $"Expected {arguments.ExpectedOccurrences} occurrence(s), found {occurrences}; replacement was not applied.");
            string updated = content.Replace(arguments.OldText, arguments.NewText);
            string temporary = full + ".airuntime-" + Guid.NewGuid().ToString("N") + ".tmp";
            try
            {
                File.WriteAllText(temporary, updated, new UTF8Encoding(false));
                File.Copy(temporary, full, true);
            }
            finally
            {
                if (File.Exists(temporary)) File.Delete(temporary);
            }
            return Task.FromResult<object>(new
            {
                Succeeded = true,
                Path = ExtendedCapabilityUtility.ToWorkspaceRelative(full),
                Replacements = occurrences,
                PreviousSha256 = beforeHash,
                CurrentSha256 = ExtendedCapabilityUtility.Sha256(updated),
                PreviousLength = content.Length,
                CurrentLength = updated.Length
            });
        }
    }


    [Serializable]
    public sealed class OpenEditorSceneArguments
    {
        [Required] public string ScenePath { get; set; }
        [DefaultValue(true)] public bool Additive { get; set; } = true;
        [DefaultValue(true)] public bool SetActive { get; set; } = true;
    }


    public sealed class OpenEditorSceneCapability :
        CapabilityBase<OpenEditorSceneArguments>
    {
        public override string Name => "open_editor_scene";
        public override string Description =>
            "Opens an existing Unity scene asset additively or singly and optionally makes it active, returning exact loaded state.";
        public override CapabilityPermission Permission => CapabilityPermission.Write;

        protected override Task<object> ExecuteAsync(OpenEditorSceneArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (arguments == null) throw new ArgumentNullException(nameof(arguments));
            string path = ExtendedCapabilityUtility.ValidateAssetPath(arguments.ScenePath, ".unity");
            if (AssetDatabase.LoadAssetAtPath<SceneAsset>(path) == null)
                throw new InvalidOperationException($"Scene asset does not exist: '{path}'.");
            Scene scene = EditorSceneManager.OpenScene(path,
                arguments.Additive ? OpenSceneMode.Additive : OpenSceneMode.Single);
            bool active = !arguments.SetActive || SceneManager.SetActiveScene(scene);
            return Task.FromResult<object>(new
            {
                Succeeded = true,
                ScenePath = scene.path.Replace('\\', '/'),
                scene.name,
                scene.isLoaded,
                Active = scene == SceneManager.GetActiveScene(),
                ActiveRequestSucceeded = active,
                RootCount = scene.GetRootGameObjects().Length
            });
        }
    }


    [Serializable]
    public sealed class CloseEditorSceneArguments
    {
        [Required] public string ScenePath { get; set; }
        [DefaultValue(true)] public bool SaveChanges { get; set; } = true;
        [Required]
        [Description("Must equal the current scene path. This confirms the destructive close target.")]
        public string ExpectedScenePath { get; set; }
    }


    public sealed class CloseEditorSceneCapability :
        CapabilityBase<CloseEditorSceneArguments>
    {
        public override string Name => "close_editor_scene";
        public override string Description =>
            "Closes one exact loaded scene after path confirmation, optionally saving first. Discarding dirty state is destructive.";
        public override CapabilityPermission Permission => CapabilityPermission.Destructive;

        protected override Task<object> ExecuteAsync(CloseEditorSceneArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (arguments == null) throw new ArgumentNullException(nameof(arguments));
            string path = ExtendedCapabilityUtility.ValidateAssetPath(arguments.ScenePath, ".unity");
            if (!string.Equals(path, arguments.ExpectedScenePath?.Trim().Replace('\\', '/'), StringComparison.Ordinal))
                throw new InvalidOperationException("ExpectedScenePath does not confirm ScenePath.");
            Scene scene = SceneManager.GetSceneByPath(path);
            if (!scene.IsValid() || !scene.isLoaded)
                throw new InvalidOperationException($"Scene is not loaded: '{path}'.");
            bool wasDirty = scene.isDirty;
            if (arguments.SaveChanges && wasDirty)
                EditorSceneAuthoringUtility.EnsureSceneCanBeSavedNonInteractively(scene);
            if (arguments.SaveChanges && wasDirty && !EditorSceneManager.SaveScene(scene))
                throw new InvalidOperationException($"Unity could not save scene '{path}' before closing it.");
            bool closed = EditorSceneManager.CloseScene(scene, true);
            return Task.FromResult<object>(new
            {
                Succeeded = closed,
                ScenePath = path,
                WasDirty = wasDirty,
                SavedBeforeClose = arguments.SaveChanges && wasDirty,
                DiscardedChanges = !arguments.SaveChanges && wasDirty
            });
        }
    }


    [Serializable]
    public sealed class ApplyPrefabOverridesArguments
    {
        public int GameObjectInstanceId { get; set; }
        public string GlobalObjectId { get; set; }
        [DefaultValue(true)] public bool SaveScene { get; set; } = true;
    }


    public sealed class ApplyPrefabOverridesCapability :
        CapabilityBase<ApplyPrefabOverridesArguments>
    {
        public override string Name => "apply_prefab_overrides";
        public override string Description =>
            "Applies all overrides from an exact prefab instance root to its source prefab and verifies that no overrides remain.";
        public override CapabilityPermission Permission => CapabilityPermission.Write;

        protected override Task<object> ExecuteAsync(ApplyPrefabOverridesArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (arguments == null) throw new ArgumentNullException(nameof(arguments));
            GameObject target = EditorCapabilityUtility.ResolveGameObject(
                arguments.GameObjectInstanceId, arguments.GlobalObjectId, true);
            GameObject root = PrefabUtility.GetOutermostPrefabInstanceRoot(target);
            if (root == null) throw new InvalidOperationException("Target is not part of a prefab instance.");
            if (arguments.SaveScene)
                EditorSceneAuthoringUtility.EnsureSceneCanBeSavedNonInteractively(root.scene);
            string source = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(root);
            PrefabUtility.ApplyPrefabInstance(root, InteractionMode.AutomatedAction);
            AssetDatabase.SaveAssets();
            bool sceneSaved = !arguments.SaveScene || EditorSceneManager.SaveScene(root.scene);
            return Task.FromResult<object>(new
            {
                Succeeded = true,
                InstanceRootId = root.GetInstanceID(),
                SourceAssetPath = source,
                HasOverrides = PrefabUtility.HasPrefabInstanceAnyOverrides(root, false),
                SceneSaved = sceneSaved
            });
        }
    }


    [Serializable]
    public sealed class RevertOrUnpackPrefabArguments
    {
        public int GameObjectInstanceId { get; set; }
        public string GlobalObjectId { get; set; }
        [Required]
        [Description("Revert, UnpackOutermost, or UnpackCompletely.")]
        public string Action { get; set; }
        [Required]
        [Description("Exact current prefab instance root hierarchy path.")]
        public string ExpectedHierarchyPath { get; set; }
        [DefaultValue(true)] public bool SaveScene { get; set; } = true;
    }


    public sealed class RevertOrUnpackPrefabCapability :
        CapabilityBase<RevertOrUnpackPrefabArguments>
    {
        public override string Name => "revert_or_unpack_prefab";
        public override string Description =>
            "Reverts all prefab overrides or unpacks an exact prefab instance after hierarchy confirmation. This can discard relationships or changes.";
        public override CapabilityPermission Permission => CapabilityPermission.Destructive;

        protected override Task<object> ExecuteAsync(RevertOrUnpackPrefabArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (arguments == null) throw new ArgumentNullException(nameof(arguments));
            GameObject target = EditorCapabilityUtility.ResolveGameObject(
                arguments.GameObjectInstanceId, arguments.GlobalObjectId, false);
            GameObject root = PrefabUtility.GetOutermostPrefabInstanceRoot(target);
            if (root == null) throw new InvalidOperationException("Target is not part of a prefab instance.");
            if (arguments.SaveScene)
                EditorSceneAuthoringUtility.EnsureSceneCanBeSavedNonInteractively(root.scene);
            string hierarchy = EditorCapabilityUtility.GetHierarchyPath(root);
            if (!string.Equals(hierarchy, arguments.ExpectedHierarchyPath?.Trim(), StringComparison.Ordinal))
                throw new InvalidOperationException($"Hierarchy confirmation mismatch. Current path is '{hierarchy}'.");
            string source = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(root);
            string action = arguments.Action?.Trim();
            if (string.Equals(action, "Revert", StringComparison.OrdinalIgnoreCase))
                PrefabUtility.RevertPrefabInstance(root, InteractionMode.AutomatedAction);
            else if (string.Equals(action, "UnpackOutermost", StringComparison.OrdinalIgnoreCase))
                PrefabUtility.UnpackPrefabInstance(root, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction);
            else if (string.Equals(action, "UnpackCompletely", StringComparison.OrdinalIgnoreCase))
                PrefabUtility.UnpackPrefabInstance(root, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction);
            else
                throw new ArgumentException("Action must be Revert, UnpackOutermost, or UnpackCompletely.");
            bool saved = !arguments.SaveScene || EditorSceneManager.SaveScene(root.scene);
            return Task.FromResult<object>(new
            {
                Succeeded = true,
                Action = action,
                HierarchyPath = hierarchy,
                SourceAssetPath = source,
                IsPrefabInstanceAfter = PrefabUtility.IsPartOfPrefabInstance(root),
                SceneSaved = saved
            });
        }
    }


    [Serializable]
    public sealed class ControlPlayModeArguments
    {
        [Required]
        [Description("Enter, Exit, Pause, or Resume.")]
        public string Action { get; set; }

        [DefaultValue(30)]
        [Description("Maximum seconds to wait for Unity to finish entering or exiting Play Mode.")]
        public int TransitionTimeoutSeconds { get; set; } = 30;

        [DefaultValue(true)]
        [Description("When Enter reaches Play Mode paused without a startup error, automatically resume the owned session.")]
        public bool RecoverUnexpectedPause { get; set; } = true;

        [DefaultValue(true)]
        [Description("When Enter observes a startup error or exception, exit the owned Play Mode session and return the diagnostic so the agent can repair the cause in Edit Mode.")]
        public bool ExitOnStartupError { get; set; } = true;
    }


    public sealed class ControlPlayModeCapability :
        CapabilityBase<ControlPlayModeArguments>
    {
        private const int MinimumTransitionTimeoutSeconds = 5;
        private const int MaximumTransitionTimeoutSeconds = 120;
        private const int StartupSettleUpdateCount = 2;
        private const int MaximumStartupErrors = 10;
        internal const string NoCorrectiveAction = "None";
        internal const string ResumeUnexpectedPauseAction = "ResumedUnexpectedPause";
        internal const string ExitAfterStartupErrorAction = "ExitedOwnedPlayModeAfterStartupError";

        private readonly string _runId;
        private static bool _scopeActive;
        private static string _scopeOwnerRunId;
        private static bool _previousOptionsEnabled;
        private static EnterPlayModeOptions _previousOptions;


        private sealed class PlayModeStartupError
        {
            public string Message { get; set; }
            public string StackTrace { get; set; }
            public string Type { get; set; }
        }

        static ControlPlayModeCapability()
        {
            EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
            EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
        }

        public ControlPlayModeCapability()
        {
        }

        internal ControlPlayModeCapability(
            IKanbanRunIdentity runIdentity)
        {
            _runId = runIdentity?.RunId;
        }

        public override string Name => "control_play_mode";
        public override string Description =>
            "Enters, exits, pauses, or resumes Unity Play Mode while preserving the Kanban callback domain. Enter waits for the transition to settle, reports startup errors, resumes an unexplained pause, and exits an owned startup-error session so the agent can repair the cause in Edit Mode.";
        public override CapabilityPermission Permission => CapabilityPermission.Write;

        protected override async Task<object> ExecuteAsync(
            ControlPlayModeArguments arguments,
            CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            string action = arguments?.Action?.Trim();
            if (string.Equals(action, "Enter", StringComparison.OrdinalIgnoreCase))
            {
                if (_scopeActive &&
                    !string.Equals(
                        _scopeOwnerRunId,
                        _runId,
                        StringComparison.Ordinal))
                {
                    throw new InvalidOperationException(
                        "Play Mode is already controlled by another agent run. Wait for that run to finish or exit Play Mode before retrying.");
                }

                if (!_scopeActive)
                {
                    _previousOptionsEnabled = EditorSettings.enterPlayModeOptionsEnabled;
                    _previousOptions = EditorSettings.enterPlayModeOptions;
                    ApplySafeAutomatedReloadOptions(
                        _previousOptions);
                    _scopeOwnerRunId = _runId;
                    _scopeActive = true;
                }

                int timeoutSeconds =
                    Math.Max(
                        MinimumTransitionTimeoutSeconds,
                        Math.Min(
                            MaximumTransitionTimeoutSeconds,
                            arguments?.TransitionTimeoutSeconds ?? 30));

                var correctiveActions =
                    new List<string>();

                bool clearedPauseBeforeEntry =
                    EditorApplication.isPaused;

                if (clearedPauseBeforeEntry)
                {
                    EditorApplication.isPaused = false;
                    correctiveActions.Add(
                        "ClearedStalePauseBeforeEntry");
                }

                List<PlayModeStartupError> startupErrors;

                try
                {
                    startupErrors =
                        await EnterAndObservePlayModeAsync(
                            timeoutSeconds,
                            context.CancellationToken);
                }
                catch (TimeoutException exception)
                {
                    bool exited =
                        await TryExitOwnedSessionAsync(
                            timeoutSeconds,
                            CancellationToken.None);

                    if (exited)
                    {
                        correctiveActions.Add(
                            "ExitedOwnedPlayModeAfterTransitionTimeout");
                    }

                    return new
                    {
                        Succeeded = false,
                        RequestedAction = action,
                        ErrorCode = "PlayModeEntryTimeout",
                        Error = exception.Message,
                        CorrectiveActionTaken =
                            JoinCorrectiveActions(
                                correctiveActions),
                        StartupErrors =
                            Array.Empty<PlayModeStartupError>(),
                        EditorApplication.isPlaying,
                        EditorApplication.isPlayingOrWillChangePlaymode,
                        EditorApplication.isPaused,
                        DomainReloadDisabledForSession = _scopeActive,
                        SceneReloadEnabledForSession = _scopeActive,
                        AgentGuidance =
                            "Inspect the Unity console and editor state, correct the blocking transition condition in Edit Mode, then retry Enter."
                    };
                }

                bool pausedAfterEntry =
                    EditorApplication.isPaused;

                string selectedCorrection =
                    SelectAutomaticCorrection(
                        pausedAfterEntry,
                        startupErrors.Count > 0,
                        arguments?.RecoverUnexpectedPause ?? true,
                        arguments?.ExitOnStartupError ?? true);

                if (string.Equals(
                        selectedCorrection,
                        ExitAfterStartupErrorAction,
                        StringComparison.Ordinal))
                {
                    bool exited =
                        await TryExitOwnedSessionAsync(
                            timeoutSeconds,
                            CancellationToken.None);

                    correctiveActions.Add(
                        exited
                            ? ExitAfterStartupErrorAction
                            : "StartupErrorDetectedButOwnedSessionCouldNotExit");
                }
                else if (string.Equals(
                             selectedCorrection,
                             ResumeUnexpectedPauseAction,
                             StringComparison.Ordinal))
                {
                    EditorApplication.isPaused = false;
                    EditorApplication.QueuePlayerLoopUpdate();

                    await WaitForEditorUpdatesAsync(
                        StartupSettleUpdateCount,
                        timeoutSeconds,
                        context.CancellationToken);

                    correctiveActions.Add(
                        EditorApplication.isPaused
                            ? "UnexpectedPauseResumeFailed"
                            : ResumeUnexpectedPauseAction);
                }

                bool startupFailed =
                    startupErrors.Count > 0;

                bool stillPaused =
                    EditorApplication.isPaused;

                bool succeeded =
                    !startupFailed &&
                    EditorApplication.isPlaying &&
                    !stillPaused;

                return new
                {
                    Succeeded = succeeded,
                    RequestedAction = action,
                    ErrorCode =
                        startupFailed
                            ? "PlayModeStartupError"
                            : stillPaused
                                ? "PlayModeRemainedPaused"
                                : string.Empty,
                    Error =
                        startupFailed
                            ? "Unity reported one or more errors while entering Play Mode. The agent must correct the startup failure before runtime verification continues."
                            : stillPaused
                                ? "Unity entered Play Mode paused and automatic resume was disabled or unsuccessful."
                                : string.Empty,
                    CorrectiveActionTaken =
                        JoinCorrectiveActions(
                            correctiveActions),
                    StartupErrors =
                        startupErrors.ToArray(),
                    PausedAfterEntry =
                        pausedAfterEntry,
                    EditorApplication.isPlaying,
                    EditorApplication.isPlayingOrWillChangePlaymode,
                    EditorApplication.isPaused,
                    DomainReloadDisabledForSession = _scopeActive,
                    SceneReloadEnabledForSession = _scopeActive,
                    AgentGuidance =
                        startupFailed
                            ? "Inspect StartupErrors, repair the scene or script in Edit Mode, verify compilation, and retry Enter. Do not blindly resume an error-paused session."
                            : stillPaused
                                ? "Inspect the Unity console and pause source, then explicitly Resume or Exit after correcting it."
                                : "Play Mode is active and advancing; continue with bounded frame waits, runtime inspection, or capture."
                };
            }
            else if (string.Equals(action, "Exit", StringComparison.OrdinalIgnoreCase))
            {
                if (_scopeActive &&
                    !string.Equals(
                        _scopeOwnerRunId,
                        _runId,
                        StringComparison.Ordinal))
                {
                    throw new InvalidOperationException(
                        "Play Mode is controlled by another agent run and cannot be exited by this capability instance.");
                }

                EditorApplication.ExitPlaymode();
            }
            else if (string.Equals(action, "Pause", StringComparison.OrdinalIgnoreCase))
                EditorApplication.isPaused = true;
            else if (string.Equals(action, "Resume", StringComparison.OrdinalIgnoreCase))
                EditorApplication.isPaused = false;
            else
                throw new ArgumentException("Action must be Enter, Exit, Pause, or Resume.");

            return new
            {
                Succeeded = true,
                RequestedAction = action,
                EditorApplication.isPlaying,
                EditorApplication.isPlayingOrWillChangePlaymode,
                EditorApplication.isPaused,
                DomainReloadDisabledForSession = _scopeActive,
                SceneReloadEnabledForSession = _scopeActive
            };
        }


        internal static string SelectAutomaticCorrection(
            bool isPaused,
            bool hasStartupErrors,
            bool recoverUnexpectedPause,
            bool exitOnStartupError)
        {
            if (hasStartupErrors && exitOnStartupError)
                return ExitAfterStartupErrorAction;

            if (isPaused &&
                !hasStartupErrors &&
                recoverUnexpectedPause)
            {
                return ResumeUnexpectedPauseAction;
            }

            return NoCorrectiveAction;
        }


        private static async Task<List<PlayModeStartupError>> EnterAndObservePlayModeAsync(
            int timeoutSeconds,
            CancellationToken cancellationToken)
        {
            var startupErrors =
                new List<PlayModeStartupError>();

            var completion =
                new TaskCompletionSource<bool>(
                    TaskCreationOptions.RunContinuationsAsynchronously);

            double deadline =
                EditorApplication.timeSinceStartup +
                timeoutSeconds;

            bool enteredPlayMode =
                EditorApplication.isPlaying;

            bool beganTransition =
                EditorApplication.isPlayingOrWillChangePlaymode;

            int settledUpdates = 0;
            EditorApplication.CallbackFunction update = null;
            Action<PlayModeStateChange> stateChanged = null;
            Application.LogCallback logReceived = null;

            update = () =>
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    completion.TrySetCanceled(
                        cancellationToken);
                    return;
                }

                if (EditorApplication.timeSinceStartup >= deadline)
                {
                    completion.TrySetException(
                        new TimeoutException(
                            $"Unity did not finish entering Play Mode within {timeoutSeconds} seconds."));
                    return;
                }

                if (!enteredPlayMode &&
                    EditorApplication.isPlaying)
                {
                    enteredPlayMode = true;
                }

                if (enteredPlayMode &&
                    ++settledUpdates >= StartupSettleUpdateCount)
                {
                    completion.TrySetResult(
                        true);
                }
            };

            stateChanged = state =>
            {
                if (state == PlayModeStateChange.ExitingEditMode)
                    beganTransition = true;

                if (state == PlayModeStateChange.EnteredPlayMode)
                    enteredPlayMode = true;

                if (beganTransition &&
                    state == PlayModeStateChange.EnteredEditMode &&
                    !enteredPlayMode)
                {
                    completion.TrySetException(
                        new InvalidOperationException(
                            "Unity returned to Edit Mode before Play Mode entry completed."));
                }
            };

            logReceived = (message, stackTrace, type) =>
            {
                if (!IsStartupFailureLogType(type) ||
                    startupErrors.Count >= MaximumStartupErrors)
                {
                    return;
                }

                startupErrors.Add(
                    new PlayModeStartupError
                    {
                        Message = message ?? string.Empty,
                        StackTrace = stackTrace ?? string.Empty,
                        Type = type.ToString()
                    });
            };

            EditorApplication.update += update;
            EditorApplication.playModeStateChanged += stateChanged;
            Application.logMessageReceived += logReceived;

            using (cancellationToken.Register(
                       () => completion.TrySetCanceled(
                           cancellationToken)))
            {
                try
                {
                    if (!EditorApplication.isPlayingOrWillChangePlaymode)
                    {
                        beganTransition = true;
                        ReassertSafeAutomatedReloadOptions();
                        EditorApplication.EnterPlaymode();
                    }

                    await completion.Task;
                }
                finally
                {
                    EditorApplication.update -= update;
                    EditorApplication.playModeStateChanged -= stateChanged;
                    Application.logMessageReceived -= logReceived;
                }
            }

            return startupErrors;
        }


        private async Task<bool> TryExitOwnedSessionAsync(
            int timeoutSeconds,
            CancellationToken cancellationToken)
        {
            if (!_scopeActive ||
                !string.Equals(
                    _scopeOwnerRunId,
                    _runId,
                    StringComparison.Ordinal))
            {
                return false;
            }

            if (EditorApplication.isPlayingOrWillChangePlaymode)
            {
                EditorApplication.ExitPlaymode();

                try
                {
                    await WaitForEditModeAsync(
                        timeoutSeconds,
                        cancellationToken);
                }
                catch (TimeoutException)
                {
                    RestoreSettings();
                    return false;
                }
            }

            RestoreSettings();
            return true;
        }


        private static async Task WaitForEditModeAsync(
            int timeoutSeconds,
            CancellationToken cancellationToken)
        {
            var completion =
                new TaskCompletionSource<bool>(
                    TaskCreationOptions.RunContinuationsAsynchronously);

            double deadline =
                EditorApplication.timeSinceStartup +
                timeoutSeconds;

            EditorApplication.CallbackFunction update = null;
            update = () =>
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    completion.TrySetCanceled(
                        cancellationToken);
                }
                else if (!EditorApplication.isPlayingOrWillChangePlaymode)
                {
                    completion.TrySetResult(
                        true);
                }
                else if (EditorApplication.timeSinceStartup >= deadline)
                {
                    completion.TrySetException(
                        new TimeoutException(
                            $"Unity did not return to Edit Mode within {timeoutSeconds} seconds."));
                }
            };

            EditorApplication.update += update;

            using (cancellationToken.Register(
                       () => completion.TrySetCanceled(
                           cancellationToken)))
            {
                try
                {
                    await completion.Task;
                }
                finally
                {
                    EditorApplication.update -= update;
                }
            }
        }


        private static async Task WaitForEditorUpdatesAsync(
            int updateCount,
            int timeoutSeconds,
            CancellationToken cancellationToken)
        {
            var completion =
                new TaskCompletionSource<bool>(
                    TaskCreationOptions.RunContinuationsAsynchronously);

            double deadline =
                EditorApplication.timeSinceStartup +
                timeoutSeconds;

            int observedUpdates = 0;
            EditorApplication.CallbackFunction update = null;
            update = () =>
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    completion.TrySetCanceled(
                        cancellationToken);
                }
                else if (++observedUpdates >= updateCount)
                {
                    completion.TrySetResult(
                        true);
                }
                else if (EditorApplication.timeSinceStartup >= deadline)
                {
                    completion.TrySetException(
                        new TimeoutException(
                            $"Unity did not produce {updateCount} editor updates within {timeoutSeconds} seconds."));
                }
            };

            EditorApplication.update += update;

            using (cancellationToken.Register(
                       () => completion.TrySetCanceled(
                           cancellationToken)))
            {
                try
                {
                    await completion.Task;
                }
                finally
                {
                    EditorApplication.update -= update;
                }
            }
        }


        private static bool IsStartupFailureLogType(
            LogType type)
        {
            return type == LogType.Error ||
                   type == LogType.Assert ||
                   type == LogType.Exception;
        }


        private static string JoinCorrectiveActions(
            IEnumerable<string> correctiveActions)
        {
            string[] actions =
                (correctiveActions ?? Enumerable.Empty<string>())
                    .Where(value => !string.IsNullOrWhiteSpace(value))
                    .Distinct(StringComparer.Ordinal)
                    .ToArray();

            return actions.Length == 0
                ? NoCorrectiveAction
                : string.Join(",", actions);
        }

        internal static EnterPlayModeOptions WithSafeAutomatedReloadOptions(
            EnterPlayModeOptions options)
        {
            return (options |
                    EnterPlayModeOptions.DisableDomainReload) &
                   ~EnterPlayModeOptions.DisableSceneReload;
        }

        internal static void ApplySafeAutomatedReloadOptions(
            EnterPlayModeOptions options)
        {
            // Publish the safe mask before enabling the override. Unity must
            // never observe DisableSceneReload during an automated entry.
            EditorSettings.enterPlayModeOptions =
                WithSafeAutomatedReloadOptions(
                    options);
            EditorSettings.enterPlayModeOptionsEnabled = true;
        }

        private static void ReassertSafeAutomatedReloadOptions()
        {
            if (!_scopeActive)
                return;

            ApplySafeAutomatedReloadOptions(
                _previousOptions);
        }

        private static void RestoreSettings()
        {
            if (!_scopeActive) return;
            EditorSettings.enterPlayModeOptions = _previousOptions;
            EditorSettings.enterPlayModeOptionsEnabled = _previousOptionsEnabled;
            _scopeOwnerRunId = null;
            _scopeActive = false;
        }

        internal static bool EndOwnedSession(
            string runId)
        {
            if (!_scopeActive ||
                string.IsNullOrWhiteSpace(runId) ||
                !string.Equals(
                    _scopeOwnerRunId,
                    runId,
                    StringComparison.Ordinal))
            {
                return false;
            }

            if (EditorApplication.isPlayingOrWillChangePlaymode)
            {
                EditorApplication.ExitPlaymode();
            }
            else
            {
                RestoreSettings();
            }

            return true;
        }

        private static void OnPlayModeStateChanged(PlayModeStateChange state)
        {
            if (ShouldRestoreSettings(state))
                RestoreSettings();
        }

        internal static bool ShouldRestoreSettings(
            PlayModeStateChange state)
        {
            // Keep scene reload enabled for the entire exit transition. An
            // early restore can leak DisableSceneReload into a queued retry.
            return state == PlayModeStateChange.EnteredEditMode;
        }
    }


    [Serializable]
    public sealed class WaitForEditorFramesArguments
    {
        [DefaultValue(1)] public int FrameCount { get; set; } = 1;
    }


    public sealed class WaitForEditorFramesCapability :
        CapabilityBase<WaitForEditorFramesArguments>
    {
        public override string Name => "wait_for_editor_frames";
        public override string Description =>
            "Waits for a bounded number of Unity Editor updates so Play Mode and runtime state can advance before inspection.";
        public override CapabilityPermission Permission => CapabilityPermission.ReadOnly;

        protected override async Task<object> ExecuteAsync(WaitForEditorFramesArguments arguments, CapabilityContext context)
        {
            int requested = Math.Max(1, Math.Min(600, arguments?.FrameCount ?? 1));
            int observed = 0;
            TaskCompletionSource<bool> completion = new TaskCompletionSource<bool>();
            EditorApplication.CallbackFunction update = null;
            update = () =>
            {
                if (context.CancellationToken.IsCancellationRequested || ++observed >= requested)
                {
                    EditorApplication.update -= update;
                    completion.TrySetResult(true);
                }
            };
            EditorApplication.update += update;
            await completion.Task;
            context.CancellationToken.ThrowIfCancellationRequested();
            return new
            {
                Succeeded = true,
                RequestedFrames = requested,
                ObservedFrames = observed,
                EditorApplication.isPlaying,
                EditorApplication.isPaused
            };
        }
    }


    [Serializable]
    public sealed class CaptureCameraViewArguments
    {
        public int CameraGameObjectInstanceId { get; set; }
        [Required]
        [Description("Contained workspace path ending in .png, normally under Assets/ or an Outputs folder.")]
        public string OutputPath { get; set; }
        [DefaultValue(1280)] public int Width { get; set; } = 1280;
        [DefaultValue(720)] public int Height { get; set; } = 720;
    }


    public sealed class CaptureCameraViewCapability :
        CapabilityBase<CaptureCameraViewArguments>
    {
        public override string Name => "capture_camera_view";
        public override string Description =>
            "Renders an exact Camera or Camera.main to a contained PNG and returns dimensions and file identity for visual verification.";
        public override CapabilityPermission Permission => CapabilityPermission.Write;

        protected override Task<object> ExecuteAsync(CaptureCameraViewArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (arguments == null || string.IsNullOrWhiteSpace(arguments.OutputPath) ||
                !arguments.OutputPath.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
                throw new ArgumentException("OutputPath ending in .png is required.");
            Camera camera = null;
            if (arguments.CameraGameObjectInstanceId != 0)
                camera = EditorCapabilityUtility.ResolveGameObject(
                    arguments.CameraGameObjectInstanceId, null, false).GetComponent<Camera>();
            if (camera == null) camera = Camera.main;
            if (camera == null) camera = UnityEngine.Object.FindObjectsOfType<Camera>(true).FirstOrDefault();
            if (camera == null) throw new InvalidOperationException("No Camera is available to render.");
            int width = Math.Max(16, Math.Min(4096, arguments.Width));
            int height = Math.Max(16, Math.Min(4096, arguments.Height));
            string output = ExtendedCapabilityUtility.ResolveWorkspaceOutputPath(arguments.OutputPath);
            RenderTexture render = new RenderTexture(width, height, 24);
            Texture2D texture = new Texture2D(width, height, TextureFormat.RGB24, false);
            RenderTexture previousActive = RenderTexture.active;
            RenderTexture previousTarget = camera.targetTexture;
            try
            {
                camera.targetTexture = render;
                RenderTexture.active = render;
                camera.Render();
                texture.ReadPixels(new Rect(0, 0, width, height), 0, 0);
                texture.Apply();
                File.WriteAllBytes(output, texture.EncodeToPNG());
            }
            finally
            {
                camera.targetTexture = previousTarget;
                RenderTexture.active = previousActive;
                UnityEngine.Object.DestroyImmediate(texture);
                render.Release();
                UnityEngine.Object.DestroyImmediate(render);
            }
            string relative = ExtendedCapabilityUtility.ToWorkspaceRelative(output);
            if (relative.StartsWith("Assets/", StringComparison.Ordinal))
                AssetDatabase.ImportAsset(relative, ImportAssetOptions.ForceSynchronousImport);
            return Task.FromResult<object>(new
            {
                Succeeded = true,
                OutputPath = relative,
                Width = width,
                Height = height,
                CameraInstanceId = camera.GetInstanceID(),
                CameraName = camera.name,
                Bytes = new FileInfo(output).Length,
                Artifacts = new[]
                {
                    ExtendedCapabilityUtility.DescribeFileArtifact(relative, "Image", "image/png")
                }
            });
        }
    }


    [Serializable]
    public sealed class CaptureGameViewArguments
    {
        [Required]
        [Description("Contained workspace path ending in .png.")]
        public string OutputPath { get; set; }
        [DefaultValue(300)] public int MaximumEditorFrames { get; set; } = 300;
    }


    public sealed class CaptureGameViewCapability : CapabilityBase<CaptureGameViewArguments>
    {
        public override string Name => "capture_game_view";
        public override string Description =>
            "Captures the played Game View, including Screen Space Overlay UI, to a contained PNG and emits durable image-artifact metadata.";
        public override CapabilityPermission Permission => CapabilityPermission.Write;

        protected override async Task<object> ExecuteAsync(CaptureGameViewArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (!EditorApplication.isPlaying)
                throw new InvalidOperationException("capture_game_view requires active Play Mode.");
            if (arguments == null || string.IsNullOrWhiteSpace(arguments.OutputPath) ||
                !arguments.OutputPath.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
                throw new ArgumentException("OutputPath ending in .png is required.");
            string output = ExtendedCapabilityUtility.ResolveWorkspaceOutputPath(arguments.OutputPath);
            if (File.Exists(output)) File.Delete(output);
            int maximumFrames = Math.Max(1, Math.Min(1800, arguments.MaximumEditorFrames));
            int observed = 0;
            TaskCompletionSource<bool> completion = new TaskCompletionSource<bool>();
            EditorApplication.CallbackFunction update = null;
            update = () =>
            {
                observed++;
                if (context.CancellationToken.IsCancellationRequested ||
                    (File.Exists(output) && new FileInfo(output).Length > 0) || observed >= maximumFrames)
                {
                    EditorApplication.update -= update;
                    completion.TrySetResult(true);
                }
            };
            EditorApplication.update += update;
            ScreenCapture.CaptureScreenshot(output);
            await completion.Task;
            context.CancellationToken.ThrowIfCancellationRequested();
            if (!File.Exists(output) || new FileInfo(output).Length == 0)
                throw new TimeoutException($"Game View screenshot was not written within {maximumFrames} Editor frames.");
            string relative = ExtendedCapabilityUtility.ToWorkspaceRelative(output);
            if (relative.StartsWith("Assets/", StringComparison.Ordinal))
                AssetDatabase.ImportAsset(relative, ImportAssetOptions.ForceSynchronousImport);
            return new
            {
                Succeeded = true,
                OutputPath = relative,
                ObservedEditorFrames = observed,
                Bytes = new FileInfo(output).Length,
                Artifacts = new[]
                {
                    ExtendedCapabilityUtility.DescribeFileArtifact(relative, "Image", "image/png")
                }
            };
        }
    }


    [Serializable]
    public sealed class BuildPlayerArguments
    {
        [Required] public string OutputPath { get; set; }
        [DefaultValue("StandaloneWindows64")]
        public string BuildTarget { get; set; } = "StandaloneWindows64";
        public List<string> ScenePaths { get; set; } = new List<string>();
        [DefaultValue(false)] public bool DevelopmentBuild { get; set; }
    }


    public sealed class BuildPlayerCapability :
        CapabilityBase<BuildPlayerArguments>,
        ICapabilityAuthorizationSemantics
    {
        public override string Name => "build_player";
        public override string Description =>
            "Builds the Unity player for a supported target into a contained workspace output and returns the authoritative BuildReport summary.";
        public override CapabilityPermission Permission => CapabilityPermission.Write;
        public IReadOnlyList<CapabilityPermission> RequiredPermissions =>
            new[] { CapabilityPermission.Write, CapabilityPermission.Process };
        public IReadOnlyList<string> EffectScopes =>
            new[] { "workspace:build-output", "process:unity-build-pipeline" };

        protected override Task<object> ExecuteAsync(BuildPlayerArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (arguments == null) throw new ArgumentNullException(nameof(arguments));
            if (!Enum.TryParse(arguments.BuildTarget, true, out BuildTarget target))
                throw new ArgumentException($"Unknown BuildTarget '{arguments.BuildTarget}'.");
            string output = ExtendedCapabilityUtility.ResolveWorkspaceOutputPath(arguments.OutputPath);
            string[] scenes = (arguments.ScenePaths ?? new List<string>()).Count > 0
                ? arguments.ScenePaths.Select(path => ExtendedCapabilityUtility.ValidateAssetPath(path, ".unity")).ToArray()
                : EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(scene => scene.path).ToArray();
            if (scenes.Length == 0) throw new InvalidOperationException("No build scenes were supplied or enabled.");
            BuildOptions options = arguments.DevelopmentBuild ? BuildOptions.Development : BuildOptions.None;
            UnityEditor.Build.Reporting.BuildReport report = BuildPipeline.BuildPlayer(
                new BuildPlayerOptions
                {
                    scenes = scenes,
                    locationPathName = output,
                    target = target,
                    options = options
                });
            var summary = report.summary;
            return Task.FromResult<object>(new
            {
                Succeeded = summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded,
                Result = summary.result.ToString(),
                OutputPath = ExtendedCapabilityUtility.ToWorkspaceRelative(output),
                summary.totalErrors,
                summary.totalWarnings,
                summary.totalSize,
                DurationSeconds = summary.totalTime.TotalSeconds,
                Scenes = scenes
            });
        }
    }


    [Serializable]
    public sealed class CreateScriptableObjectArguments
    {
        [Required] public string TypeName { get; set; }
        [Required] public string AssetPath { get; set; }
    }


    public sealed class CreateScriptableObjectCapability :
        CapabilityBase<CreateScriptableObjectArguments>
    {
        public override string Name => "create_scriptable_object";
        public override string Description =>
            "Creates a persistent Unity ScriptableObject asset from an exact concrete installed type and returns its identity for serialized-property configuration.";
        public override CapabilityPermission Permission => CapabilityPermission.Write;

        protected override Task<object> ExecuteAsync(CreateScriptableObjectArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            if (arguments == null) throw new ArgumentNullException(nameof(arguments));
            string path = ExtendedCapabilityUtility.ValidateAssetPath(arguments.AssetPath, ".asset");
            if (AssetDatabase.LoadMainAssetAtPath(path) != null)
                throw new InvalidOperationException($"An asset already exists at '{path}'.");
            Type type = ExtendedCapabilityUtility.ResolveType(arguments.TypeName, typeof(ScriptableObject));
            ScriptableObject asset = ScriptableObject.CreateInstance(type);
            asset.name = Path.GetFileNameWithoutExtension(path);
            EditorSceneAuthoringUtility.EnsureAssetFolder(EditorSceneAuthoringUtility.GetParentAssetPath(path));
            AssetDatabase.CreateAsset(asset, path);
            AssetDatabase.SaveAssets();
            ScriptableObject verified = AssetDatabase.LoadAssetAtPath<ScriptableObject>(path);
            if (verified == null) throw new InvalidOperationException($"Unity did not persist ScriptableObject '{path}'.");
            return Task.FromResult<object>(new
            {
                Succeeded = true,
                AssetPath = path,
                AssetGuid = AssetDatabase.AssetPathToGUID(path),
                InstanceId = verified.GetInstanceID(),
                Type = verified.GetType().FullName,
                verified.name
            });
        }
    }


    [Serializable]
    public sealed class ListUnityPackagesArguments
    {
        [DefaultValue(true)] public bool IncludeIndirectDependencies { get; set; } = true;
    }


    public sealed class ListUnityPackagesCapability :
        CapabilityBase<ListUnityPackagesArguments>
    {
        public override string Name => "list_unity_packages";
        public override string Description =>
            "Lists installed Unity packages through Package Manager, including direct/indirect status and resolved versions.";
        public override CapabilityPermission Permission => CapabilityPermission.ReadOnly;

        protected override async Task<object> ExecuteAsync(ListUnityPackagesArguments arguments, CapabilityContext context)
        {
            ListRequest request = Client.List(arguments?.IncludeIndirectDependencies ?? true);
            await ExtendedCapabilityUtility.AwaitPackageRequest(request, context);
            return new
            {
                Succeeded = request.Status == StatusCode.Success,
                Packages = request.Result.Select(package => new
                {
                    package.name,
                    package.displayName,
                    package.version,
                    package.source,
                    package.isDirectDependency,
                    package.resolvedPath
                }).ToArray()
            };
        }
    }


    [Serializable]
    public sealed class RunProcessArguments
    {
        [Required] public string Executable { get; set; }
        [Description("Arguments are passed directly to the executable without a command shell.")]
        public List<string> Arguments { get; set; } = new List<string>();
        [Description("Workspace-relative working directory. Defaults to the project root.")]
        public string WorkingDirectory { get; set; }
        [DefaultValue(300)] public int TimeoutSeconds { get; set; } = 300;
        [DefaultValue(200000)] public int MaximumOutputCharacters { get; set; } = 200000;
    }


    public sealed class RunProcessCapability :
        CapabilityBase<RunProcessArguments>,
        ICapabilityAuthorizationSemantics
    {
        public override string Name => "run_process";
        public override string Description =>
            "Runs an explicit executable without a shell in a workspace-contained working directory, capturing bounded stdout, stderr, exit code, timeout, and cancellation state.";
        public override CapabilityPermission Permission => CapabilityPermission.Process;
        public IReadOnlyList<CapabilityPermission> RequiredPermissions =>
            new[] { CapabilityPermission.Process };
        public IReadOnlyList<string> EffectScopes =>
            new[]
            {
                "process:explicit-executable-no-shell",
                "workspace:contained-working-directory",
                "warning:child-process-effects-require-os-sandbox-for-independent-enforcement"
            };

        protected override async Task<object> ExecuteAsync(RunProcessArguments arguments, CapabilityContext context)
        {
            string executable = arguments?.Executable?.Trim();
            if (string.IsNullOrWhiteSpace(executable))
                throw new ArgumentException("Executable is required.");

            string workingDirectory = ExtendedCapabilityUtility.ResolveWorkspacePath(
                arguments.WorkingDirectory ?? string.Empty, true);
            int timeoutSeconds = Math.Max(1, Math.Min(1800, arguments.TimeoutSeconds));
            int maximumCharacters = Math.Max(1024, Math.Min(1000000, arguments.MaximumOutputCharacters));
            StringBuilder stdout = new StringBuilder();
            StringBuilder stderr = new StringBuilder();
            object outputGate = new object();

            using (System.Diagnostics.Process process = new System.Diagnostics.Process())
            {
                process.StartInfo = new System.Diagnostics.ProcessStartInfo
                {
                    FileName = executable,
                    Arguments = ExtendedCapabilityUtility.JoinProcessArguments(arguments.Arguments),
                    WorkingDirectory = workingDirectory,
                    UseShellExecute = false,
                    CreateNoWindow = true,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true
                };
                process.OutputDataReceived += (_, eventArgs) =>
                    ExtendedCapabilityUtility.AppendBounded(stdout, eventArgs.Data, maximumCharacters, outputGate);
                process.ErrorDataReceived += (_, eventArgs) =>
                    ExtendedCapabilityUtility.AppendBounded(stderr, eventArgs.Data, maximumCharacters, outputGate);

                if (!process.Start())
                    throw new InvalidOperationException($"Could not start process '{executable}'.");
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();

                DateTime deadline = DateTime.UtcNow.AddSeconds(timeoutSeconds);
                bool timedOut = false;
                while (!process.HasExited)
                {
                    if (context.CancellationToken.IsCancellationRequested || DateTime.UtcNow >= deadline)
                    {
                        timedOut = !context.CancellationToken.IsCancellationRequested;
                        try { process.Kill(); } catch (InvalidOperationException) { }
                        break;
                    }
                    await Task.Delay(50);
                }
                process.WaitForExit();
                context.CancellationToken.ThrowIfCancellationRequested();
                return new
                {
                    Succeeded = !timedOut && process.ExitCode == 0,
                    process.ExitCode,
                    TimedOut = timedOut,
                    StandardOutput = stdout.ToString(),
                    StandardError = stderr.ToString(),
                    WorkingDirectory = ExtendedCapabilityUtility.ToWorkspaceRelative(workingDirectory)
                };
            }
        }
    }


    [Serializable]
    public sealed class InspectAssetImporterArguments
    {
        [Required] public string AssetPath { get; set; }
        [DefaultValue(false)] public bool IncludeHidden { get; set; }
        [DefaultValue(256)] public int MaximumProperties { get; set; } = 256;
    }


    public sealed class InspectAssetImporterCapability : CapabilityBase<InspectAssetImporterArguments>
    {
        public override string Name => "inspect_asset_importer";
        public override string Description =>
            "Inspects the real Unity AssetImporter and its serialized import settings for an existing asset.";
        public override CapabilityPermission Permission => CapabilityPermission.ReadOnly;

        protected override Task<object> ExecuteAsync(InspectAssetImporterArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            string assetPath = ExtendedCapabilityUtility.ValidateAnyAssetPath(arguments?.AssetPath);
            AssetImporter importer = AssetImporter.GetAtPath(assetPath);
            if (importer == null)
                throw new FileNotFoundException($"No AssetImporter exists for '{assetPath}'.", assetPath);
            bool truncated;
            List<EditorSerializedPropertyRecord> properties = EditorCapabilityUtility.ReadSerializedProperties(
                importer, arguments.IncludeHidden, arguments.MaximumProperties, out truncated);
            return Task.FromResult<object>(new
            {
                Succeeded = true,
                AssetPath = assetPath,
                ImporterType = importer.GetType().FullName,
                Properties = properties,
                Truncated = truncated
            });
        }
    }


    [Serializable]
    public sealed class SetAssetImporterPropertyArguments
    {
        [Required] public string AssetPath { get; set; }
        [Required] public string PropertyPath { get; set; }
        [Required]
        [Description("JSON value using the same typed serialized-property contract as set_serialized_property.")]
        public string ValueJson { get; set; }
    }


    public sealed class SetAssetImporterPropertyCapability : CapabilityBase<SetAssetImporterPropertyArguments>
    {
        public override string Name => "set_asset_importer_property";
        public override string Description =>
            "Sets one exact serialized AssetImporter property, applies it, reimports the asset, and returns the verified resulting value.";
        public override CapabilityPermission Permission => CapabilityPermission.Write;

        protected override Task<object> ExecuteAsync(SetAssetImporterPropertyArguments arguments, CapabilityContext context)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            string assetPath = ExtendedCapabilityUtility.ValidateWritableAssetPath(arguments?.AssetPath);
            string propertyPath = arguments?.PropertyPath?.Trim();
            if (string.IsNullOrWhiteSpace(propertyPath) || string.IsNullOrWhiteSpace(arguments.ValueJson))
                throw new ArgumentException("PropertyPath and ValueJson are required.");
            AssetImporter importer = AssetImporter.GetAtPath(assetPath);
            if (importer == null)
                throw new FileNotFoundException($"No AssetImporter exists for '{assetPath}'.", assetPath);
            SerializedObject serialized = new SerializedObject(importer);
            serialized.UpdateIfRequiredOrScript();
            SerializedProperty property = serialized.FindProperty(propertyPath);
            if (property == null)
                throw new ArgumentException($"Importer property '{propertyPath}' does not exist on {importer.GetType().FullName}.");
            EditorCapabilityUtility.AssignSerializedValue(property, JToken.Parse(arguments.ValueJson));
            serialized.ApplyModifiedPropertiesWithoutUndo();
            importer.SaveAndReimport();

            serialized = new SerializedObject(AssetImporter.GetAtPath(assetPath));
            serialized.UpdateIfRequiredOrScript();
            SerializedProperty verified = serialized.FindProperty(propertyPath);
            return Task.FromResult<object>(new
            {
                Succeeded = true,
                AssetPath = assetPath,
                PropertyPath = propertyPath,
                Value = EditorCapabilityUtility.DescribeSerializedProperty(verified)
            });
        }
    }


    internal static class ExtendedCapabilityUtility
    {
        internal static string[] GetProjectTags()
        {
            var manager = AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset").FirstOrDefault();
            if (manager == null) throw new InvalidOperationException("Unity TagManager asset could not be loaded.");
            using (var serialized = new SerializedObject(manager))
            {
                var tags = serialized.FindProperty("tags");
                var names = new List<string> { "Untagged", "Respawn", "Finish", "EditorOnly", "MainCamera", "Player", "GameController" };
                for (int index = 0; index < tags.arraySize; index++)
                    names.Add(tags.GetArrayElementAtIndex(index).stringValue);
                return names.Distinct(StringComparer.Ordinal).ToArray();
            }
        }

        internal static string ValidateAssetPath(string value, string extension)
        {
            string path = (value ?? string.Empty).Trim().Replace('\\', '/');
            if (!path.StartsWith("Assets/", StringComparison.Ordinal) ||
                !path.EndsWith(extension, StringComparison.OrdinalIgnoreCase) ||
                path.Contains("../") || path.Contains("/../"))
                throw new ArgumentException($"Path must be contained under Assets/ and end in {extension}.");
            return path;
        }

        internal static string ValidateAnyAssetPath(string value)
        {
            string path = (value ?? string.Empty).Trim().Replace('\\', '/');
            if ((!path.StartsWith("Assets/", StringComparison.Ordinal) &&
                 !path.StartsWith("Packages/", StringComparison.Ordinal)) ||
                path.Contains("../") || path.Contains("/../"))
                throw new ArgumentException("AssetPath must be contained under Assets/ or Packages/.");
            return path;
        }

        internal static string ValidateWritableAssetPath(string value)
        {
            string path = ValidateAnyAssetPath(value);
            if (!path.StartsWith("Assets/", StringComparison.Ordinal))
                throw new UnauthorizedAccessException("Writable asset paths must be contained under Assets/.");
            return path;
        }

        internal static string JoinProcessArguments(IEnumerable<string> arguments) =>
            string.Join(" ", (arguments ?? Enumerable.Empty<string>()).Select(QuoteProcessArgument));

        internal static string QuoteProcessArgument(string value)
        {
            string argument = value ?? string.Empty;
            if (argument.Length > 0 && !argument.Any(character => char.IsWhiteSpace(character) || character == '\"'))
                return argument;
            StringBuilder quoted = new StringBuilder("\"");
            int backslashes = 0;
            foreach (char character in argument)
            {
                if (character == '\\')
                {
                    backslashes++;
                    continue;
                }
                if (character == '\"')
                {
                    quoted.Append('\\', backslashes * 2 + 1);
                    quoted.Append(character);
                    backslashes = 0;
                    continue;
                }
                quoted.Append('\\', backslashes);
                quoted.Append(character);
                backslashes = 0;
            }
            quoted.Append('\\', backslashes * 2);
            quoted.Append('\"');
            return quoted.ToString();
        }

        internal static void AppendBounded(StringBuilder builder, string line, int maximum, object gate)
        {
            if (line == null) return;
            lock (gate)
            {
                if (builder.Length >= maximum) return;
                int remaining = maximum - builder.Length;
                string addition = line + Environment.NewLine;
                builder.Append(addition, 0, Math.Min(remaining, addition.Length));
            }
        }

        internal static IEnumerable<string> CleanNames(IEnumerable<string> values) =>
            (values ?? Enumerable.Empty<string>())
            .Where(value => !string.IsNullOrWhiteSpace(value))
            .Select(value => value.Trim())
            .Distinct(StringComparer.Ordinal);

        internal static int StablePositiveId(string value)
        {
            unchecked
            {
                int hash = 17;
                foreach (char character in value) hash = hash * 31 + character;
                return hash == int.MinValue ? int.MaxValue : Math.Abs(hash);
            }
        }

        internal static string ResolveWorkspacePath(string relative, bool allowDirectory)
        {
            string root = Path.GetFullPath(Path.Combine(Application.dataPath, ".."))
                .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
            string rootPrefix = root + Path.DirectorySeparatorChar;
            string requested = string.IsNullOrWhiteSpace(relative) ? "." : relative.Trim();
            string full = Path.GetFullPath(Path.Combine(root,
                requested.Replace('/', Path.DirectorySeparatorChar)));
            if (!string.Equals(full, root, StringComparison.OrdinalIgnoreCase) &&
                !full.StartsWith(rootPrefix, StringComparison.OrdinalIgnoreCase))
                throw new UnauthorizedAccessException("Path escapes the Unity workspace.");
            if (allowDirectory ? !Directory.Exists(full) : !File.Exists(full))
                throw new FileNotFoundException($"Workspace path does not exist: {requested}");
            return full;
        }

        internal static string ResolveWorkspaceOutputPath(string relative)
        {
            string root = Path.GetFullPath(Path.Combine(Application.dataPath, ".."))
                .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) +
                Path.DirectorySeparatorChar;
            string full = Path.GetFullPath(Path.Combine(root,
                (relative ?? string.Empty).Trim().Replace('/', Path.DirectorySeparatorChar)));
            if (!full.StartsWith(root, StringComparison.OrdinalIgnoreCase) || string.Equals(full, root, StringComparison.OrdinalIgnoreCase))
                throw new UnauthorizedAccessException("Output path escapes the Unity workspace or targets its root.");
            string parent = Path.GetDirectoryName(full);
            if (string.IsNullOrWhiteSpace(parent)) throw new ArgumentException("Output path requires a parent directory.");
            Directory.CreateDirectory(parent);
            return full;
        }

        internal static Type ResolveType(string typeName, Type requiredBaseType)
        {
            string requested = typeName?.Trim();
            Type type = Type.GetType(requested ?? string.Empty, false) ??
                AppDomain.CurrentDomain.GetAssemblies()
                    .Select(assembly => assembly.GetType(requested ?? string.Empty, false))
                    .FirstOrDefault(candidate => candidate != null);
            if (type == null || !requiredBaseType.IsAssignableFrom(type) || type.IsAbstract || type.ContainsGenericParameters)
                throw new ArgumentException($"Type '{typeName}' is not a concrete {requiredBaseType.FullName} type.");
            return type;
        }

        internal static async Task AwaitPackageRequest(Request request, CapabilityContext context)
        {
            while (!request.IsCompleted)
            {
                context.CancellationToken.ThrowIfCancellationRequested();
                TaskCompletionSource<bool> nextUpdate = new TaskCompletionSource<bool>();
                EditorApplication.CallbackFunction update = null;
                update = () =>
                {
                    EditorApplication.update -= update;
                    nextUpdate.TrySetResult(true);
                };
                EditorApplication.update += update;
                await nextUpdate.Task;
            }
            if (request.Status == StatusCode.Failure)
                throw new InvalidOperationException(request.Error?.message ?? "Unity Package Manager request failed.");
        }

        internal static string ToWorkspaceRelative(string fullPath)
        {
            string root = Path.GetFullPath(Path.Combine(Application.dataPath, ".."))
                .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
            string normalized = Path.GetFullPath(fullPath);
            return string.Equals(root, normalized, StringComparison.OrdinalIgnoreCase)
                ? "."
                : normalized.Substring(root.Length + 1).Replace('\\', '/');
        }

        internal static bool GlobMatches(string fileName, string glob)
        {
            string pattern = "^" + Regex.Escape(glob.Trim())
                .Replace("\\*", ".*").Replace("\\?", ".") + "$";
            return Regex.IsMatch(fileName, pattern, RegexOptions.IgnoreCase);
        }

        internal static int CountOccurrences(string value, string fragment)
        {
            int count = 0;
            int offset = 0;
            while ((offset = value.IndexOf(fragment, offset, StringComparison.Ordinal)) >= 0)
            {
                count++;
                offset += fragment.Length;
            }
            return count;
        }

        internal static string Sha256(string value)
        {
            using (SHA256 algorithm = SHA256.Create())
            {
                byte[] hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(value));
                return string.Concat(hash.Select(item => item.ToString("x2")));
            }
        }

        internal static object DescribeFileArtifact(string relativePath, string kind, string mediaType)
        {
            string full = Path.GetFullPath(Path.Combine(Application.dataPath, "..",
                relativePath.Replace('/', Path.DirectorySeparatorChar)));
            byte[] bytes = File.ReadAllBytes(full);
            string hash;
            using (SHA256 algorithm = SHA256.Create())
                hash = string.Concat(algorithm.ComputeHash(bytes).Select(item => item.ToString("x2")));
            return new
            {
                Kind = kind,
                Path = relativePath,
                MediaType = mediaType,
                Sha256 = hash,
                Bytes = bytes.LongLength
            };
        }
    }
}
