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

namespace io.github.hatayama.uLoopMCP
{
    public class McpEditorWindow : EditorWindow
    {
        private const bool ForceFlatSkillInstall = true;
        private const double DeferredInitialRefreshDelaySeconds = 0.05;
        private const double ToolSettingsRegistryWarmupInitialDelaySeconds = 0.05;
        private const double ToolSettingsRegistryWarmupMaxDelaySeconds = 0.8;
        private const int ToolSettingsRegistryWarmupMaxAttempts = 5;

        private McpConfigServiceFactory _configServiceFactory;
        private McpEditorWindowUI _view;
        private McpEditorModel _model;
        private McpEditorWindowEventHandler _eventHandler;

        private SkillsTarget _skillsTarget = SkillsTarget.Claude;
        private bool _installSkillsFlat;
        private bool _isInstallingCli;
        private bool _isInstallingSkills;
        private bool _isRefreshingVersion;
        private bool _isToolSettingsCatalogDirty = true;
        private bool _isDeferredInitialRefreshScheduled;
        private bool _hasCompletedDeferredInitialRefresh;
        private double _deferredInitialRefreshDueTime;
        private bool _isToolSettingsRegistryWarmupScheduled;
        private double _toolSettingsRegistryWarmupDueTime;
        private int _toolSettingsRegistryWarmupAttemptCount;
        private SkillInstallState _selectedTargetInstallState = SkillInstallState.Missing;
        private CancellationTokenSource _skillInstallStateRefreshCts;

        [MenuItem("Window/Unity CLI Loop/Settings", priority = 0)]
        public static void ShowWindow()
        {
            McpEditorWindow window = GetWindow<McpEditorWindow>("Unity CLI Loop");
            window.Show();
        }

        private void OnEnable()
        {
            InitializeAll();
        }

        private void OnDestroy()
        {
            CancelDeferredInitialRefresh();
            CancelToolSettingsRegistryWarmup();
            ResetToolSettingsRegistryWarmupAttemptCount();
            CancelSkillInstallStateRefresh();
            _view?.Dispose();
            _view = null;
        }

        private void CreateGUI()
        {
            InitializeView();
            RefreshAllSections(refreshMode: McpEditorWindowRefreshMode.InitialPaint);
            ScheduleDeferredInitialRefresh();
        }

        private void InitializeAll()
        {
            InitializeModel();
            InitializeConfigurationServices();
            InitializeEventHandler();
            LoadSavedSettings();
            RestoreSessionState();
            HandlePostCompileMode();
        }

        private void InitializeModel()
        {
            _model = new McpEditorModel();
        }

        private void InitializeView()
        {
            _view = new McpEditorWindowUI(rootVisualElement);
            SetupViewCallbacks();
        }

        private void SetupViewCallbacks()
        {
            _view.OnConnectionModeChanged += UpdateConnectionMode;
            _view.OnRefreshCliVersion += HandleRefreshCliVersion;
            _view.OnInstallCli += HandleInstallCli;
            _view.OnInstallSkills += HandleInstallSkills;
            _view.OnRefreshSkillsState += HandleRefreshSkillsState;
            _view.OnSkillsTargetChanged += value =>
            {
                _skillsTarget = value;
                RefreshSelectedTargetInstallStateFast();
                RefreshSelectedTargetInstallStateInBackground();
            };
            _view.OnGroupSkillsChanged += HandleGroupSkillsChanged;
            _view.OnConfigurationFoldoutChanged += UpdateShowConfiguration;
            _view.OnConnectedToolsFoldoutChanged += UpdateShowConnectedTools;
            _view.OnEditorTypeChanged += UpdateSelectedEditorType;
            _view.OnRepositoryRootChanged += UpdateAddRepositoryRoot;
            _view.OnConfigureClicked += ConfigureEditor;
            _view.OnDeleteConfigClicked += DeleteEditorConfiguration;
            _view.OnOpenSettingsClicked += OpenConfigurationFile;
            _view.OnToolSettingsFoldoutChanged += UpdateShowToolSettings;
            _view.OnToolToggled += HandleToolToggled;
            _view.OnAllowThirdPartyChanged += UpdateAllowThirdPartyTools;
            _view.OnSecurityLevelChanged += UpdateDynamicCodeSecurityLevel;
        }

        public IEnumerable<ConnectedClient> GetConnectedToolsAsClients()
        {
            return ConnectedToolsMonitoringService.GetConnectedToolsAsClients();
        }

        private void InitializeConfigurationServices()
        {
            _configServiceFactory = new McpConfigServiceFactory();
        }

        private void InitializeEventHandler()
        {
            _eventHandler = new McpEditorWindowEventHandler(_model, this);
            _eventHandler.Initialize();
        }

        private void LoadSavedSettings()
        {
            _model.LoadFromSettings();
            _installSkillsFlat = ForceFlatSkillInstall;
        }

        private void RestoreSessionState()
        {
            _model.LoadFromSessionState();
        }

        private async void HandlePostCompileMode()
        {
            _model.EnablePostCompileMode();
            McpEditorSettings.SetShowReconnectingUI(false);

            Task recoveryTask = McpServerController.RecoveryTask;
            if (recoveryTask != null && !recoveryTask.IsCompleted)
            {
                await recoveryTask;
            }

            bool isAfterCompile = McpEditorSettings.GetIsAfterCompile();

            if (isAfterCompile)
            {
                McpEditorSettings.ClearAfterCompileFlag();

                int savedPort = McpEditorSettings.GetCustomPort();
                bool portNeedsUpdate = savedPort != _model.UI.CustomPort;

                if (portNeedsUpdate)
                {
                    _model.UpdateCustomPort(savedPort);
                }

                return;
            }

            // McpServerController.[InitializeOnLoad] handles automatic server recovery via RestoreServerStateIfNeeded()
        }

        private void OnDisable()
        {
            CancelDeferredInitialRefresh();
            CancelToolSettingsRegistryWarmup();
            ResetToolSettingsRegistryWarmupAttemptCount();
            CancelSkillInstallStateRefresh();
            CleanupEventHandler();
            SaveSessionState();
            _view?.Dispose();
            _view = null;
        }

        private void CleanupEventHandler()
        {
            _eventHandler?.Cleanup();
        }

        private void SaveSessionState()
        {
            _model.SaveToSessionState();
        }

        private void ScheduleDeferredInitialRefresh()
        {
            if (_isDeferredInitialRefreshScheduled)
            {
                return;
            }

            _isDeferredInitialRefreshScheduled = true;
            _deferredInitialRefreshDueTime = EditorApplication.timeSinceStartup + DeferredInitialRefreshDelaySeconds;
            EditorApplication.update += RunDeferredInitialRefreshWhenDue;
        }

        private void RunDeferredInitialRefreshWhenDue()
        {
            if (EditorApplication.timeSinceStartup < _deferredInitialRefreshDueTime)
            {
                return;
            }

            CancelDeferredInitialRefresh();
            if (_view == null)
            {
                return;
            }

            _hasCompletedDeferredInitialRefresh = true;
            _selectedTargetInstallState = SkillInstallState.Checking;
            RefreshRepositoryRootSupport();
            RefreshAllSections(
                refreshSkillInstallState: false,
                refreshMode: McpEditorWindowRefreshMode.Full);
            RefreshSelectedTargetInstallStateInBackground();
        }

        private void CancelDeferredInitialRefresh()
        {
            if (!_isDeferredInitialRefreshScheduled)
            {
                return;
            }

            EditorApplication.update -= RunDeferredInitialRefreshWhenDue;
            _isDeferredInitialRefreshScheduled = false;
        }

        private void OnFocus()
        {
            if (!_hasCompletedDeferredInitialRefresh)
            {
                RefreshAllSections(refreshMode: McpEditorWindowRefreshMode.InitialPaint);
            }

            ScheduleDeferredInitialRefresh();
        }

        internal void RefreshAllSections(
            bool refreshSkillInstallState = false,
            McpEditorWindowRefreshMode refreshMode = McpEditorWindowRefreshMode.Full)
        {
            if (_view == null)
            {
                return;
            }

            bool runExpensiveChecks = McpEditorWindowRefreshPolicy.ShouldRunExpensiveChecks(refreshMode);

            ConnectionModeData modeData = new ConnectionModeData(_model.UI.ConnectionMode);
            _view.UpdateConnectionMode(modeData);
            _view.UpdateConfigurationFoldout(_model.UI.ShowConfiguration);
            _view.UpdateSectionVisibility(_model.UI.ConnectionMode);

            if (McpEditorWindowRefreshPolicy.ShouldRefreshSkillInstallState(refreshMode, refreshSkillInstallState))
            {
                RefreshSelectedTargetInstallStateFast();
            }

            if (runExpensiveChecks)
            {
                RefreshCliVersionInBackground();
                if (refreshSkillInstallState)
                {
                    RefreshSelectedTargetInstallStateInBackground();
                }
            }
            RefreshCliSetupSection(runExpensiveChecks);

            ConnectedToolsData toolsData = CreateConnectedToolsData();
            _view.UpdateConnectedTools(toolsData);

            EditorConfigData configData = runExpensiveChecks
                ? CreateEditorConfigData()
                : CreateEditorConfigPlaceholderData();
            _view.UpdateEditorConfig(configData);

            RefreshToolSettingsHeader();
            if (runExpensiveChecks)
            {
                RefreshToolSettingsCatalogIfNeeded();
            }
        }

        private async void RefreshCliVersionInBackground()
        {
            if (CliInstallationDetector.IsCheckCompleted())
            {
                return;
            }

            await CliInstallationDetector.RefreshCliVersionAsync(CancellationToken.None);
            RefreshCliSetupSection();
            RefreshSelectedTargetInstallStateInBackground();
        }

        private async void HandleRefreshCliVersion()
        {
            if (_isRefreshingVersion)
            {
                return;
            }

            _isRefreshingVersion = true;
            RefreshCliSetupSection();

            try
            {
                Task forceRefresh = CliInstallationDetector.ForceRefreshCliVersionAsync(CancellationToken.None);
                Task minimumDelay = Task.Delay(500);
                await Task.WhenAll(forceRefresh, minimumDelay);
            }
            finally
            {
                _isRefreshingVersion = false;
                RefreshCliSetupSection();
                RefreshSelectedTargetInstallStateInBackground();
            }
        }

        public void RefreshConnectedToolsSection()
        {
            if (_view == null)
            {
                return;
            }

            ConnectedToolsData toolsData = CreateConnectedToolsData();
            _view.UpdateConnectedTools(toolsData);
        }

        private ConnectedToolsData CreateConnectedToolsData()
        {
            bool isServerRunning = McpServerController.IsServerRunning;
            ConnectedClient[] connectedClients = GetConnectedToolsAsClients().ToArray();
            bool showReconnectingUIFlag = McpEditorSettings.GetShowReconnectingUI();
            bool showPostCompileUIFlag = McpEditorSettings.GetShowPostCompileReconnectingUI();
            bool hasNamedClients = connectedClients.Any();
            bool showReconnectingUI = (showReconnectingUIFlag || showPostCompileUIFlag) && !hasNamedClients;

            if (hasNamedClients && showPostCompileUIFlag)
            {
                McpEditorSettings.ClearPostCompileReconnectingUI();
            }

            bool showSection = isServerRunning && hasNamedClients;

            return new ConnectedToolsData(connectedClients, _model.UI.ShowConnectedTools, isServerRunning, showReconnectingUI, showSection);
        }

        private EditorConfigData CreateEditorConfigData()
        {
            bool isServerRunning = McpServerController.IsServerRunning;
            int currentPort = McpServerController.ServerPort;

            bool isConfigured = false;
            bool hasPortMismatch = false;
            bool isUpdateNeeded = true;
            string configurationError = null;

            IMcpConfigService configService = GetConfigService(_model.UI.SelectedEditorType);
            isConfigured = configService.IsConfigured();

            if (isConfigured)
            {
                int configuredPort = configService.GetConfiguredPort();

                if (isServerRunning)
                {
                    hasPortMismatch = currentPort != configuredPort;
                }
                else
                {
                    hasPortMismatch = McpEditorSettings.GetCustomPort() != configuredPort;
                }
            }

            int portToCheck = isServerRunning ? currentPort : McpEditorSettings.GetCustomPort();
            isUpdateNeeded = configService.IsUpdateNeeded(portToCheck);

            return new EditorConfigData(
                _model.UI.SelectedEditorType,
                isServerRunning,
                currentPort,
                isConfigured,
                hasPortMismatch,
                configurationError,
                isUpdateNeeded,
                _model.UI.AddRepositoryRoot,
                _model.UI.SupportsRepositoryRootToggle,
                _model.UI.ShowRepositoryRootToggle);
        }

        private EditorConfigData CreateEditorConfigPlaceholderData()
        {
            return new EditorConfigData(
                _model.UI.SelectedEditorType,
                McpServerController.IsServerRunning,
                McpServerController.ServerPort,
                isConfigured: false,
                hasPortMismatch: false,
                configurationError: null,
                isUpdateNeeded: true,
                addRepositoryRoot: _model.UI.AddRepositoryRoot,
                supportsRepositoryRootToggle: _model.UI.SupportsRepositoryRootToggle,
                showRepositoryRootToggle: _model.UI.ShowRepositoryRootToggle,
                isChecking: true);
        }

        public void InvalidateToolSettingsCatalog()
        {
            _isToolSettingsCatalogDirty = true;
        }

        private void RefreshToolSettingsHeader()
        {
            ToolSettingsSectionData toolSettingsData = CreateToolSettingsHeaderData();
            _view.UpdateToolSettings(toolSettingsData);
        }

        private void RefreshToolSettingsCatalog()
        {
            ToolSettingsSectionData toolSettingsData = CreateToolSettingsData();
            _view.UpdateToolSettings(toolSettingsData);

            if (McpEditorWindowRefreshPolicy.ShouldKeepToolSettingsCatalogDirty(toolSettingsData))
            {
                if (ScheduleToolSettingsRegistryWarmup())
                {
                    _isToolSettingsCatalogDirty = true;
                    return;
                }

                _isToolSettingsCatalogDirty = false;
                return;
            }

            CancelToolSettingsRegistryWarmup();
            ResetToolSettingsRegistryWarmupAttemptCount();
            _isToolSettingsCatalogDirty = false;
        }

        private void RefreshToolSettingsCatalogIfNeeded()
        {
            if (!_model.UI.ShowToolSettings || !_isToolSettingsCatalogDirty)
            {
                return;
            }

            if (_view == null)
            {
                return;
            }

            RefreshToolSettingsCatalog();
        }

        private ToolSettingsSectionData CreateToolSettingsHeaderData()
        {
            return new ToolSettingsSectionData(
                _model.UI.ShowToolSettings,
                ULoopSettings.GetAllowThirdPartyTools(),
                ULoopSettings.GetDynamicCodeSecurityLevel(),
                System.Array.Empty<ToolToggleItem>(),
                System.Array.Empty<ToolToggleItem>(),
                true,
                false);
        }

        private ToolSettingsSectionData CreateToolSettingsData()
        {
            UnityToolRegistry registry = CustomToolManager.TryGetRegistry();
            if (registry == null)
            {
                return new ToolSettingsSectionData(
                    _model.UI.ShowToolSettings,
                    ULoopSettings.GetAllowThirdPartyTools(),
                    ULoopSettings.GetDynamicCodeSecurityLevel(),
                    System.Array.Empty<ToolToggleItem>(),
                    System.Array.Empty<ToolToggleItem>(),
                    false,
                    true);
            }

            ToolSettingsCatalogItem[] allTools = registry.GetToolSettingsCatalog();

            System.Collections.Generic.List<ToolToggleItem> builtIn = new();
            System.Collections.Generic.List<ToolToggleItem> thirdParty = new();

            foreach (ToolSettingsCatalogItem tool in allTools)
            {
                if (tool.DisplayDevelopmentOnly)
                {
                    continue;
                }

                bool isEnabled = ToolSettings.IsToolEnabled(tool.Name);
                bool isThirdPartyTool = tool.IsThirdParty;

                ToolToggleItem item = new ToolToggleItem(tool.Name, tool.Description, isEnabled, isThirdPartyTool);
                if (isThirdPartyTool)
                {
                    thirdParty.Add(item);
                }
                else
                {
                    builtIn.Add(item);
                }
            }

            Comparison<ToolToggleItem> compareByName = (a, b) => string.Compare(a.ToolName, b.ToolName, StringComparison.Ordinal);
            builtIn.Sort(compareByName);
            thirdParty.Sort(compareByName);

            return new ToolSettingsSectionData(
                _model.UI.ShowToolSettings,
                ULoopSettings.GetAllowThirdPartyTools(),
                ULoopSettings.GetDynamicCodeSecurityLevel(),
                builtIn.ToArray(),
                thirdParty.ToArray(),
                true,
                true);
        }

        private void UpdateShowToolSettings(bool show)
        {
            _model.UpdateShowToolSettings(show);
            RefreshToolSettingsHeader();

            if (!show)
            {
                _isToolSettingsCatalogDirty = true;
                CancelToolSettingsRegistryWarmup();
                ResetToolSettingsRegistryWarmupAttemptCount();
                return;
            }

            RefreshToolSettingsCatalogIfNeeded();
        }

        private bool ScheduleToolSettingsRegistryWarmup()
        {
            if (McpEditorWindowRefreshPolicy.ShouldStartToolSettingsRegistryWarmup(
                    _isToolSettingsRegistryWarmupScheduled,
                    _toolSettingsRegistryWarmupAttemptCount,
                    ToolSettingsRegistryWarmupMaxAttempts))
            {
                double delaySeconds = McpEditorWindowRefreshPolicy.CalculateToolSettingsRegistryWarmupDelaySeconds(
                    ToolSettingsRegistryWarmupInitialDelaySeconds,
                    ToolSettingsRegistryWarmupMaxDelaySeconds,
                    _toolSettingsRegistryWarmupAttemptCount);

                _isToolSettingsRegistryWarmupScheduled = true;
                _toolSettingsRegistryWarmupDueTime = EditorApplication.timeSinceStartup + delaySeconds;
                _toolSettingsRegistryWarmupAttemptCount++;
                EditorApplication.update += RunToolSettingsRegistryWarmupWhenDue;
                return true;
            }

            return _isToolSettingsRegistryWarmupScheduled;
        }

        private void RunToolSettingsRegistryWarmupWhenDue()
        {
            if (EditorApplication.timeSinceStartup < _toolSettingsRegistryWarmupDueTime)
            {
                return;
            }

            CancelToolSettingsRegistryWarmup();

            if (_view == null || !_model.UI.ShowToolSettings)
            {
                ResetToolSettingsRegistryWarmupAttemptCount();
                return;
            }

            CustomToolManager.WarmupRegistry();
            InvalidateToolSettingsCatalog();
            RefreshToolSettingsCatalogIfNeeded();
        }

        private void CancelToolSettingsRegistryWarmup()
        {
            if (!_isToolSettingsRegistryWarmupScheduled)
            {
                return;
            }

            EditorApplication.update -= RunToolSettingsRegistryWarmupWhenDue;
            _isToolSettingsRegistryWarmupScheduled = false;
        }

        private void ResetToolSettingsRegistryWarmupAttemptCount()
        {
            _toolSettingsRegistryWarmupAttemptCount = 0;
        }

        private void HandleToolToggled(string toolName, bool enabled)
        {
            _model.UpdateToolEnabled(toolName, enabled);
            _view?.UpdateSingleToolToggle(toolName, enabled);

            // Skill synchronization can touch many files, so defer it to keep UI input responsive.
            EditorApplication.delayCall += () => ApplyToolToggleSideEffects(toolName, enabled);
        }

        private async void ApplyToolToggleSideEffects(string toolName, bool enabled)
        {
            ClientNotificationService.TriggerToolChangeNotification();

            if (!enabled)
            {
                ToolSkillSynchronizer.RemoveSkillFiles(toolName);
            }
            else
            {
                await ToolSkillSynchronizer.InstallSkillFilesForTool(toolName, !_installSkillsFlat);

                if (!ToolSkillSynchronizer.IsSkillInstalled(toolName))
                {
                    Debug.LogWarning(
                        $"[uLoopMCP] Skill for '{toolName}' was not installed after enabling. " +
                        "The skill source may have an incorrect directory structure " +
                        "(expected: <ToolDir>/Skill/SKILL.md). Run 'uloop skills list' for details."
                    );
                }
            }
        }

        private void ConfigureEditor()
        {
            IMcpConfigService configService = GetConfigService(_model.UI.SelectedEditorType);
            bool isServerRunning = McpServerController.IsServerRunning;
            int portToUse = isServerRunning ? McpServerController.ServerPort : McpEditorSettings.GetCustomPort();

            configService.AutoConfigure(portToUse);
            RefreshAllSections();
        }

        private void DeleteEditorConfiguration()
        {
            string editorName = GetEditorDisplayName(_model.UI.SelectedEditorType);

            bool confirmed = EditorUtility.DisplayDialog(
                "Delete MCP Configuration",
                $"Are you sure you want to delete the {editorName} MCP configuration?\n\n" +
                "This will remove the uLoopMCP entry from the configuration file. " +
                "Other MCP server configurations will not be affected.",
                "Delete",
                "Cancel");

            if (!confirmed)
            {
                return;
            }

            IMcpConfigService configService = GetConfigService(_model.UI.SelectedEditorType);
            configService.DeleteConfiguration();
            RefreshAllSections();
        }

        private void OpenConfigurationFile()
        {
            string projectRoot = UnityMcpPathResolver.GetProjectRoot();
            string gitRoot = UnityMcpPathResolver.GetGitRepositoryRoot();
            string baseRoot = _model.UI.AddRepositoryRoot
                ? (gitRoot ?? projectRoot)
                : projectRoot;

            string configPath = UnityMcpPathResolver.GetConfigPathForRoot(_model.UI.SelectedEditorType, baseRoot);
            bool exists = System.IO.File.Exists(configPath);

            if (exists)
            {
                EditorUtility.OpenWithDefaultApp(configPath);
            }
            else
            {
                string editorName = GetEditorDisplayName(_model.UI.SelectedEditorType);
                EditorUtility.DisplayDialog(
                    "Configuration File Not Found",
                    $"Configuration file for {editorName} not found at:\n{configPath}\n\nPlease run 'Configure {editorName}' first to create the configuration file.",
                    "OK");
            }
        }

        private string GetEditorDisplayName(McpEditorType editorType)
        {
            return editorType switch
            {
                McpEditorType.Cursor => "Cursor",
                McpEditorType.ClaudeCode => "Claude Code",
                McpEditorType.VSCode => "VSCode",
                McpEditorType.GeminiCLI => "Gemini CLI",
                McpEditorType.Codex => "Codex",
                McpEditorType.McpInspector => "MCP Inspector",
                _ => editorType.ToString()
            };
        }

        private IMcpConfigService GetConfigService(McpEditorType editorType)
        {
            return _configServiceFactory.GetConfigService(editorType);
        }

        private void UpdateShowConnectedTools(bool show)
        {
            _model.UpdateShowConnectedTools(show);
        }

        private void UpdateSelectedEditorType(McpEditorType type)
        {
            _model.UpdateSelectedEditorType(type);
            RefreshAllSections();
        }

        private void UpdateShowConfiguration(bool show)
        {
            _model.UpdateShowConfiguration(show);
        }

        private void UpdateAllowThirdPartyTools(bool allow)
        {
            _model.UpdateAllowThirdPartyTools(allow);
            RefreshToolSettingsHeader();
        }

        private void UpdateAddRepositoryRoot(bool addRepositoryRoot)
        {
            _model.UpdateAddRepositoryRoot(addRepositoryRoot);
            RefreshAllSections();
        }

        private void UpdateDynamicCodeSecurityLevel(DynamicCodeSecurityLevel level)
        {
            ULoopSettings.SetDynamicCodeSecurityLevel(level);
        }

        private void UpdateConnectionMode(ConnectionMode mode)
        {
            _model.UpdateConnectionMode(mode);
            _view.UpdateConnectionMode(new ConnectionModeData(mode));
            _view.UpdateSectionVisibility(mode);
            RefreshCliSetupSection();
        }

        private void RefreshCliSetupSection(bool includeSkillDirectoryChecks = true)
        {
            if (_view == null)
            {
                return;
            }

            CliSetupData cliData = CreateCliSetupData(includeSkillDirectoryChecks);
            _view.UpdateCliSetup(cliData);
        }

        private CliSetupData CreateCliSetupData(bool includeSkillDirectoryChecks = true)
        {
            string cliVersion = CliInstallationDetector.GetCachedCliVersion();
            bool isCliInstalled = cliVersion != null;
            bool isChecking = !CliInstallationDetector.IsCheckCompleted()
                || _isRefreshingVersion
                || !includeSkillDirectoryChecks;
            string packageVersion = McpConstants.PackageInfo.version;
            bool needsUpdate = false;
            bool needsDowngrade = false;
            if (isCliInstalled)
            {
                bool comparisonAvailable = CliVersionComparison.TryCompare(
                    cliVersion,
                    packageVersion,
                    out int cliVersionComparison);
                needsUpdate = comparisonAvailable && cliVersionComparison < 0;
                needsDowngrade = comparisonAvailable && cliVersionComparison > 0;
            }
            bool groupSkillsUnderUnityCliLoop = !_installSkillsFlat;
            SkillInstallState selectedTargetInstallState = includeSkillDirectoryChecks
                ? _selectedTargetInstallState
                : SkillInstallState.Checking;

            return new CliSetupData(
                isCliInstalled,
                cliVersion,
                packageVersion,
                needsUpdate,
                needsDowngrade,
                _isInstallingCli,
                isChecking,
                isClaudeSkillsInstalled: false,
                isAgentsSkillsInstalled: false,
                isCursorSkillsInstalled: false,
                isGeminiSkillsInstalled: false,
                isCodexSkillsInstalled: false,
                isAntigravitySkillsInstalled: false,
                selectedTargetInstallState,
                _skillsTarget,
                groupSkillsUnderUnityCliLoop,
                _isInstallingSkills);
        }

        private void RefreshSelectedTargetInstallStateFast()
        {
            if (!CliInstallationDetector.IsCliInstalled())
            {
                _selectedTargetInstallState = SkillInstallState.Missing;
                RefreshCliSetupSection();
                return;
            }

            _selectedTargetInstallState = GetSelectedTargetInstallState(includeFreshnessCheck: false);
            RefreshCliSetupSection();
        }

        private void RefreshSelectedTargetInstallStateInBackground()
        {
            CancelSkillInstallStateRefresh();
            if (!CliInstallationDetector.IsCliInstalled() || _isRefreshingVersion || _isInstallingSkills)
            {
                return;
            }

            CancellationTokenSource cts = new();
            _skillInstallStateRefreshCts = cts;
            RefreshSelectedTargetInstallStateAsync(cts.Token);
        }

        private async void RefreshSelectedTargetInstallStateAsync(CancellationToken ct)
        {
            string projectRoot = UnityMcpPathResolver.GetProjectRoot();
            SkillInstallState installState = await Task.Run(
                () => GetSelectedTargetInstallState(projectRoot, includeFreshnessCheck: true));
            if (ct.IsCancellationRequested)
            {
                return;
            }

            _selectedTargetInstallState = installState;
            RefreshCliSetupSection();
        }

        private SkillInstallState GetSelectedTargetInstallState(bool includeFreshnessCheck)
        {
            string projectRoot = UnityMcpPathResolver.GetProjectRoot();
            return GetSelectedTargetInstallState(projectRoot, includeFreshnessCheck);
        }

        private SkillInstallState GetSelectedTargetInstallState(
            string projectRoot,
            bool includeFreshnessCheck)
        {
            SkillsTargetSelection selection = SkillsTargetSelectionResolver.Resolve(
                _skillsTarget,
                !_installSkillsFlat);
            List<ToolSkillSynchronizer.SkillTargetInfo> targets = includeFreshnessCheck
                ? ToolSkillSynchronizer.DetectTargetsForLayoutAtProjectRoot(projectRoot, !_installSkillsFlat)
                : ToolSkillSynchronizer.DetectTargetsForLayoutFastAtProjectRoot(projectRoot, !_installSkillsFlat);
            ToolSkillSynchronizer.SkillTargetInfo targetInfo = targets
                .FirstOrDefault(target => target.DirName == selection.DirectoryName);

            return string.IsNullOrEmpty(targetInfo.DirName)
                ? SkillInstallState.Missing
                : targetInfo.InstallState;
        }

        private void CancelSkillInstallStateRefresh()
        {
            if (_skillInstallStateRefreshCts == null)
            {
                return;
            }

            _skillInstallStateRefreshCts.Cancel();
            _skillInstallStateRefreshCts.Dispose();
            _skillInstallStateRefreshCts = null;
        }

        private async void HandleInstallCli()
        {
            bool wasCliInstalledBeforeInstall = CliInstallationDetector.IsCliInstalled();
            string npmPath = NodeEnvironmentResolver.FindNpmPath();
            if (string.IsNullOrEmpty(npmPath))
            {
                EditorUtility.DisplayDialog(
                    "npm Not Found",
                    "npm was not found on this system.\nPlease install Node.js first, then try again.",
                    "OK");
                return;
            }

            string packageVersion = McpConstants.PackageInfo.version;
            string installTarget = $"{CliConstants.NPM_PACKAGE_NAME}@{packageVersion}";

            bool permissionOk = CliInstaller.CheckWindowsPermissions(
                npmPath, installTarget, out string globalPrefix, out string manualCommand);
            if (!permissionOk)
            {
                EditorUtility.DisplayDialog(
                    "Permission Issue Detected",
                    $"npm's global directory ({globalPrefix}) requires elevated permissions.\n\n"
                    + NpmInstallDiagnostics.BuildPermissionSolutions(manualCommand),
                    "OK");
                return;
            }

            _isInstallingCli = true;
            RefreshCliSetupSection();

            try
            {
                string nodePath = NodeEnvironmentResolver.FindNodePath();
                CliInstallResult result = await CliInstaller.InstallAsync(npmPath, installTarget, nodePath);

                if (!result.Success)
                {
                    string installCommand = $"npm install -g {installTarget}";

                    // Classifier emits Windows-specific remediation with the command embedded;
                    // on other platforms (or unrecognized errors) show raw stderr + manual command footer
                    string guidance = Application.platform == RuntimePlatform.WindowsEditor
                        ? NpmInstallDiagnostics.ClassifyInstallError(result.ErrorOutput, installCommand)
                        : null;

                    string message;
                    if (guidance != null && !string.IsNullOrEmpty(result.ErrorOutput))
                    {
                        message = "Failed to install uLoop CLI.\n\n"
                            + NpmInstallDiagnostics.BuildInstallErrorMessage(guidance, result.ErrorOutput);
                    }
                    else
                    {
                        message = $"Failed to install uLoop CLI.\n\n{result.ErrorOutput}\n\nYou can try manually:\n{installCommand}";
                    }

                    EditorUtility.DisplayDialog("Installation Failed", message, "OK");
                }
            }
            finally
            {
                _isInstallingCli = false;
                RefreshAllSections(
                    refreshSkillInstallState:
                    CliInstallRefreshPolicy.ShouldRefreshSkillsAfterCliInstall(wasCliInstalledBeforeInstall));
            }
        }

        private async void HandleInstallSkills()
        {
            if (!CliInstallationDetector.IsCliInstalled())
            {
                EditorUtility.DisplayDialog(
                    "CLI Not Found",
                    "uloop-cli is not installed. Please install the CLI first.",
                    "OK");
                return;
            }

            CancelSkillInstallStateRefresh();
            _isInstallingSkills = true;
            RefreshCliSetupSection();

            try
            {
                SkillsTargetSelection selection = SkillsTargetSelectionResolver.Resolve(
                    _skillsTarget,
                    !_installSkillsFlat);
                ToolSkillSynchronizer.SkillTargetInfo target = new(
                    selection.DisplayName,
                    selection.DirectoryName,
                    selection.InstallFlag,
                    hasSkillsDirectory: true,
                    hasExistingSkills: false);
                await ToolSkillSynchronizer.InstallSkillFiles(
                    new List<ToolSkillSynchronizer.SkillTargetInfo> { target },
                    !_installSkillsFlat);
                EditorDialogHelper.ShowSkillsInstalledDialog();
            }
            finally
            {
                _isInstallingSkills = false;
                RefreshSelectedTargetInstallStateFast();
                RefreshSelectedTargetInstallStateInBackground();
                RefreshCliSetupSection();
            }
        }

        private void HandleGroupSkillsChanged(bool groupSkillsUnderUnityCliLoop)
        {
            ApplyFlatSkillInstallPreference();
            RefreshSelectedTargetInstallStateFast();
            RefreshSelectedTargetInstallStateInBackground();
        }

        private void RefreshRepositoryRootSupport()
        {
            ApplyFlatSkillInstallPreference();

            bool gitRootDiffers = UnityMcpPathResolver.GitRootDiffersFromProjectRoot();
            _model.UpdateSupportsRepositoryRootToggle(gitRootDiffers);
            _model.UpdateShowRepositoryRootToggle(gitRootDiffers);

            if (!gitRootDiffers && _model.UI.AddRepositoryRoot)
            {
                _model.UpdateAddRepositoryRoot(false);
            }
        }

        private void ApplyFlatSkillInstallPreference()
        {
            // Claude Code does not resolve nested skill folders, so editor-driven installs stay flat for every target.
            _installSkillsFlat = ForceFlatSkillInstall;
            McpEditorSettings.SetInstallSkillsFlat(_installSkillsFlat);
        }

        private void HandleRefreshSkillsState()
        {
            RefreshSelectedTargetInstallStateFast();
            RefreshSelectedTargetInstallStateInBackground();
        }

    }
}
