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

using AIRuntime.Runtime.Agents;
using AIRuntime.Runtime.Agents.Runtime;
using AIRuntime.Runtime.Capabilities;
using AIRuntime.Runtime.Capabilities.Permissions;
using AIRuntime.Runtime.Filesystem;
using AIRuntime.Runtime.Filesystem.Models;
using AIRuntime.Runtime.Providers;
using AIRuntime.Runtime.Services.Agent;

using Newtonsoft.Json.Linq;

using UnityEditor;
using UnityEngine;
using UnityEngine.Serialization;

using RuntimeFacade =
    AIRuntime.Runtime.AIRuntime;


namespace AIRuntime.Editor
{
    /// <summary>
    /// Interactive Unity Editor chat client for AIRuntime providers.
    ///
    /// API keys are deliberately kept in a non-serialized field and
    /// therefore last only for the lifetime of this editor session.
    /// </summary>
    public sealed class AIRuntimeChatConsole :
        EditorWindow
    {
        private sealed class TranscriptEntry
        {
            public string Role;

            public string Content;
        }

        private sealed class IntentAwareCreateDirectoryCapability :
            ICapability
        {
            private readonly CreateDirectoryCapability _inner =
                new CreateDirectoryCapability();

            private readonly Func<CreateDirectoryArguments> _intentProvider;


            public IntentAwareCreateDirectoryCapability(
                Func<CreateDirectoryArguments> intentProvider)
            {
                _intentProvider =
                    intentProvider;
            }


            public string Name =>
                _inner.Name;

            public string Description =>
                _inner.Description;

            public CapabilityPermission Permission =>
                _inner.Permission;

            public JObject Parameters =>
                _inner.Parameters;

            public Type ArgumentType =>
                _inner.ArgumentType;


            public Task<object> ExecuteObjectAsync(
                object arguments,
                CapabilityContext context)
            {
                CreateDirectoryArguments explicitIntent =
                    _intentProvider?.Invoke();


                return ((ICapability)_inner).ExecuteObjectAsync(
                    explicitIntent ??
                    arguments,
                    context);
            }
        }


        private static readonly Regex CreateDirectoryIntentPattern =
            new Regex(
                "^\\s*(?:please\\s+)?(?:add|create)\\s+[\"'](?<name>[^\"']+)[\"']\\s+(?:to|in|inside)\\s+(?<parent>.+?)\\s*[.!]?\\s*$",
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant);

        private static readonly Regex CreateDirectoryPathIntentPattern =
            new Regex(
                "^\\s*(?:please\\s+)?create\\s+(?<path>.+?)\\s*[.!]?\\s*$",
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant);



        private sealed class EditorApprovalHandler :
            ICapabilityApprovalHandler
        {
            public Task<CapabilityApprovalResult>
                RequestApprovalAsync(
                    CapabilityApprovalRequest request,
                    CancellationToken cancellationToken)
            {
                cancellationToken
                    .ThrowIfCancellationRequested();


                string arguments =
                    request.Arguments ??
                    string.Empty;


                if (arguments.Length > 2000)
                {
                    arguments =
                        arguments.Substring(
                            0,
                            2000) +
                        "\n…";
                }


                bool approved =
                    EditorUtility.DisplayDialog(
                        "Approve AIRuntime tool",
                        $"Capability: {request.CapabilityName}\n" +
                        $"Permission: {request.Permission}\n\n" +
                        $"Arguments:\n{arguments}",
                        "Approve once",
                        "Reject");


                return Task.FromResult(
                    approved
                        ? CapabilityApprovalResult.Approve(
                            "Approved in the AIRuntime chat console.")
                        : CapabilityApprovalResult.Reject(
                            "Rejected in the AIRuntime chat console."));
            }
        }


        private sealed class ChatConsoleObserver :
            IAgentObserver
        {
            private readonly Action<AgentEvent> _onEvent;


            public ChatConsoleObserver(
                Action<AgentEvent> onEvent)
            {
                _onEvent =
                    onEvent;
            }


            public void OnAgentEvent(
                AgentEvent agentEvent)
            {
                _onEvent?.Invoke(
                    agentEvent);
            }
        }


        private static IReadOnlyList<IAIRuntimeEditorCapabilityPlugin>
            FileTools =>
                AIRuntimeEditorPluginCatalog.GetCapabilityPlugins(
                    AIRuntimeEditorPluginHost.ChatConsole);

        private static readonly HashSet<string> AssetMutationTools =
            new HashSet<string>(
                new[]
                {
                    "write_file",
                    "append_file",
                    "copy_file",
                    "create_directory",
                    "create_editor_scene",
                    "create_game_object",
                    "save_editor_scene",
                    "move_file",
                    "delete_file",
                    "delete_directory"
                },
                StringComparer.Ordinal);


        private const float MinimumTranscriptHeight =
            120;

        private const float MinimumComposerHeight =
            54;

        private const float DefaultComposerHeight =
            90;

        private const float MinimumConfigurationHeight =
            120;

        private const float DefaultConfigurationHeight =
            260;

        private const string ToolAnswerPolicy =
            "Tool-result response rules:\n" +
            "- Follow the user's requested output shape exactly.\n" +
            "- When the user asks for a complete, full, all, or every-item list, include every matching item returned by the tool exactly once.\n" +
            "- Never summarize, sample, group, replace, or omit items from a requested complete list.\n" +
            "- Do not add introductions, observations, explanations, headings, conclusions, or offers of further help unless the user asks for them.\n" +
            "- For a complete list_directory_tree answer, output every Entries[].Path value as one plain path per line and nothing else.\n" +
            "- For all files in the root or top-level directory, call list_directory with path=\".\", entryType=\"Files\", and outputFormat=\"PathsOnly\", then output every returned path exactly once as one plain path per line and nothing else.\n" +
            "- When the user asks what a named file is, says, contains, documents, or defines, you must call read_file with that exact path before answering.\n" +
            "- Preserve every slash, period, and underscore in a user-provided path. Never rewrite path separators or filename extensions as underscores.\n" +
            "- Never guess or infer file contents from a filename, path, extension, or typical usage. Base the answer only on the returned file contents.\n" +
            "- When the user asks about the selected object, current selection, selected GameObject, or selected asset, you must call get_editor_selection immediately before answering. Never invent selection names, transforms, components, prefab status, or other details.\n" +
            "- Use create_editor_selection when the user asks you to select, focus, or clear Unity Editor objects or assets. Resolve live scene targets with read tools first and pass exact instance IDs; use exact Asset paths for Project-window assets. Never invent a target identity.\n" +
            "- When the user asks to inspect, troubleshoot, diagnose, or explain a selected GameObject component, collider, Rigidbody, renderer, or other component setting, call get_editor_selection and then inspect_editor_component before answering. Base every diagnosis on returned properties and Diagnostics; never substitute generic troubleshooting advice for inspection.\n" +
            "- inspect_editor_component is read-only. Never claim it fixed anything. When the user requests a supported property fix, call get_serialized_properties, copy the exact property path and current Value, then call set_serialized_property with useExpectedValue=true. Report success only when Verified=true; otherwise give options tied to the inspection evidence.\n" +
            "- Use inspect_game_object, get_hierarchy_tree, find_game_objects, inspect_scene, and inspect_prefab for live Unity structure instead of inferring structure from scene or prefab filenames.\n" +
            "- To author a scene, call create_editor_scene first, create objects with create_game_object, add or configure components through the component tools, and finish with save_editor_scene. Never generate or edit .unity YAML with write_file.\n" +
            "- get_console_logs contains only logs captured during the current Editor domain. get_compilation_errors contains only the current captured compilation generation; never describe either as complete historical data.\n" +
            "- run_edit_mode_tests PreviewOnly=true validates a filter but does not run tests. For a real run, FailCount greater than zero means verification failed even when the capability call completed successfully.\n" +
            "- run_play_mode_tests follows the same result contract but executes Unity PlayMode tests. Use it for integrated gameplay smoke tests; PreviewOnly=true still does not run tests.\n" +
            "- When get_editor_selection returns IsFolder=true, treat it as a selected Project-window folder and preserve FolderPath exactly for any follow-up filesystem tool.\n" +
            "- When asked to list files in a selected Project-window folder, call list_directory with path set to FolderPath, entryType=\"Files\", outputFormat=\"PathsOnly\", and includeMetaFiles=false. Unity .meta sidecars are not user files and must not be reported unless explicitly requested.\n" +
            "- Missing-component checks apply only to selected GameObjects. When a GameObject reports HasMissingComponents=true, explicitly state MissingComponentCount and include every [Missing Script] entry.\n" +
            "- MissingComponentCount=0 or HasMissingComponents=false means the GameObject is clean and is never an error. Non-GameObject assets such as TextAsset files do not require component or missing-script commentary unless the user asks for it.\n" +
            "- Before adding or removing a Unity component, call get_editor_selection immediately before the mutation. For add_editor_component use Objects[].GameObjectInstanceId. To remove a normal component, use ComponentDetails[].InstanceId; never use ComponentCount or a list position as an ID.\n" +
            "- To remove [Missing Script], call remove_editor_component with Objects[].GameObjectInstanceId and removeMissingScripts=true. Missing scripts have InstanceId=0 and cannot be removed through ComponentInstanceId.\n" +
            "- When the user explicitly asks to remove missing components from all selected objects, call remove_editor_component once with removeMissingScripts=true and removeFromAllSelected=true.\n" +
            "- Never invent an instance ID. If a missing-script target ID is unavailable, omit it and set removeMissingScripts=true; the capability will proceed only when exactly one selected GameObject contains missing scripts.\n" +
            "- Use add_editor_component to add a component and remove_editor_component to remove one. Never report success until the mutation tool returns Succeeded=true.\n" +
            "- For filesystem mutations, call the matching mutation tool before reporting success. Never claim a write, append, move, copy, create, or delete succeeded from intent alone.\n" +
            "- This workspace root is the Unity project root. When the user gives an asset path beginning with Runtime/, Editor/, Tests/, Samples/, or Scenes/ without Assets/, prefix it with Assets/ before calling a filesystem tool.\n" +
            "- When asked to create or add a directory named NAME in or to PARENT, call create_directory with parentPath set to the complete PARENT path and directoryName set to NAME. Preserve every segment of parentPath.\n" +
            "- For an 'add NAME to PARENT' request, the exact parent must already exist. If it does not, report that it cannot be created there; never shorten the parent path to an existing ancestor.\n" +
            "- When asked to create a complete directory path, set createParents to true. When asked to add a named child to a parent, set createParents to false.\n" +
            "- When a filesystem tool returns a Path field, copy that field verbatim in the final response. Never reformat, sanitize, encode, or rename it.\n" +
            "- When asked to append text, call append_file rather than write_file. If append_file is unavailable, denied, cancelled, or fails, say so plainly and do not report success.\n" +
            "- Never batch a filesystem mutation and its verification in the same response. Call the mutation first, wait for its tool result, then call read_file in the next response.\n" +
            "- When verification is requested, call read_file after the mutation result and compare its returned contents with the requested result before reporting success.\n" +
            "- If Truncated is true, do not claim the list is complete; state that the tool limit was reached.";


        [SerializeField]
        private string _providerId =
            "Ollama";

        [FormerlySerializedAs("_provider")]
        [SerializeField]
        [HideInInspector]
        private int _legacyProvider =
            -1;

        private string _provider
        {
            get => _providerId;
            set => _providerId = value;
        }

        [SerializeField] private bool _supportsImageInput;

        [SerializeField]
        private string _model =
            "qwen2.5:7b-instruct";

        [SerializeField]
        private string _baseUrl =
            "http://localhost:11434/v1";

        [SerializeField]
        private string _workspaceRoot;

        [SerializeField]
        private string _systemPrompt =
            string.Empty;

        [SerializeField]
        private string _httpReferer =
            string.Empty;

        [SerializeField]
        private string _applicationTitle =
            "AIRuntime Unity Editor";

        [SerializeField]
        private bool _showConfiguration =
            true;

        [SerializeField]
        private float _composerHeight =
            DefaultComposerHeight;

        [SerializeField]
        private float _configurationHeight =
            DefaultConfigurationHeight;

        [SerializeField]
        private bool _useAgent;

        [SerializeField]
        private bool _showAgentConfiguration =
            true;

        [SerializeField]
        private string _agentPresetId =
            "Custom";

        [FormerlySerializedAs("_agentPreset")]
        [SerializeField]
        [HideInInspector]
        private int _legacyAgentPreset =
            -1;

        private string _agentPreset
        {
            get => _agentPresetId;
            set => _agentPresetId = value;
        }

        [SerializeField]
        private string _agentId =
            "EditorAgent";

        [SerializeField]
        private string _agentDisplayName =
            "Editor Agent";

        [SerializeField]
        private string _agentDescription =
            "Interactive agent configured in the AIRuntime chat console.";

        [SerializeField]
        private string _agentSystemPrompt =
            "You are a helpful assistant working inside the Unity Editor.";

        [SerializeField]
        private float _agentTemperature =
            0.2f;

        [SerializeField]
        private int _agentMaxTokens;

        [SerializeField]
        private bool _enableTools;

        [SerializeField]
        private bool _showToolConfiguration =
            true;

        [SerializeField]
        private List<string> _enabledTools =
            new List<string>
            {
                "read_file",
                "file_exists",
                "get_file_info",
                "list_directory",
                "list_directory_tree",
                "get_editor_selection",
                "inspect_editor_component",
                "get_serialized_properties",
                "inspect_game_object",
                "get_hierarchy_tree",
                "find_game_objects",
                "get_console_logs",
                "get_compilation_errors",
                "inspect_scene",
                "create_editor_scene",
                "create_game_object",
                "save_editor_scene",
                "inspect_prefab"
            };

        [SerializeField]
        private CapabilityPermissionDecision _writePermission =
            CapabilityPermissionDecision.RequireApproval;

        [SerializeField]
        private CapabilityPermissionDecision _destructivePermission =
            CapabilityPermissionDecision.RequireApproval;

        [SerializeField]
        private CapabilityPermissionDecision _processPermission =
            CapabilityPermissionDecision.RequireApproval;

        [SerializeField]
        private CapabilityPermissionDecision _networkPermission =
            CapabilityPermissionDecision.RequireApproval;

        [SerializeField]
        private string _configurationFilePath;


        // Never serialize or persist credentials.
        private string _apiKey =
            string.Empty;

        private string _prompt =
            string.Empty;

        private string _status =
            "Ready";

        private string _runtimeSignature;

        private Vector2 _transcriptScroll;

        private Vector2 _configurationScroll;

        private Vector2 _promptScroll;

        private RuntimeFacade _runtime;

        private AIRuntimeEditorRuntimePluginContext _runtimePluginContext;

        private IDisposable _runtimePluginAttachments;

        private AgentRegistry _agentRegistry;

        private CancellationTokenSource _sendCancellation;

        private bool _isSending;

        private bool _assetRefreshQueued;

        private bool _assetMutationPending;

        private string _verifiedMutationResponse;

        private CreateDirectoryArguments _pendingCreateDirectoryIntent;

        private readonly object _streamSync =
            new object();

        private readonly StringBuilder _pendingStreamText =
            new StringBuilder();

        private TranscriptEntry _streamingEntry;

        private GUIStyle _assistantStyle;

        private GUIStyle _roleStyle;

        private GUIStyle _transcriptStyle;

        private GUIStyle _userStyle;


        private readonly List<TranscriptEntry> _transcript =
            new List<TranscriptEntry>();


        [MenuItem("Window/AIRuntime/Chat Console")]
        public static void Open()
        {
            AIRuntimeChatConsole window =
                GetWindow<AIRuntimeChatConsole>();


            window.titleContent =
                new GUIContent(
                    "AI Chat");

            window.minSize =
                new Vector2(
                    460,
                    520);

            window.Show();
        }


        internal static void OpenAgentConfiguration(
            string configurationPath)
        {
            AIRuntimeChatConsole window =
                GetWindow<AIRuntimeChatConsole>();

            window.titleContent =
                new GUIContent(
                    "AI Chat");

            window.minSize =
                new Vector2(
                    460,
                    520);

            window.Show();
            window.Focus();

            window._configurationFilePath =
                configurationPath;

            window.LoadAgentConfiguration();
            window.Repaint();
        }


        private void MigrateLegacyPluginSelections()
        {
            string[] legacyProviders =
            {
                "Ollama",
                "OpenAI",
                "NousPortal",
                "OpenRouter",
                AIRuntimeEditorProviderNames.NvidiaDeveloper
            };

            if (_legacyProvider >= 0 &&
                _legacyProvider < legacyProviders.Length)
            {
                _providerId =
                    legacyProviders[_legacyProvider];

                _legacyProvider =
                    -1;
            }

            string[] legacyAgentPresets =
            {
                "Custom",
                "Architect",
                "Developer",
                "Reviewer",
                "Testing"
            };

            if (_legacyAgentPreset >= 0 &&
                _legacyAgentPreset < legacyAgentPresets.Length)
            {
                _agentPresetId =
                    legacyAgentPresets[_legacyAgentPreset];

                _legacyAgentPreset =
                    -1;
            }
        }


        private void OnEnable()
        {
            MigrateLegacyPluginSelections();

            if (string.IsNullOrWhiteSpace(
                    _provider))
            {
                _provider =
                    "Ollama";

                ApplyProviderDefaults(
                    _provider,
                    replaceModel: string.IsNullOrWhiteSpace(
                        _model));
            }
            else if (AIRuntimeEditorPluginCatalog.TryGetProviderPlugin(
                         _provider,
                         out IAIRuntimeEditorProviderPlugin selectedProvider))
            {
                _provider =
                    selectedProvider.Id;
            }


            if (string.IsNullOrWhiteSpace(
                    _agentPreset))
            {
                _agentPreset =
                    "Custom";
            }
            else if (AIRuntimeEditorPluginCatalog.TryGetAgentPlugin(
                         _agentPreset,
                         out IAIRuntimeEditorAgentPlugin selectedAgent))
            {
                _agentPreset =
                    selectedAgent.Id;
            }


            if (string.IsNullOrWhiteSpace(
                    _workspaceRoot))
            {
                _workspaceRoot =
                    GetProjectRoot();
            }


            if (string.IsNullOrWhiteSpace(
                    _baseUrl) &&
                AIRuntimeEditorPluginCatalog.TryGetProviderPlugin(
                    _provider,
                    out _))
            {
                ApplyProviderDefaults(
                    _provider,
                    replaceModel: false);
            }


            if (string.IsNullOrWhiteSpace(
                    _configurationFilePath))
            {
                _configurationFilePath =
                    GetDefaultAgentConfigurationPath();
            }


            _enabledTools =
                _enabledTools ??
                new List<string>();


            if (!_enabledTools.Contains(
                    "get_editor_selection"))
            {
                _enabledTools.Add(
                    "get_editor_selection");
            }

            if (!_enabledTools.Contains(
                    "inspect_editor_component"))
            {
                _enabledTools.Add(
                    "inspect_editor_component");
            }
        }


        private void OnDisable()
        {
            bool cancelledActiveRequest =
                _isSending;

            CancelSend();

            if (cancelledActiveRequest)
            {
                _runtimePluginContext?.Report(
                    AIRuntimeAgentActivityKind.RunCancelled,
                    "Chat window closed; request cancelled.");
            }

            _runtimePluginAttachments?.Dispose();

            _runtimePluginAttachments =
                null;

            _runtimePluginContext =
                null;

            _runtime =
                null;

            _runtimeSignature =
                null;

            EditorApplication.delayCall -=
                RefreshAssetDatabase;

            _assetRefreshQueued =
                false;

            _assetMutationPending =
                false;

            _verifiedMutationResponse =
                null;

            _streamingEntry =
                null;


            lock (_streamSync)
            {
                _pendingStreamText.Clear();
            }
        }


        private void Update()
        {
            DrainStreamingText();
        }


        private void OnGUI()
        {
            EnsureStyles();


            DrawHeader();


            using (
                new EditorGUI.DisabledScope(
                    _isSending))
            {
                DrawConfiguration();
            }

            if (_showConfiguration)
            {
                DrawConfigurationResizeHandle();
            }


            DrawTranscript();

            DrawComposer();

            DrawStatusBar();


            HandleSendShortcut(
                Event.current);
        }


        private void DrawHeader()
        {
            using (
                new EditorGUILayout.HorizontalScope(
                    EditorStyles.toolbar))
            {
                _showConfiguration =
                    GUILayout.Toggle(
                        _showConfiguration,
                        "Configuration",
                        EditorStyles.toolbarButton);

                if (GUILayout.Button(
                        "Raw debug",
                        EditorStyles.toolbarButton))
                {
                    AIRuntimeProviderDebugConsole.Open();
                }

                if (GUILayout.Button(
                        "Activity",
                        EditorStyles.toolbarButton))
                {
                    AIRuntimeAgentActivityWindow.Open();
                }


                GUILayout.FlexibleSpace();


                using (
                    new EditorGUI.DisabledScope(
                        _isSending ||
                        _transcript.Count == 0 &&
                        _runtime == null))
                {
                    if (GUILayout.Button(
                            "New conversation",
                            EditorStyles.toolbarButton))
                    {
                        StartNewConversation();
                    }
                }


                using (
                    new EditorGUI.DisabledScope(
                        _transcript.Count == 0))
                {
                    if (GUILayout.Button(
                            "Copy",
                            EditorStyles.toolbarButton))
                    {
                        EditorGUIUtility.systemCopyBuffer =
                            BuildTranscriptText();

                        _status =
                            "Transcript copied";
                    }
                }


                GUILayout.Space(
                    6);


                if (_isSending)
                {
                    if (GUILayout.Button(
                            "Cancel",
                            EditorStyles.toolbarButton,
                            GUILayout.Width(
                                62)))
                    {
                        CancelSend();
                    }
                }
                else
                {
                    using (
                        new EditorGUI.DisabledScope(
                            string.IsNullOrWhiteSpace(
                                _prompt)))
                    {
                        if (GUILayout.Button(
                                "Send",
                                EditorStyles.toolbarButton,
                                GUILayout.Width(
                                    62)))
                        {
                            SendPrompt();
                        }
                    }
                }
            }
        }


        private void DrawConfiguration()
        {
            if (!_showConfiguration)
                return;


            EditorGUILayout.BeginVertical(
                EditorStyles.helpBox);

            _configurationScroll =
                EditorGUILayout.BeginScrollView(
                    _configurationScroll,
                    GUILayout.Height(
                        GetConfigurationHeight()));


            IReadOnlyList<IAIRuntimeEditorProviderPlugin> providerPlugins =
                AIRuntimeEditorPluginCatalog.GetProviderPlugins();

            List<string> providerIds =
                providerPlugins
                    .Select(plugin => plugin.Id)
                    .ToList();

            List<string> providerLabels =
                providerPlugins
                    .Select(plugin => plugin.DisplayName)
                    .ToList();

            if (!string.IsNullOrWhiteSpace(_provider) &&
                !AIRuntimeEditorPluginCatalog.TryGetProviderPlugin(
                    _provider,
                    out _))
            {
                providerIds.Insert(
                    0,
                    _provider);

                providerLabels.Insert(
                    0,
                    $"Missing plugin: {_provider}");
            }


            if (providerIds.Count > 0)
            {
                int providerIndex =
                    Math.Max(
                        0,
                        providerIds.FindIndex(
                            id => string.Equals(
                                id,
                                _provider,
                                StringComparison.OrdinalIgnoreCase)));

                int selectedProviderIndex =
                    EditorGUILayout.Popup(
                        "Provider",
                        providerIndex,
                        providerLabels.ToArray());

                string selectedProvider =
                    providerIds[
                        Mathf.Clamp(
                            selectedProviderIndex,
                            0,
                            providerIds.Count - 1)];


                if (!string.Equals(
                        selectedProvider,
                        _provider,
                        StringComparison.OrdinalIgnoreCase))
                {
                    _provider =
                        selectedProvider;

                    _apiKey =
                        string.Empty;

                    ApplyProviderDefaults(
                        _provider,
                        replaceModel: true);
                }
            }
            else
            {
                EditorGUILayout.HelpBox(
                    "No editor provider plugins are registered.",
                    MessageType.Error);
            }


            bool providerAvailable =
                AIRuntimeEditorPluginCatalog.TryGetProviderPlugin(
                    _provider,
                    out IAIRuntimeEditorProviderPlugin selectedProviderPlugin);

            if (!providerAvailable)
            {
                EditorGUILayout.HelpBox(
                    $"Provider plugin '{_provider}' is not currently installed. Its saved selection has been retained.",
                    MessageType.Error);
            }


            EditorGUI.BeginChangeCheck();
            _model =
                EditorGUILayout.TextField(
                    "Model",
                    _model);

            _baseUrl =
                EditorGUILayout.TextField(
                    "Base URL",
                    _baseUrl);

            if (EditorGUI.EndChangeCheck()) _supportsImageInput = false;
            _supportsImageInput = EditorGUILayout.Toggle(
                new GUIContent("Image input", "Enable only when this model at this Base URL accepts images. Unknown support stays off."),
                _supportsImageInput);


            if (providerAvailable &&
                selectedProviderPlugin.RequiresApiKey)
            {
                _apiKey =
                    EditorGUILayout.PasswordField(
                        "API key",
                        _apiKey);


                EditorGUILayout.HelpBox(
                    "The API key is kept in memory for this editor session, is not saved, and will be sent only to the configured Base URL.",
                    MessageType.Info);
            }
            else if (providerAvailable)
            {
                _apiKey =
                    string.Empty;
            }


            if (providerAvailable &&
                selectedProviderPlugin is IAIRuntimeEditorProviderConfigurationPlugin configurationPlugin)
            {
                AIRuntimeEditorProviderSettings settings =
                    CreateProviderSettings();

                configurationPlugin.DrawAdditionalSettings(
                    settings);

                ApplyProviderSettings(
                    settings);

                if (!string.IsNullOrWhiteSpace(
                        configurationPlugin.ConfigurationWarning))
                {
                    EditorGUILayout.HelpBox(
                        configurationPlugin.ConfigurationWarning,
                        MessageType.Warning);
                }
            }


            DrawWorkspaceField();


            _enableTools =
                EditorGUILayout.Toggle(
                    "Enable tool calling",
                    _enableTools);


            if (_enableTools)
            {
                DrawToolConfiguration();
            }


            _useAgent =
                EditorGUILayout.Toggle(
                    "Use agent execution",
                    _useAgent);


            if (_useAgent)
            {
                DrawAgentConfiguration();
            }
            else
            {
                EditorGUILayout.LabelField(
                    "System prompt");

                _systemPrompt =
                    EditorGUILayout.TextArea(
                        _systemPrompt,
                        GUILayout.MinHeight(
                            46));
            }


            EditorGUILayout.EndScrollView();

            EditorGUILayout.EndVertical();
        }


        private void DrawToolConfiguration()
        {
            _showToolConfiguration =
                EditorGUILayout.Foldout(
                    _showToolConfiguration,
                    $"Workspace tools ({_enabledTools.Count})",
                    true);


            if (!_showToolConfiguration)
            {
                EditorGUILayout.HelpBox(
                    "Enabled tools are advertised to the model and remain contained inside the selected workspace.",
                    MessageType.Info);

                return;
            }


            EditorGUI.indentLevel++;


            using (
                new EditorGUILayout.HorizontalScope())
            {
                EditorGUILayout.PrefixLabel(
                    "Selection");


                if (GUILayout.Button(
                        "Read only",
                        EditorStyles.miniButton))
                {
                    SetEnabledTools(
                        CapabilityPermission.ReadOnly);
                }


                if (GUILayout.Button(
                        "All",
                        EditorStyles.miniButton))
                {
                    _enabledTools =
                        FileTools
                            .Select(
                                tool =>
                                    tool.Name)
                            .ToList();
                }


                if (GUILayout.Button(
                        "None",
                        EditorStyles.miniButton))
                {
                    _enabledTools.Clear();
                }
            }


            HashSet<string> availableToolNames =
                new HashSet<string>(
                    FileTools.Select(tool => tool.Name),
                    StringComparer.Ordinal);

            string[] missingToolPlugins =
                _enabledTools
                    .Where(tool =>
                        !string.IsNullOrWhiteSpace(tool) &&
                        !availableToolNames.Contains(tool))
                    .Distinct(StringComparer.Ordinal)
                    .OrderBy(tool => tool, StringComparer.Ordinal)
                    .ToArray();

            if (missingToolPlugins.Length > 0)
            {
                EditorGUILayout.LabelField(
                    "Missing plugins",
                    EditorStyles.miniBoldLabel);

                foreach (string missingTool in missingToolPlugins)
                {
                    bool keep =
                        EditorGUILayout.ToggleLeft(
                            $"Missing plugin  ({missingTool})",
                            true);

                    if (!keep)
                    {
                        SetToolEnabled(
                            missingTool,
                            false);
                    }
                }

                EditorGUILayout.HelpBox(
                    "Install the missing capability plugins or deselect them before sending a request.",
                    MessageType.Error);
            }


            foreach (
                IGrouping<
                    CapabilityPermission,
                    IAIRuntimeEditorCapabilityPlugin> group
                in FileTools.GroupBy(
                    tool =>
                        tool.Permission))
            {
                EditorGUILayout.LabelField(
                    group.Key.ToString(),
                    EditorStyles.miniBoldLabel);


                foreach (
                    IAIRuntimeEditorCapabilityPlugin tool
                    in group)
                {
                    bool enabled =
                        IsToolEnabled(
                            tool.Name);

                    bool next =
                        EditorGUILayout.ToggleLeft(
                            $"{tool.Label}  ({tool.Name})",
                            enabled);


                    if (next != enabled)
                    {
                        SetToolEnabled(
                            tool.Name,
                            next);
                    }
                }
            }


            _writePermission =
                (CapabilityPermissionDecision)
                EditorGUILayout.EnumPopup(
                    "Write permission",
                    _writePermission);

            _destructivePermission =
                (CapabilityPermissionDecision)
                EditorGUILayout.EnumPopup(
                    "Destructive permission",
                    _destructivePermission);

            _processPermission =
                (CapabilityPermissionDecision)
                EditorGUILayout.EnumPopup(
                    "Process permission",
                    _processPermission);

            _networkPermission =
                (CapabilityPermissionDecision)
                EditorGUILayout.EnumPopup(
                    "Network permission",
                    _networkPermission);


            EditorGUILayout.HelpBox(
                "Read-only tools are allowed automatically. Write, destructive, process, and network tools have independent deny, allow, or approval policies. Unattended Kanban treats RequireApproval as denied until an interactive approval broker is available.",
                MessageType.Warning);


            EditorGUI.indentLevel--;
        }


        private bool IsToolEnabled(
            string toolName)
        {
            return _enabledTools.Any(
                enabledTool =>
                    string.Equals(
                        enabledTool,
                        toolName,
                        StringComparison.Ordinal));
        }


        private void SetToolEnabled(
            string toolName,
            bool enabled)
        {
            _enabledTools.RemoveAll(
                candidate =>
                    string.Equals(
                        candidate,
                        toolName,
                        StringComparison.Ordinal));


            if (enabled)
            {
                _enabledTools.Add(
                    toolName);
            }
        }


        private void SetEnabledTools(
            CapabilityPermission permission)
        {
            _enabledTools =
                FileTools
                    .Where(
                        tool =>
                            tool.Permission ==
                            permission)
                    .Select(
                        tool =>
                            tool.Name)
                    .ToList();
        }


        private void DrawAgentConfiguration()
        {
            _showAgentConfiguration =
                EditorGUILayout.Foldout(
                    _showAgentConfiguration,
                    "Agent configuration",
                    true);


            if (!_showAgentConfiguration)
            {
                EditorGUILayout.HelpBox(
                    $"{_agentDisplayName} · temperature {_agentTemperature:0.##} · " +
                    (_agentMaxTokens == 0
                        ? "provider token limit"
                        : $"{_agentMaxTokens} tokens"),
                    MessageType.None);

                return;
            }


            EditorGUI.indentLevel++;


            IReadOnlyList<IAIRuntimeEditorAgentPlugin> agentPlugins =
                AIRuntimeEditorPluginCatalog.GetAgentPlugins();

            List<string> presetIds =
                agentPlugins
                    .Select(plugin => plugin.Id)
                    .ToList();

            List<string> presetLabels =
                agentPlugins
                    .Select(plugin => plugin.DisplayName)
                    .ToList();

            if (!string.IsNullOrWhiteSpace(_agentPreset) &&
                !AIRuntimeEditorPluginCatalog.TryGetAgentPlugin(
                    _agentPreset,
                    out _))
            {
                presetIds.Insert(
                    0,
                    _agentPreset);

                presetLabels.Insert(
                    0,
                    $"Missing plugin: {_agentPreset}");
            }


            if (presetIds.Count > 0)
            {
                int presetIndex =
                    Math.Max(
                        0,
                        presetIds.FindIndex(
                            id => string.Equals(
                                id,
                                _agentPreset,
                                StringComparison.OrdinalIgnoreCase)));

                int selectedPresetIndex =
                    EditorGUILayout.Popup(
                        "Preset",
                        presetIndex,
                        presetLabels.ToArray());

                string selectedPreset =
                    presetIds[
                        Mathf.Clamp(
                            selectedPresetIndex,
                            0,
                            presetIds.Count - 1)];


                if (!string.Equals(
                        selectedPreset,
                        _agentPreset,
                        StringComparison.OrdinalIgnoreCase))
                {
                    _agentPreset =
                        selectedPreset;

                    ApplyAgentPreset(
                        _agentPreset);
                }
            }
            else
            {
                EditorGUILayout.HelpBox(
                    "No editor agent plugins are registered. Custom agent fields remain editable.",
                    MessageType.Warning);
            }


            if (!string.IsNullOrWhiteSpace(_agentPreset) &&
                !AIRuntimeEditorPluginCatalog.TryGetAgentPlugin(
                    _agentPreset,
                    out _))
            {
                EditorGUILayout.HelpBox(
                    $"Agent preset plugin '{_agentPreset}' is not currently installed. Its configured agent values have been retained.",
                    MessageType.Warning);
            }


            _agentId =
                EditorGUILayout.TextField(
                    "Agent ID",
                    _agentId);

            _agentDisplayName =
                EditorGUILayout.TextField(
                    "Display name",
                    _agentDisplayName);

            _agentDescription =
                EditorGUILayout.TextField(
                    "Description",
                    _agentDescription);


            float maximumTemperature =
                GetMaximumAgentTemperature();


            _agentTemperature =
                EditorGUILayout.Slider(
                    "Temperature",
                    _agentTemperature,
                    0,
                    maximumTemperature);

            _agentMaxTokens =
                Mathf.Max(
                    0,
                    EditorGUILayout.IntField(
                        "Maximum tokens (0 = provider)",
                        _agentMaxTokens));

            if (TryGetProviderConfigurationPlugin(
                    out IAIRuntimeEditorProviderConfigurationPlugin providerConfiguration) &&
                !string.IsNullOrWhiteSpace(
                    providerConfiguration.AgentOptionsWarning))
            {
                EditorGUILayout.HelpBox(
                    providerConfiguration.AgentOptionsWarning,
                    MessageType.Warning);
            }


            EditorGUILayout.LabelField(
                "Agent system prompt");

            _agentSystemPrompt =
                EditorGUILayout.TextArea(
                    _agentSystemPrompt,
                    GUILayout.MinHeight(
                        72));


            EditorGUILayout.HelpBox(
                _enableTools
                    ? "The selected workspace tools are available to this agent and filtered through its capability allow-list."
                    : "Tool calling is disabled, so the agent is configured for model conversation only.",
                MessageType.Info);

            DrawAgentFileControls();


            EditorGUI.indentLevel--;
        }


        private void DrawAgentFileControls()
        {
            EditorGUILayout.Space(
                6);

            EditorGUILayout.LabelField(
                "Saved agent",
                EditorStyles.boldLabel);


            using (
                new EditorGUILayout.HorizontalScope())
            {
                _configurationFilePath =
                    EditorGUILayout.TextField(
                        "File",
                        _configurationFilePath);


                if (GUILayout.Button(
                        "Browse",
                        GUILayout.Width(
                            64)))
                {
                    string directory =
                        GetConfigurationDirectory();

                    string fileName =
                        Path.GetFileName(
                            _configurationFilePath);

                    string selected =
                        EditorUtility.SaveFilePanel(
                            "Save AIRuntime agent configuration",
                            directory,
                            string.IsNullOrWhiteSpace(fileName)
                                ? "agent.json"
                                : fileName,
                            "json");


                    if (!string.IsNullOrWhiteSpace(
                            selected))
                    {
                        _configurationFilePath =
                            selected;
                    }
                }
            }


            EditorGUILayout.HelpBox(
                "The saved JSON file includes the API key in plain text. Keep this folder private and do not commit or share the file.",
                MessageType.Warning);


            using (
                new EditorGUILayout.HorizontalScope())
            {
                if (GUILayout.Button(
                        "Save agent"))
                {
                    SaveAgentConfiguration();
                }


                using (
                    new EditorGUI.DisabledScope(
                        !File.Exists(
                            _configurationFilePath)))
                {
                    if (GUILayout.Button(
                            "Load agent"))
                    {
                        LoadAgentConfiguration();
                    }
                }


                using (
                    new EditorGUI.DisabledScope(
                        !ConfigurationDirectoryExists()))
                {
                    if (GUILayout.Button(
                            "Show folder"))
                    {
                        EditorUtility.RevealInFinder(
                            GetConfigurationDirectory());
                    }
                }
            }
        }


        private void DrawWorkspaceField()
        {
            using (
                new EditorGUILayout.HorizontalScope())
            {
                _workspaceRoot =
                    EditorGUILayout.TextField(
                        "Workspace",
                        _workspaceRoot);


                if (GUILayout.Button(
                        "Browse",
                        GUILayout.Width(
                            64)))
                {
                    string selected =
                        EditorUtility.OpenFolderPanel(
                            "Select AIRuntime workspace",
                            _workspaceRoot,
                            string.Empty);


                    if (!string.IsNullOrWhiteSpace(
                            selected))
                    {
                        _workspaceRoot =
                            selected;
                    }
                }
            }
        }


        private void DrawTranscript()
        {
            float transcriptHeight =
                Mathf.Max(
                    MinimumTranscriptHeight,
                    position.height -
                    GetComposerHeight() -
                    (_showConfiguration
                        ? GetConfigurationHeight()
                        : 0) -
                    110);


            _transcriptScroll =
                EditorGUILayout.BeginScrollView(
                    _transcriptScroll,
                    EditorStyles.helpBox,
                    GUILayout.Height(
                        transcriptHeight),
                    GUILayout.ExpandWidth(
                        true));

            EditorGUILayout.BeginVertical(
                GUILayout.ExpandWidth(
                    true));


            if (_transcript.Count == 0)
            {
                GUILayout.Label(
                    "Choose a provider, enter a prompt, and start a conversation.",
                    _assistantStyle,
                    GUILayout.ExpandWidth(
                        true));
            }
            else
            {
                foreach (
                    TranscriptEntry entry
                    in _transcript)
                {
                    GUILayout.Label(
                        entry.Role,
                        _roleStyle,
                        GUILayout.ExpandWidth(
                            true));


                    DrawSelectableTranscriptContent(
                        entry);

                    GUILayout.Space(
                        10);
                }
            }


            if (_isSending)
            {
                GUILayout.Label(
                    "Assistant",
                    _roleStyle,
                    GUILayout.ExpandWidth(
                        true));

                GUILayout.Label(
                    "Waiting for response…",
                    _assistantStyle,
                    GUILayout.ExpandWidth(
                        true));
            }

            EditorGUILayout.EndVertical();

            EditorGUILayout.EndScrollView();
        }


        private void DrawSelectableTranscriptContent(
            TranscriptEntry entry)
        {
            GUIStyle style =
                string.Equals(
                    entry.Role,
                    "You",
                    StringComparison.Ordinal)
                    ? _userStyle
                    : _assistantStyle;

            GUIContent content =
                new GUIContent(
                    entry.Content ??
                    string.Empty);

            float availableWidth =
                Mathf.Max(
                    100,
                    EditorGUIUtility.currentViewWidth -
                    40);

            float contentHeight =
                Mathf.Max(
                    EditorGUIUtility.singleLineHeight,
                    style.CalcHeight(
                        content,
                        availableWidth));

            Rect contentRect =
                EditorGUILayout.GetControlRect(
                    false,
                    contentHeight,
                    GUILayout.ExpandWidth(
                        true));


            EditorGUI.SelectableLabel(
                contentRect,
                content.text,
                style);
        }


        private void DrawConfigurationResizeHandle()
        {
            Rect handleRect =
                GUILayoutUtility.GetRect(
                    1,
                    7,
                    GUILayout.ExpandWidth(
                        true));

            int controlId =
                GUIUtility.GetControlID(
                    FocusType.Passive,
                    handleRect);

            Event currentEvent =
                Event.current;


            EditorGUIUtility.AddCursorRect(
                handleRect,
                MouseCursor.ResizeVertical,
                controlId);


            switch (
                currentEvent.GetTypeForControl(
                    controlId))
            {
                case EventType.MouseDown:
                    if (currentEvent.button == 0 &&
                        handleRect.Contains(
                            currentEvent.mousePosition))
                    {
                        GUIUtility.hotControl =
                            controlId;

                        currentEvent.Use();
                    }

                    break;


                case EventType.MouseDrag:
                    if (GUIUtility.hotControl ==
                        controlId)
                    {
                        _configurationHeight =
                            Mathf.Clamp(
                                _configurationHeight +
                                currentEvent.delta.y,
                                MinimumConfigurationHeight,
                                GetMaximumConfigurationHeight());

                        currentEvent.Use();

                        Repaint();
                    }

                    break;


                case EventType.MouseUp:
                    if (GUIUtility.hotControl ==
                        controlId)
                    {
                        GUIUtility.hotControl =
                            0;

                        currentEvent.Use();
                    }

                    break;


                case EventType.Repaint:
                    Color handleColor =
                        EditorGUIUtility.isProSkin
                            ? new Color(
                                1,
                                1,
                                1,
                                0.16f)
                            : new Color(
                                0,
                                0,
                                0,
                                0.18f);


                    EditorGUI.DrawRect(
                        new Rect(
                            handleRect.x,
                            handleRect.center.y - 1,
                            handleRect.width,
                            2),
                        handleColor);

                    break;
            }
        }


        private void DrawComposer()
        {
            DrawComposerResizeHandle();


            EditorGUILayout.LabelField(
                "Prompt");


            _promptScroll =
                EditorGUILayout.BeginScrollView(
                    _promptScroll,
                    GUILayout.Height(
                        GetComposerHeight()));

            GUI.SetNextControlName(
                "AIRuntimePrompt");

            _prompt =
                EditorGUILayout.TextArea(
                    _prompt,
                    _transcriptStyle,
                    GUILayout.ExpandHeight(
                        true));

            EditorGUILayout.EndScrollView();


            using (
                new EditorGUILayout.HorizontalScope())
            {
                GUILayout.Label(
                    "Ctrl/Cmd + Enter to send",
                    EditorStyles.miniLabel);

                GUILayout.FlexibleSpace();


                if (_isSending)
                {
                    if (GUILayout.Button(
                            "Cancel",
                            GUILayout.Width(
                                90)))
                    {
                        CancelSend();
                    }
                }
                else
                {
                    using (
                        new EditorGUI.DisabledScope(
                            string.IsNullOrWhiteSpace(
                                _prompt)))
                    {
                        if (GUILayout.Button(
                                "Send",
                                GUILayout.Width(
                                    90)))
                        {
                            SendPrompt();
                        }
                    }
                }
            }
        }


        private void DrawComposerResizeHandle()
        {
            Rect handleRect =
                GUILayoutUtility.GetRect(
                    1,
                    7,
                    GUILayout.ExpandWidth(
                        true));

            int controlId =
                GUIUtility.GetControlID(
                    FocusType.Passive,
                    handleRect);

            Event currentEvent =
                Event.current;


            EditorGUIUtility.AddCursorRect(
                handleRect,
                MouseCursor.ResizeVertical,
                controlId);


            switch (
                currentEvent.GetTypeForControl(
                    controlId))
            {
                case EventType.MouseDown:
                    if (currentEvent.button == 0 &&
                        handleRect.Contains(
                            currentEvent.mousePosition))
                    {
                        GUIUtility.hotControl =
                            controlId;

                        currentEvent.Use();
                    }

                    break;


                case EventType.MouseDrag:
                    if (GUIUtility.hotControl ==
                        controlId)
                    {
                        _composerHeight =
                            Mathf.Clamp(
                                _composerHeight -
                                currentEvent.delta.y,
                                MinimumComposerHeight,
                                GetMaximumComposerHeight());

                        currentEvent.Use();

                        Repaint();
                    }

                    break;


                case EventType.MouseUp:
                    if (GUIUtility.hotControl ==
                        controlId)
                    {
                        GUIUtility.hotControl =
                            0;

                        currentEvent.Use();
                    }

                    break;


                case EventType.Repaint:
                    Color handleColor =
                        EditorGUIUtility.isProSkin
                            ? new Color(
                                1,
                                1,
                                1,
                                0.16f)
                            : new Color(
                                0,
                                0,
                                0,
                                0.18f);


                    EditorGUI.DrawRect(
                        new Rect(
                            handleRect.x,
                            handleRect.center.y - 1,
                            handleRect.width,
                            2),
                        handleColor);

                    break;
            }
        }


        private float GetComposerHeight()
        {
            _composerHeight =
                Mathf.Clamp(
                    _composerHeight,
                    MinimumComposerHeight,
                    GetMaximumComposerHeight());


            return _composerHeight;
        }


        private float GetMaximumComposerHeight()
        {
            return Mathf.Max(
                MinimumComposerHeight,
                position.height * 0.45f);
        }


        private float GetConfigurationHeight()
        {
            _configurationHeight =
                Mathf.Clamp(
                    _configurationHeight,
                    MinimumConfigurationHeight,
                    GetMaximumConfigurationHeight());


            return _configurationHeight;
        }


        private float GetMaximumConfigurationHeight()
        {
            return Mathf.Max(
                MinimumConfigurationHeight,
                position.height -
                GetComposerHeight() -
                MinimumTranscriptHeight -
                135);
        }


        private void DrawStatusBar()
        {
            using (
                new EditorGUILayout.HorizontalScope(
                    EditorStyles.helpBox))
            {
                GUILayout.Label(
                    _status,
                    EditorStyles.miniLabel);

                GUILayout.FlexibleSpace();

                GUILayout.Label(
                    _useAgent
                        ? $"{_provider} · {_agentDisplayName}"
                        : _provider,
                    EditorStyles.miniLabel);
            }
        }


        private void HandleSendShortcut(
            Event currentEvent)
        {
            if (currentEvent == null ||
                currentEvent.type !=
                EventType.KeyDown ||
                currentEvent.keyCode !=
                KeyCode.Return ||
                (!currentEvent.control &&
                 !currentEvent.command) ||
                _isSending ||
                string.IsNullOrWhiteSpace(
                    _prompt))
            {
                return;
            }


            currentEvent.Use();

            SendPrompt();
        }


        private async void SendPrompt()
        {
            if (_isSending ||
                string.IsNullOrWhiteSpace(
                    _prompt))
            {
                return;
            }


            string prompt =
                _prompt.Trim();

            _pendingCreateDirectoryIntent =
                ParseCreateDirectoryIntent(
                    prompt);


            try
            {
                EnsureRuntime();

                _runtimePluginContext?.UpdateRun(
                    _useAgent
                        ? _agentDisplayName
                        : "Assistant",
                    prompt,
                    _useAgent
                        ? "Agent execution"
                        : "Conversation",
                    Guid.NewGuid().ToString("N"),
                    "Working on the request.");
            }
            catch (Exception exception)
            {
                _pendingCreateDirectoryIntent =
                    null;

                _status =
                    exception.Message;

                AddTranscriptEntry(
                    "Error",
                    exception.Message);

                Repaint();

                return;
            }


            _prompt =
                string.Empty;

            _verifiedMutationResponse =
                null;

            AddTranscriptEntry(
                "You",
                prompt);


            _sendCancellation =
                new CancellationTokenSource();

            _isSending =
                true;

            _streamingEntry =
                new TranscriptEntry
                {
                    Role =
                        _useAgent
                            ? _agentDisplayName
                            : "Assistant",
                    Content =
                        string.Empty
                };

            _transcript.Add(
                _streamingEntry);

            _status =
                _useAgent
                    ? $"Running {_agentDisplayName} with {_runtime.PrimaryProvider.Name}…"
                    : $"Streaming from {_runtime.PrimaryProvider.Name}…";

            ScrollToBottom();

            Repaint();


            try
            {
                string response;


                using (
                    ProviderStreamingContext.Begin(
                        QueueStreamingText))
                {
                    response =
                        await ExecutePromptAsync(
                            prompt,
                            _sendCancellation.Token);
                }


                DrainStreamingText();

                string displayedResponse =
                    string.IsNullOrWhiteSpace(
                        _verifiedMutationResponse)
                        ? response
                        : _verifiedMutationResponse;


                _streamingEntry.Content =
                    string.IsNullOrEmpty(displayedResponse)
                        ? "(empty response)"
                        : displayedResponse;

                _status =
                    "Ready";

                _runtimePluginContext?.UpdateStage(
                    "Complete",
                    "Response completed.");
            }
            catch (OperationCanceledException)
            {
                _runtimePluginContext?.Report(
                    AIRuntimeAgentActivityKind.RunCancelled,
                    "Request cancelled.");

                AddTranscriptEntry(
                    "System",
                    "Request cancelled.");

                _status =
                    "Cancelled";
            }
            catch (TimeoutException exception)
            {
                _runtimePluginContext?.Report(
                    AIRuntimeAgentActivityKind.RunTimedOut,
                    "Request timed out.",
                    exception.Message);

                AddTranscriptEntry(
                    "System",
                    exception.Message);

                _status =
                    "Timed out";
            }
            catch (Exception exception)
            {
                _runtimePluginContext?.Report(
                    AIRuntimeAgentActivityKind.RunFailed,
                    "Request failed.",
                    exception.Message);

                if (exception.Message.StartsWith(
                        "Parent directory does not exist:",
                        StringComparison.Ordinal))
                {
                    AddTranscriptEntry(
                        _useAgent
                            ? _agentDisplayName
                            : "Assistant",
                        "Sorry, I can't do that. " +
                        exception.Message);

                    _status =
                        "Ready";
                }
                else
                {
                    AddTranscriptEntry(
                        "Error",
                        exception.Message);

                    _status =
                        "Request failed";
                }
            }
            finally
            {
                _sendCancellation?.Dispose();

                _sendCancellation =
                    null;

                _isSending =
                    false;

                _pendingCreateDirectoryIntent =
                    null;

                DrainStreamingText();


                if (_streamingEntry !=
                        null &&
                    string.IsNullOrEmpty(
                        _streamingEntry.Content))
                {
                    _transcript.Remove(
                        _streamingEntry);
                }


                _streamingEntry =
                    null;

                ScrollToBottom();


                if (this != null)
                {
                    Repaint();
                }
            }
        }


        private void EnsureRuntime()
        {
            ValidateConfiguration();


            string signature =
                BuildRuntimeSignature();


            if (_runtime != null &&
                string.Equals(
                    _runtimeSignature,
                    signature,
                    StringComparison.Ordinal))
            {
                return;
            }


            bool replacedExistingRuntime =
                _runtime != null;


            RuntimeFacade runtime =
                new RuntimeFacade(
                    _workspaceRoot.Trim());


            runtime.SystemPrompt =
                _useAgent
                    ? string.Empty
                    : BuildEffectiveSystemPrompt(
                        _systemPrompt);

            runtime.UseProvider(
                CreateProvider());


            ConfigureTools(
                runtime);


            AgentRegistry agentRegistry =
                _useAgent
                    ? CreateAgentRegistry()
                    : null;

            AIRuntimeEditorRuntimePluginContext pluginContext =
                new AIRuntimeEditorRuntimePluginContext(
                    "Chat Console",
                    _useAgent
                        ? _agentDisplayName
                        : "Assistant",
                    string.IsNullOrWhiteSpace(
                        _prompt)
                        ? "Interactive conversation"
                        : _prompt,
                    "Preparing");

            IDisposable pluginAttachments =
                AIRuntimeEditorRuntimePluginRegistry.Attach(
                    runtime,
                    pluginContext);


            _runtimePluginAttachments?.Dispose();


            _runtime =
                runtime;

            _runtimePluginContext =
                pluginContext;

            _runtimePluginAttachments =
                pluginAttachments;

            _runtimeSignature =
                signature;

            _agentRegistry =
                agentRegistry;


            if (replacedExistingRuntime)
            {
                AddTranscriptEntry(
                    "System",
                    "Provider or runtime configuration changed. A new conversation was started.");
            }
        }


        private async Task<string> ExecutePromptAsync(
            string prompt,
            CancellationToken cancellationToken)
        {
            if (!_useAgent)
            {
                return await _runtime.ChatAsync(
                    prompt,
                    cancellationToken);
            }


            AgentSessionManager sessionManager =
                new AgentSessionManager(
                    _agentRegistry);

            AgentExecutor executor =
                new AgentExecutor(
                    _agentRegistry,
                    sessionManager,
                    _runtime);


            AgentRunResult result =
                await executor.RunAsync(
                    _agentId.Trim(),
                    prompt,
                    cancellationToken);


            if (result.Succeeded)
            {
                return result.Response ??
                       string.Empty;
            }


            if (result.Cancelled)
            {
                throw new OperationCanceledException(
                    cancellationToken);
            }


            if (result.TimedOut)
            {
                throw new TimeoutException(
                    string.IsNullOrWhiteSpace(
                        result.ErrorMessage)
                        ? "The agent run timed out."
                        : result.ErrorMessage);
            }


            throw new InvalidOperationException(
                string.IsNullOrWhiteSpace(
                    result.ErrorMessage)
                    ? "The agent run failed."
                    : result.ErrorMessage);
        }


        private AgentRegistry CreateAgentRegistry()
        {
            AgentDefinition definition =
                new AgentDefinition(
                    _agentId.Trim(),
                    _agentDisplayName.Trim(),
                    _agentDescription?.Trim() ??
                        string.Empty,
                    BuildEffectiveSystemPrompt(
                        _agentSystemPrompt));

            AgentProfile profile =
                new AgentProfile(
                    definition.Id)
                {
                    Temperature =
                        _agentTemperature,

                    MaxTokens =
                        _agentMaxTokens
                };


            definition.AttachProfile(
                profile);


            if (_enableTools)
            {
                foreach (
                    IAIRuntimeEditorCapabilityPlugin tool
                    in GetEnabledToolDefinitions())
                {
                    definition.AllowCapability(
                        tool.Name);
                }
            }


            AgentRegistry registry =
                new AgentRegistry();

            registry.Register(
                definition);


            return registry;
        }


        private string BuildEffectiveSystemPrompt(
            string configuredPrompt)
        {
            string prompt =
                configuredPrompt?.Trim() ??
                string.Empty;


            if (!_enableTools)
                return prompt;


            return string.IsNullOrEmpty(
                prompt)
                ? ToolAnswerPolicy
                : prompt +
                  "\n\n" +
                  ToolAnswerPolicy;
        }


        private void ConfigureTools(
            RuntimeFacade runtime)
        {
            runtime.AddAgentObserver(
                new ChatConsoleObserver(
                    HandleAgentEvent));


            if (!_enableTools)
                return;


            foreach (
                IAIRuntimeEditorCapabilityPlugin tool
                in GetEnabledToolDefinitions())
            {
                runtime.RegisterCapability(
                    string.Equals(
                        tool.Name,
                        "create_directory",
                        StringComparison.Ordinal)
                        ? new IntentAwareCreateDirectoryCapability(
                            () => _pendingCreateDirectoryIntent)
                        : tool.CreateCapability());
            }


            runtime.AllowPermission(
                CapabilityPermission.ReadOnly);

            ApplyPermissionDecision(
                runtime,
                CapabilityPermission.Write,
                _writePermission);

            ApplyPermissionDecision(
                runtime,
                CapabilityPermission.Destructive,
                _destructivePermission);

            ApplyPermissionDecision(
                runtime,
                CapabilityPermission.Process,
                _processPermission);

            ApplyPermissionDecision(
                runtime,
                CapabilityPermission.Network,
                _networkPermission);

            runtime.SetApprovalHandler(
                new EditorApprovalHandler());

        }


        private IEnumerable<IAIRuntimeEditorCapabilityPlugin>
            GetEnabledToolDefinitions()
        {
            return FileTools.Where(
                tool =>
                    IsToolEnabled(
                        tool.Name));
        }


        private static CreateDirectoryArguments
            ParseCreateDirectoryIntent(
                string prompt)
        {
            Match match =
                CreateDirectoryIntentPattern.Match(
                    prompt ??
                    string.Empty);


            if (!match.Success)
            {
                return ParseCreateDirectoryPathIntent(
                    prompt);
            }


            string directoryName =
                match.Groups["name"]
                    .Value
                    .Trim();

            string parentPath =
                match.Groups["parent"]
                    .Value
                    .Trim();


            if (string.IsNullOrWhiteSpace(
                    directoryName) ||
                string.IsNullOrWhiteSpace(
                    parentPath))
            {
                return null;
            }


            return new CreateDirectoryArguments
            {
                ParentPath =
                    parentPath,

                DirectoryName =
                    directoryName,

                CreateParents =
                    false
            };
        }


        private static CreateDirectoryArguments
            ParseCreateDirectoryPathIntent(
                string prompt)
        {
            Match match =
                CreateDirectoryPathIntentPattern.Match(
                    prompt ??
                    string.Empty);


            if (!match.Success)
                return null;


            string path =
                match.Groups["path"]
                    .Value
                    .Trim()
                    .Trim('"', '\'')
                    .Replace(
                        '\\',
                        '/')
                    .TrimEnd('/');

            int separatorIndex =
                path.LastIndexOf('/');


            if (separatorIndex < 0)
            {
                return new CreateDirectoryArguments
                {
                    ParentPath =
                        ".",

                    DirectoryName =
                        path,

                    CreateParents =
                        true
                };
            }


            return new CreateDirectoryArguments
            {
                ParentPath =
                    path.Substring(
                        0,
                        separatorIndex),

                DirectoryName =
                    path.Substring(
                        separatorIndex +
                        1),

                CreateParents =
                    true
            };
        }


        private static void ApplyPermissionDecision(
            RuntimeFacade runtime,
            CapabilityPermission permission,
            CapabilityPermissionDecision decision)
        {
            switch (decision)
            {
                case CapabilityPermissionDecision.Allow:
                    runtime.AllowPermission(
                        permission);
                    break;

                case CapabilityPermissionDecision.Deny:
                    runtime.DenyPermission(
                        permission);
                    break;

                case CapabilityPermissionDecision.RequireApproval:
                    runtime.RequirePermissionApproval(
                        permission);
                    break;

                default:
                    throw new ArgumentOutOfRangeException(
                        nameof(decision),
                        decision,
                        "Unknown capability permission decision.");
            }
        }


        private void HandleAgentEvent(
            AgentEvent agentEvent)
        {
            if (agentEvent == null)
                return;


            switch (agentEvent.Type)
            {
                case AgentEventType.ToolStarted:
                    AddTranscriptEntry(
                        "Tool",
                        $"Calling {agentEvent.ToolName}...");
                    break;

                case AgentEventType.ToolCompleted:
                    AddTranscriptEntry(
                        "Tool",
                        $"{agentEvent.ToolName} completed.");


                    if (AssetMutationTools.Contains(
                            agentEvent.ToolName))
                    {
                        _assetMutationPending =
                            true;

                        CaptureVerifiedMutationResponse(
                            agentEvent);
                    }


                    break;

                case AgentEventType.ToolFailed:
                    AddTranscriptEntry(
                        "Tool error",
                        string.IsNullOrWhiteSpace(
                            agentEvent.Message)
                            ? $"{agentEvent.ToolName} failed."
                            : $"{agentEvent.ToolName}: {agentEvent.Message}");
                    break;

                case AgentEventType.ModelResponseRepair:
                    AddTranscriptEntry(
                        "Model correction",
                        "AIRuntime asked the model to correct malformed capability arguments.");
                    break;

                case AgentEventType.ModelResponseNormalized:
                    AddTranscriptEntry(
                        "Provider compatibility",
                        agentEvent.Message ??
                        "AIRuntime normalized a provider capability-call batch.");
                    break;

                case AgentEventType.RunCompleted:
                case AgentEventType.RunCancelled:
                case AgentEventType.RunTimedOut:
                case AgentEventType.RunFailed:
                    if (_assetMutationPending)
                    {
                        _assetMutationPending =
                            false;

                        QueueAssetDatabaseRefresh();
                    }


                    return;

                default:
                    return;
            }


            ScrollToBottom();


            if (this != null)
            {
                Repaint();
            }
        }


        private void QueueAssetDatabaseRefresh()
        {
            if (_assetRefreshQueued)
                return;


            _assetRefreshQueued =
                true;

            EditorApplication.delayCall +=
                RefreshAssetDatabase;
        }


        private void CaptureVerifiedMutationResponse(
            AgentEvent agentEvent)
        {
            if (!string.Equals(
                    agentEvent.ToolName,
                    "create_directory",
                    StringComparison.Ordinal) ||
                string.IsNullOrWhiteSpace(
                    agentEvent.Message))
            {
                return;
            }


            try
            {
                JObject result =
                    JObject.Parse(
                        agentEvent.Message);


                if (result["Succeeded"]?.Value<bool>() !=
                    true)
                {
                    return;
                }


                string path =
                    result["Path"]?.Value<string>();


                if (string.IsNullOrWhiteSpace(
                        path))
                {
                    return;
                }


                _verifiedMutationResponse =
                    "Directory created successfully.\n" +
                    "Path: " +
                    path;
            }
            catch
            {
                // Only structured, verified mutation results override
                // the provider's prose response.
            }
        }


        private void RefreshAssetDatabase()
        {
            EditorApplication.delayCall -=
                RefreshAssetDatabase;

            _assetRefreshQueued =
                false;


            AssetDatabase.Refresh(
                ImportAssetOptions.Default);


            if (this != null)
            {
                Repaint();
            }
        }


        private IProvider CreateProvider()
        {
            return AIRuntimeEditorPluginCatalog.CreateProvider(
                _provider,
                CreateProviderSettings());
        }


        private AIRuntimeEditorProviderSettings CreateProviderSettings()
        {
            return new AIRuntimeEditorProviderSettings
            {
                ApiKey = _apiKey ?? string.Empty,
                Model = _model?.Trim() ?? string.Empty,
                SupportsImageInput = _supportsImageInput,
                BaseUrl = _baseUrl?.Trim() ?? string.Empty,
                HttpReferer = _httpReferer ?? string.Empty,
                ApplicationTitle = _applicationTitle ?? string.Empty
            };
        }


        private void ApplyProviderSettings(
            AIRuntimeEditorProviderSettings settings)
        {
            if (settings == null)
            {
                return;
            }

            _apiKey = settings.ApiKey ?? string.Empty;
            _supportsImageInput = settings.SupportsImageInput &&
                string.Equals(_model, settings.Model, StringComparison.Ordinal) &&
                string.Equals(_baseUrl, settings.BaseUrl, StringComparison.Ordinal);
            _model = settings.Model ?? string.Empty;
            _baseUrl = settings.BaseUrl ?? string.Empty;
            _httpReferer = settings.HttpReferer ?? string.Empty;
            _applicationTitle = settings.ApplicationTitle ?? string.Empty;
        }


        private void ValidateConfiguration()
        {
            if (string.IsNullOrWhiteSpace(
                    _workspaceRoot) ||
                !Directory.Exists(
                    _workspaceRoot))
            {
                throw new InvalidOperationException(
                    "Select an existing workspace folder.");
            }


            if (!AIRuntimeEditorPluginCatalog.TryGetProviderPlugin(
                    _provider,
                    out _))
            {
                throw new InvalidOperationException(
                    $"Provider plugin '{_provider}' is not installed.");
            }


            AIRuntimeEditorPluginCatalog.ValidateProviderSettings(
                _provider,
                CreateProviderSettings());


            if (_enableTools &&
                !_enabledTools.Any())
            {
                throw new InvalidOperationException(
                    "Select at least one workspace tool or disable tool calling.");
            }


            if (_enableTools)
            {
                HashSet<string> availableTools =
                    new HashSet<string>(
                        FileTools.Select(tool => tool.Name),
                        StringComparer.Ordinal);

                string[] missingTools =
                    _enabledTools
                        .Where(tool =>
                            !string.IsNullOrWhiteSpace(tool) &&
                            !availableTools.Contains(tool))
                        .Distinct(StringComparer.Ordinal)
                        .OrderBy(tool => tool, StringComparer.Ordinal)
                        .ToArray();

                if (missingTools.Length > 0)
                {
                    throw new InvalidOperationException(
                        "Selected capability plugins are not installed: " +
                        string.Join(", ", missingTools) +
                        ".");
                }
            }


            if (!_useAgent)
                return;


            if (string.IsNullOrWhiteSpace(
                    _agentId))
            {
                throw new InvalidOperationException(
                    "Enter an agent ID.");
            }


            if (string.IsNullOrWhiteSpace(
                    _agentDisplayName))
            {
                throw new InvalidOperationException(
                    "Enter an agent display name.");
            }


            float maximumTemperature =
                GetMaximumAgentTemperature();


            if (_agentTemperature < 0 ||
                _agentTemperature >
                maximumTemperature)
            {
                throw new InvalidOperationException(
                    $"Agent temperature must be between 0 and {maximumTemperature}.");
            }


            if (_agentMaxTokens < 0)
            {
                throw new InvalidOperationException(
                    "Agent maximum tokens cannot be negative. Use zero to let the provider choose its native limit.");
            }
        }


        private string BuildRuntimeSignature()
        {
            return string.Join(
                "\n",
                _supportsImageInput.ToString(),
                _provider,
                _model?.Trim() ??
                    string.Empty,
                _baseUrl?.Trim() ??
                    string.Empty,
                _workspaceRoot?.Trim() ??
                    string.Empty,
                _systemPrompt?.Trim() ??
                    string.Empty,
                _apiKey?.Trim() ??
                    string.Empty,
                _httpReferer?.Trim() ??
                    string.Empty,
                _applicationTitle?.Trim() ??
                    string.Empty,
                _useAgent.ToString(),
                _agentPreset,
                _agentId?.Trim() ??
                    string.Empty,
                _agentDisplayName?.Trim() ??
                    string.Empty,
                _agentDescription?.Trim() ??
                    string.Empty,
                _agentSystemPrompt?.Trim() ??
                    string.Empty,
                _agentTemperature.ToString(
                    System.Globalization.CultureInfo.InvariantCulture),
                _agentMaxTokens.ToString(
                    System.Globalization.CultureInfo.InvariantCulture),
                _enableTools.ToString(),
                _writePermission.ToString(),
                _destructivePermission.ToString(),
                _processPermission.ToString(),
                _networkPermission.ToString(),
                AIRuntimeEditorRuntimePluginRegistry.Revision.ToString(
                    System.Globalization.CultureInfo.InvariantCulture),
                string.Join(
                    ",",
                    _enabledTools
                        .OrderBy(
                            tool =>
                                tool,
                            StringComparer.Ordinal)));
        }


        private void ApplyAgentPreset(
            string preset)
        {
            AgentDefinition definition =
                AIRuntimeEditorPluginCatalog.CreateAgentDefinition(
                    preset);


            _agentId =
                definition.Id;

            _agentDisplayName =
                definition.DisplayName;

            _agentDescription =
                definition.Description;

            _agentSystemPrompt =
                definition.SystemPrompt;

            _agentTemperature =
                (float)definition.Profile.Temperature;

            _agentMaxTokens =
                definition.Profile.MaxTokens;
        }


        private void ApplyProviderDefaults(
            string provider,
            bool replaceModel)
        {
            _supportsImageInput = false;
            IAIRuntimeEditorProviderPlugin plugin =
                AIRuntimeEditorPluginCatalog.GetProviderPlugin(
                    provider);

            _provider =
                plugin.Id;

            _baseUrl =
                plugin.DefaultBaseUrl ??
                string.Empty;

            if (replaceModel)
            {
                _model =
                    plugin.DefaultModel ??
                    string.Empty;
            }
        }


        private void StartNewConversation()
        {
            CancelSend();


            _runtime?.ResetConversation();

            _transcript.Clear();

            _status =
                "New conversation";

            _transcriptScroll =
                Vector2.zero;

            Repaint();
        }


        private void SaveAgentConfiguration()
        {
            try
            {
                if (!_useAgent)
                {
                    throw new InvalidOperationException(
                        "Enable agent execution before saving an agent configuration.");
                }


                ValidateConfiguration();


                string path =
                    NormalizeConfigurationPath();

                string directory =
                    Path.GetDirectoryName(
                        path);


                if (string.IsNullOrWhiteSpace(
                        directory))
                {
                    throw new InvalidOperationException(
                        "The agent configuration file must have a parent folder.");
                }


                Directory.CreateDirectory(
                    directory);


                AIRuntimeEditorAgentConfiguration configuration =
                    new AIRuntimeEditorAgentConfiguration
                    {
                        Provider =
                            _provider,

                        SupportsImageInput = _supportsImageInput,

                        Model =
                            _model?.Trim() ??
                            string.Empty,

                        BaseUrl =
                            _baseUrl?.Trim() ??
                            string.Empty,

                        ApiKey =
                            _apiKey?.Trim() ??
                            string.Empty,

                        WorkspaceRoot =
                            _workspaceRoot?.Trim() ??
                            string.Empty,

                        ChatSystemPrompt =
                            _systemPrompt ??
                            string.Empty,

                        HttpReferer =
                            _httpReferer?.Trim() ??
                            string.Empty,

                        ApplicationTitle =
                            _applicationTitle?.Trim() ??
                            string.Empty,

                        UseAgent =
                            _useAgent,

                        AgentPreset =
                            _agentPreset,

                        AgentId =
                            _agentId?.Trim() ??
                            string.Empty,

                        AgentDisplayName =
                            _agentDisplayName?.Trim() ??
                            string.Empty,

                        AgentDescription =
                            _agentDescription ??
                            string.Empty,

                        AgentSystemPrompt =
                            _agentSystemPrompt ??
                            string.Empty,

                        AgentTemperature =
                            _agentTemperature,

                        AgentMaxTokens =
                            _agentMaxTokens,

                        EnableTools =
                            _enableTools,

                        EnabledTools =
                            new List<string>(
                                _enabledTools),

                        WritePermission =
                            _writePermission.ToString(),

                        DestructivePermission =
                            _destructivePermission.ToString(),

                        ProcessPermission =
                            _processPermission.ToString(),

                        NetworkPermission =
                            _networkPermission.ToString()
                    };


                string json =
                    JsonUtility.ToJson(
                        configuration,
                        prettyPrint: true);


                File.WriteAllText(
                    path,
                    json,
                    new UTF8Encoding(
                        encoderShouldEmitUTF8Identifier: false));


                _configurationFilePath =
                    path;

                _status =
                    $"Saved {_agentDisplayName}";


                EditorUtility.DisplayDialog(
                    "Agent saved",
                    $"Saved the agent configuration to:\n\n{path}\n\nThe file contains the API key in plain text.",
                    "OK");
            }
            catch (Exception exception)
            {
                _status =
                    "Save failed";

                EditorUtility.DisplayDialog(
                    "Could not save agent",
                    exception.Message,
                    "OK");
            }
        }


        private void LoadAgentConfiguration()
        {
            try
            {
                string path =
                    NormalizeConfigurationPath();


                if (!File.Exists(path))
                {
                    throw new FileNotFoundException(
                        "The agent configuration file does not exist.",
                        path);
                }


                string json =
                    File.ReadAllText(
                        path,
                        Encoding.UTF8);

                AIRuntimeEditorAgentConfiguration configuration =
                    JsonUtility.FromJson<AIRuntimeEditorAgentConfiguration>(
                        json);


                if (configuration == null ||
                    configuration.Version != 1)
                {
                    throw new InvalidOperationException(
                        "The agent configuration file has an unsupported format or version.");
                }


                string savedProviderId =
                    configuration.Provider?.Trim() ??
                    string.Empty;

                if (AIRuntimeEditorPluginCatalog.TryGetProviderPlugin(
                        savedProviderId,
                        out IAIRuntimeEditorProviderPlugin provider))
                {
                    savedProviderId =
                        provider.Id;
                }


                string savedAgentPresetId =
                    configuration.AgentPreset?.Trim() ??
                    string.Empty;

                if (AIRuntimeEditorPluginCatalog.TryGetAgentPlugin(
                        savedAgentPresetId,
                        out IAIRuntimeEditorAgentPlugin preset))
                {
                    savedAgentPresetId =
                        preset.Id;
                }


                CapabilityPermissionDecision writePermission =
                    ParsePermissionDecision(
                        configuration.WritePermission,
                        CapabilityPermissionDecision.RequireApproval,
                        "write");

                CapabilityPermissionDecision destructivePermission =
                    ParsePermissionDecision(
                        configuration.DestructivePermission,
                        CapabilityPermissionDecision.RequireApproval,
                        "destructive");

                CapabilityPermissionDecision processPermission =
                    ParsePermissionDecision(
                        configuration.ProcessPermission,
                        CapabilityPermissionDecision.RequireApproval,
                        "process");

                CapabilityPermissionDecision networkPermission =
                    ParsePermissionDecision(
                        configuration.NetworkPermission,
                        CapabilityPermissionDecision.RequireApproval,
                        "network");

                List<string> enabledTools =
                    NormalizeEnabledTools(
                        configuration.EnabledTools);


                _provider =
                    savedProviderId;

                _supportsImageInput = configuration.SupportsImageInput;

                _model =
                    configuration.Model ??
                    string.Empty;

                _baseUrl =
                    configuration.BaseUrl ??
                    string.Empty;

                _apiKey =
                    configuration.ApiKey ??
                    string.Empty;

                _workspaceRoot =
                    configuration.WorkspaceRoot ??
                    GetProjectRoot();

                _systemPrompt =
                    configuration.ChatSystemPrompt ??
                    string.Empty;

                _httpReferer =
                    configuration.HttpReferer ??
                    string.Empty;

                _applicationTitle =
                    configuration.ApplicationTitle ??
                    string.Empty;

                _useAgent =
                    configuration.UseAgent;

                _agentPreset =
                    savedAgentPresetId;

                _agentId =
                    configuration.AgentId ??
                    string.Empty;

                _agentDisplayName =
                    configuration.AgentDisplayName ??
                    string.Empty;

                _agentDescription =
                    configuration.AgentDescription ??
                    string.Empty;

                _agentSystemPrompt =
                    configuration.AgentSystemPrompt ??
                    string.Empty;

                _agentTemperature =
                    configuration.AgentTemperature;

                _agentMaxTokens =
                    configuration.AgentMaxTokens;

                _enableTools =
                    configuration.EnableTools;

                _enabledTools =
                    enabledTools;

                _writePermission =
                    writePermission;

                _destructivePermission =
                    destructivePermission;

                _processPermission =
                    processPermission;

                _networkPermission =
                    networkPermission;

                _configurationFilePath =
                    path;


                _runtime =
                    null;

                _runtimeSignature =
                    null;

                _agentRegistry =
                    null;

                _transcript.Clear();

                AddTranscriptEntry(
                    "System",
                    $"Loaded agent configuration for {_agentDisplayName}.");

                _status =
                    $"Loaded {_agentDisplayName}";

                ScrollToBottom();

                Repaint();
            }
            catch (Exception exception)
            {
                _status =
                    "Load failed";

                EditorUtility.DisplayDialog(
                    "Could not load agent",
                    exception.Message,
                    "OK");
            }
        }


        private static CapabilityPermissionDecision
            ParsePermissionDecision(
                string value,
                CapabilityPermissionDecision fallback,
                string permissionLabel)
        {
            if (string.IsNullOrWhiteSpace(
                    value))
            {
                return fallback;
            }


            if (Enum.TryParse(
                    value,
                    ignoreCase: true,
                    out CapabilityPermissionDecision decision))
            {
                return decision;
            }


            throw new InvalidOperationException(
                $"Unknown saved {permissionLabel} permission decision '{value}'.");
        }


        private static List<string> NormalizeEnabledTools(
            IEnumerable<string> savedTools)
        {
            if (savedTools == null)
            {
                return FileTools
                    .Where(
                        tool =>
                            tool.Permission ==
                            CapabilityPermission.ReadOnly)
                    .Select(
                        tool =>
                            tool.Name)
                    .ToList();
            }


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


            foreach (
                string savedTool
                in savedTools)
            {
                string savedName =
                    savedTool?.Trim();

                if (string.IsNullOrWhiteSpace(
                        savedName))
                {
                    continue;
                }

                IAIRuntimeEditorCapabilityPlugin definition =
                    FileTools.FirstOrDefault(
                        tool =>
                            string.Equals(
                                tool.Name,
                                savedName,
                                StringComparison.Ordinal));

                string normalizedName =
                    definition?.Name ??
                    savedName;

                if (!normalized.Contains(
                        normalizedName))
                {
                    normalized.Add(
                        normalizedName);
                }
            }

            if (!normalized.Contains(
                    "get_editor_selection"))
            {
                normalized.Add(
                    "get_editor_selection");
            }

            if (!normalized.Contains(
                    "inspect_editor_component"))
            {
                normalized.Add(
                    "inspect_editor_component");
            }

            return normalized;
        }


        private string NormalizeConfigurationPath()
        {
            if (string.IsNullOrWhiteSpace(
                    _configurationFilePath))
            {
                throw new InvalidOperationException(
                    "Enter an agent configuration file path.");
            }


            string path =
                Path.GetFullPath(
                    _configurationFilePath.Trim());


            if (!string.Equals(
                    Path.GetExtension(path),
                    ".json",
                    StringComparison.OrdinalIgnoreCase))
            {
                path +=
                    ".json";
            }


            return path;
        }


        private string GetConfigurationDirectory()
        {
            try
            {
                string directory =
                    Path.GetDirectoryName(
                        _configurationFilePath);


                return string.IsNullOrWhiteSpace(
                        directory)
                    ? Environment.GetFolderPath(
                        Environment.SpecialFolder.MyDocuments)
                    : directory;
            }
            catch (ArgumentException)
            {
                return Environment.GetFolderPath(
                    Environment.SpecialFolder.MyDocuments);
            }
        }


        private bool ConfigurationDirectoryExists()
        {
            return Directory.Exists(
                GetConfigurationDirectory());
        }


        private void CancelSend()
        {
            if (_sendCancellation == null ||
                _sendCancellation.IsCancellationRequested)
            {
                return;
            }


            _status =
                "Cancelling…";

            _sendCancellation.Cancel();

            Repaint();
        }


        private void AddTranscriptEntry(
            string role,
            string content)
        {
            _transcript.Add(
                new TranscriptEntry
                {
                    Role =
                        role,

                    Content =
                        content ??
                        string.Empty
                });
        }


        private void QueueStreamingText(
            string text)
        {
            if (string.IsNullOrEmpty(
                    text))
            {
                return;
            }


            lock (_streamSync)
            {
                _pendingStreamText.Append(
                    text);
            }
        }


        private void DrainStreamingText()
        {
            string text;


            lock (_streamSync)
            {
                if (_pendingStreamText.Length ==
                    0)
                {
                    return;
                }


                text =
                    _pendingStreamText.ToString();

                _pendingStreamText.Clear();
            }


            if (_streamingEntry ==
                null)
            {
                return;
            }


            _streamingEntry.Content =
                (_streamingEntry.Content ??
                 string.Empty) +
                text;

            ScrollToBottom();


            if (this !=
                null)
            {
                Repaint();
            }
        }


        private string BuildTranscriptText()
        {
            StringBuilder builder =
                new StringBuilder();


            foreach (
                TranscriptEntry entry
                in _transcript)
            {
                if (builder.Length > 0)
                {
                    builder.AppendLine();
                }


                builder.Append(
                    entry.Role);

                builder.AppendLine(
                    ":");

                builder.AppendLine(
                    entry.Content);
            }


            return builder.ToString();
        }


        private void ScrollToBottom()
        {
            _transcriptScroll.y =
                float.MaxValue;
        }


        private void EnsureStyles()
        {
            if (_transcriptStyle != null)
                return;


            _transcriptStyle =
                new GUIStyle(
                    EditorStyles.textArea)
                {
                    wordWrap =
                        true
                };


            _roleStyle =
                new GUIStyle(
                    EditorStyles.boldLabel)
                {
                    fontSize =
                        11,

                    stretchWidth =
                        true
                };


            _assistantStyle =
                new GUIStyle(
                    EditorStyles.label)
                {
                    wordWrap =
                        true,

                    richText =
                        false,

                    stretchWidth =
                        true,

                    padding =
                        new RectOffset(
                            8,
                            8,
                            6,
                            6)
                };


            _userStyle =
                new GUIStyle(
                    _assistantStyle)
                {
                    normal =
                    {
                        textColor =
                            EditorGUIUtility.isProSkin
                                ? new Color(
                                    0.65f,
                                    0.85f,
                                    1f)
                                : new Color(
                                    0.05f,
                                    0.3f,
                                    0.55f)
                    }
                };
        }


        private bool TryGetProviderConfigurationPlugin(
            out IAIRuntimeEditorProviderConfigurationPlugin configuration)
        {
            if (AIRuntimeEditorPluginCatalog.TryGetProviderPlugin(
                    _provider,
                    out IAIRuntimeEditorProviderPlugin plugin) &&
                plugin is IAIRuntimeEditorProviderConfigurationPlugin configured)
            {
                configuration = configured;
                return true;
            }

            configuration = null;
            return false;
        }


        private float GetMaximumAgentTemperature()
        {
            if (TryGetProviderConfigurationPlugin(
                    out IAIRuntimeEditorProviderConfigurationPlugin configuration) &&
                configuration.MaximumAgentTemperature > 0)
            {
                return configuration.MaximumAgentTemperature;
            }

            return 2f;
        }


        private static string GetProjectRoot()
        {
            DirectoryInfo assetsDirectory =
                new DirectoryInfo(
                    Application.dataPath);


            return assetsDirectory.Parent?.FullName ??
                   Application.dataPath;
        }


        private static string GetDefaultAgentConfigurationPath()
        {
            string documents =
                Environment.GetFolderPath(
                    Environment.SpecialFolder.MyDocuments);


            if (string.IsNullOrWhiteSpace(
                    documents))
            {
                documents =
                    GetProjectRoot();
            }


            return Path.Combine(
                documents,
                "AIRuntime",
                "architect",
                "agent.json");
        }
    }


    /// <summary>
    /// Dockable view of raw provider request bodies and response frames.
    /// Authentication headers are deliberately excluded by the transport.
    /// </summary>
    public sealed class AIRuntimeProviderDebugConsole :
        EditorWindow
    {
        private Vector2 _scroll;

        private bool _autoScroll =
            true;

        private bool _showRequests =
            true;

        private bool _showResponses =
            true;

        private int _pendingRepaint;

        private GUIStyle _payloadStyle;


        [MenuItem("Window/AIRuntime/Provider Debug Console")]
        public static void Open()
        {
            AIRuntimeProviderDebugConsole window =
                GetWindow<AIRuntimeProviderDebugConsole>();

            window.titleContent =
                new GUIContent(
                    "Provider Debug");

            window.minSize =
                new Vector2(
                    520,
                    260);

            window.Show();
        }


        private void OnEnable()
        {
            ProviderTransportDebug.RecordPublished +=
                OnRecordPublished;
        }


        private void OnDisable()
        {
            ProviderTransportDebug.RecordPublished -=
                OnRecordPublished;
        }


        private void Update()
        {
            if (Interlocked.Exchange(
                    ref _pendingRepaint,
                    0) ==
                0)
            {
                return;
            }


            if (_autoScroll)
            {
                _scroll.y =
                    float.MaxValue;
            }


            Repaint();
        }


        private void OnGUI()
        {
            EnsurePayloadStyle();


            using (
                new EditorGUILayout.HorizontalScope(
                    EditorStyles.toolbar))
            {
                _showRequests =
                    GUILayout.Toggle(
                        _showRequests,
                        "Requests",
                        EditorStyles.toolbarButton);

                _showResponses =
                    GUILayout.Toggle(
                        _showResponses,
                        "Responses",
                        EditorStyles.toolbarButton);

                _autoScroll =
                    GUILayout.Toggle(
                        _autoScroll,
                        "Auto-scroll",
                        EditorStyles.toolbarButton);

                GUILayout.FlexibleSpace();


                if (GUILayout.Button(
                        "Clear view",
                        EditorStyles.toolbarButton))
                {
                    ProviderTransportDebug.Clear();
                }

                if (GUILayout.Button("Open log folder", EditorStyles.toolbarButton) &&
                    !string.IsNullOrWhiteSpace(ProviderTransportDebug.JournalDirectory))
                {
                    System.IO.Directory.CreateDirectory(ProviderTransportDebug.JournalDirectory);
                    EditorUtility.RevealInFinder(ProviderTransportDebug.JournalDirectory);
                }
            }


            EditorGUILayout.HelpBox(
                "Full prompts, tool schemas, responses, transport errors and retry decisions are saved to " +
                ProviderTransportDebug.JournalDirectory +
                ". Authentication header values and known API keys are redacted. Clear view preserves the files.",
                MessageType.Info);

            if (!string.IsNullOrEmpty(ProviderTransportDebug.LastJournalError))
                EditorGUILayout.HelpBox("Provider log write failed: " + ProviderTransportDebug.LastJournalError,
                    MessageType.Error);

            IReadOnlyList<ProviderTransportRecord> records =
                ProviderTransportDebug.Snapshot();

            _scroll =
                EditorGUILayout.BeginScrollView(
                    _scroll);


            foreach (
                ProviderTransportRecord record
                in records)
            {
                bool request =
                    string.Equals(
                        record.Direction,
                        "REQUEST",
                        StringComparison.OrdinalIgnoreCase);


                if ((request &&
                     !_showRequests) ||
                    (!request &&
                     !_showResponses))
                {
                    continue;
                }


                EditorGUILayout.LabelField(
                    $"{record.TimestampUtc:HH:mm:ss.fff} UTC  {record.ProviderName}  {record.Direction}" +
                    (record.IsStreaming
                        ? "  [stream]"
                        : string.Empty) +
                    (!string.IsNullOrWhiteSpace(
                         record.StreamTerminationSignal)
                        ? $"  [complete: {record.StreamTerminationSignal}]"
                        : string.Empty),
                    EditorStyles.boldLabel);

                EditorGUILayout.SelectableLabel(
                    record.Endpoint,
                    EditorStyles.miniLabel,
                    GUILayout.Height(
                        EditorGUIUtility.singleLineHeight));

                EditorGUILayout.SelectableLabel(
                    $"Request {record.RequestId} | Session {record.SessionId} | Attempt {record.Attempt}",
                    EditorStyles.miniLabel, GUILayout.Height(EditorGUIUtility.singleLineHeight));

                if (record.Metadata != null)
                    EditorGUILayout.SelectableLabel(record.Metadata.ToString(), _payloadStyle,
                        GUILayout.Height(Mathf.Clamp(_payloadStyle.CalcHeight(
                            new GUIContent(record.Metadata.ToString()), Mathf.Max(200, position.width - 32)), 42, 240)));

                float payloadHeight =
                    Mathf.Clamp(
                        _payloadStyle.CalcHeight(
                            new GUIContent(
                                record.Payload),
                            Mathf.Max(
                                200,
                                position.width -
                                32)),
                        42,
                        420);

                EditorGUILayout.SelectableLabel(
                    record.Payload,
                    _payloadStyle,
                    GUILayout.Height(
                        payloadHeight));

                GUILayout.Space(
                    8);
            }


            EditorGUILayout.EndScrollView();
        }


        private void OnRecordPublished(
            ProviderTransportRecord record)
        {
            Interlocked.Exchange(
                ref _pendingRepaint,
                1);
        }


        private void EnsurePayloadStyle()
        {
            if (_payloadStyle !=
                null)
            {
                return;
            }


            _payloadStyle =
                new GUIStyle(
                    EditorStyles.textArea)
                {
                    wordWrap =
                        true,
                    font =
                        EditorStyles.standardFont
                };
        }
    }
}
