// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.IO.Abstractions;
using System.Linq;
using System.Runtime.Versioning;
using System.Text.Json;

using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
using Microsoft.PowerToys.Settings.UI.SerializationContext;
using Microsoft.UI.Dispatching;
using Microsoft.Win32;
using Windows.Security.Credentials;

namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
    public partial class AdvancedPasteViewModel : PageViewModelBase
    {
        private static readonly HashSet<string> WarnHotkeys = ["Ctrl + V", "Ctrl + Shift + V"];

        private bool _disposed;
        private PasteAIProviderDefinition _pasteAIProviderDraft;
        private PasteAIProviderDefinition _editingPasteAIProvider;

        protected override string ModuleName => AdvancedPasteSettings.ModuleName;

        private GeneralSettings GeneralSettingsConfig { get; set; }

        private readonly SettingsUtils _settingsUtils;

        private readonly AdvancedPasteSettings _advancedPasteSettings;
        private readonly AdvancedPasteAdditionalActions _additionalActions;
        private readonly ObservableCollection<AdvancedPasteCustomAction> _customActions;
        private readonly DispatcherQueue _dispatcherQueue;
        private IFileSystemWatcher _settingsWatcher;
        private bool _suppressSave;

        private GpoRuleConfigured _enabledGpoRuleConfiguration;
        private bool _enabledStateIsGPOConfigured;
        private GpoRuleConfigured _onlineAIModelsGpoRuleConfiguration;
        private bool _onlineAIModelsDisallowedByGPO;
        private bool _isEnabled;

        private Func<string, int> SendConfigMSG { get; }

        private static readonly HashSet<string> CustomActionNonPersistedProperties = new(StringComparer.Ordinal)
        {
            nameof(AdvancedPasteCustomAction.CanMoveUp),
            nameof(AdvancedPasteCustomAction.CanMoveDown),
            nameof(AdvancedPasteCustomAction.IsValid),
            nameof(AdvancedPasteCustomAction.HasConflict),
            nameof(AdvancedPasteCustomAction.Tooltip),
            nameof(AdvancedPasteCustomAction.SubActions),
        };

        public AdvancedPasteViewModel(
            SettingsUtils settingsUtils,
            ISettingsRepository<GeneralSettings> settingsRepository,
            ISettingsRepository<AdvancedPasteSettings> advancedPasteSettingsRepository,
            Func<string, int> ipcMSGCallBackFunc)
        {
            // To obtain the general settings configurations of PowerToys Settings.
            ArgumentNullException.ThrowIfNull(settingsRepository);

            GeneralSettingsConfig = settingsRepository.SettingsConfig;

            // To obtain the settings configurations of Advanced Paste.
            ArgumentNullException.ThrowIfNull(advancedPasteSettingsRepository);

            _dispatcherQueue = DispatcherQueue.GetForCurrentThread();

            _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));

            _advancedPasteSettings = advancedPasteSettingsRepository.SettingsConfig ?? throw new ArgumentException("SettingsConfig cannot be null", nameof(advancedPasteSettingsRepository));

            if (_advancedPasteSettings.Properties is null)
            {
                throw new ArgumentException("AdvancedPasteSettings.Properties cannot be null", nameof(advancedPasteSettingsRepository));
            }

            // Ensure AdditionalActions and CustomActions are initialized to prevent null reference exceptions
            // This handles legacy settings files that may be missing these properties
            _advancedPasteSettings.Properties.AdditionalActions ??= new AdvancedPasteAdditionalActions();
            _advancedPasteSettings.Properties.CustomActions ??= new AdvancedPasteCustomActions();

            AttachConfigurationHandlers();

            // set the callback functions value to handle outgoing IPC message.
            SendConfigMSG = ipcMSGCallBackFunc;

            _additionalActions = _advancedPasteSettings.Properties.AdditionalActions;
            _customActions = _advancedPasteSettings.Properties.CustomActions.Value ?? new ObservableCollection<AdvancedPasteCustomAction>();

            SetupSettingsFileWatcher();

            InitializePasteAIProviderState();

            InitializeEnabledValue();
            MigrateLegacyAIEnablement();

            foreach (var action in _additionalActions.GetAllActions())
            {
                action.PropertyChanged += OnAdditionalActionPropertyChanged;
            }

            foreach (var customAction in _customActions)
            {
                customAction.PropertyChanged += OnCustomActionPropertyChanged;
            }

            _customActions.CollectionChanged += OnCustomActionsCollectionChanged;
            UpdateCustomActionsCanMoveUpDown();
        }

        public override Dictionary<string, HotkeySettings[]> GetAllHotkeySettings()
        {
            var hotkeySettings = new List<HotkeySettings>
            {
                PasteAsPlainTextShortcut,
                AdvancedPasteUIShortcut,
                PasteAsMarkdownShortcut,
                PasteAsJsonShortcut,
            };

            foreach (var action in _additionalActions.GetAllActions())
            {
                if (action is AdvancedPasteAdditionalAction additionalAction)
                {
                    hotkeySettings.Add(additionalAction.Shortcut);
                }
            }

            // Custom actions do not have localization header, just use the action name.
            foreach (var customAction in _customActions)
            {
                hotkeySettings.Add(customAction.Shortcut);
            }

            return new Dictionary<string, HotkeySettings[]>
            {
                [ModuleName] = hotkeySettings.ToArray(),
            };
        }

        private void InitializeEnabledValue()
        {
            _enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredAdvancedPasteEnabledValue();
            if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
            {
                // Get the enabled state from GPO.
                _enabledStateIsGPOConfigured = true;
                _isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
            }
            else
            {
                _isEnabled = GeneralSettingsConfig.Enabled.AdvancedPaste;
            }

            _onlineAIModelsGpoRuleConfiguration = GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue();
            _onlineAIModelsDisallowedByGPO = _onlineAIModelsGpoRuleConfiguration == GpoRuleConfigured.Disabled;

            if (_onlineAIModelsDisallowedByGPO)
            {
                // disable AI if it was enabled
                DisableAI();
            }
        }

        private void MigrateLegacyAIEnablement()
        {
            var properties = _advancedPasteSettings?.Properties;
            if (properties is null)
            {
                return;
            }

            bool legacyAdvancedAIConsumed = properties.TryConsumeLegacyAdvancedAIEnabled(out var advancedFlag);
            bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed && advancedFlag;

            if (IsOnlineAIModelsDisallowedByGPO)
            {
                if (legacyAdvancedAIConsumed)
                {
                    SaveAndNotifySettings();
                }

                return;
            }

            PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();

            if (legacyCredential is null)
            {
                if (legacyAdvancedAIConsumed)
                {
                    SaveAndNotifySettings();
                }

                return;
            }

            var configuration = properties.PasteAIConfiguration;
            if (configuration is null)
            {
                configuration = new PasteAIConfiguration();
                properties.PasteAIConfiguration = configuration;
            }

            bool configurationUpdated = false;

            var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
            PasteAIProviderDefinition openAIProvider = ensureResult.Provider;
            configurationUpdated |= ensureResult.Updated;

            if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled)
            {
                openAIProvider.EnableAdvancedAI = legacyAdvancedAIEnabled;
                configurationUpdated = true;
            }

            if (legacyCredential is not null && openAIProvider is not null)
            {
                SavePasteAIApiKey(openAIProvider.Id, openAIProvider.ServiceType, legacyCredential.Password);
                RemoveLegacyOpenAICredential();
            }

            const bool shouldEnableAI = true;
            bool enabledChanged = false;
            if (properties.IsAIEnabled != shouldEnableAI)
            {
                properties.IsAIEnabled = shouldEnableAI;
                enabledChanged = true;
            }

            bool shouldPersist = configurationUpdated || enabledChanged || legacyAdvancedAIConsumed;

            if (shouldPersist)
            {
                SaveAndNotifySettings();

                if (configurationUpdated)
                {
                    OnPropertyChanged(nameof(PasteAIConfiguration));
                }

                if (enabledChanged)
                {
                    OnPropertyChanged(nameof(IsAIEnabled));
                }
            }
        }

        public bool IsEnabled
        {
            get => _isEnabled;
            set
            {
                if (_enabledStateIsGPOConfigured)
                {
                    // If it's GPO configured, shouldn't be able to change this state.
                    return;
                }

                if (_isEnabled != value)
                {
                    _isEnabled = value;
                    OnPropertyChanged(nameof(IsEnabled));
                    OnPropertyChanged(nameof(ShowOnlineAIModelsGpoConfiguredInfoBar));
                    OnPropertyChanged(nameof(ShowClipboardHistoryIsGpoConfiguredInfoBar));

                    // Set the status of AdvancedPaste in the general settings
                    GeneralSettingsConfig.Enabled.AdvancedPaste = value;
                    var outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);

                    SendConfigMSG(outgoing.ToString());
                }
            }
        }

        public ObservableCollection<AdvancedPasteCustomAction> CustomActions => _customActions;

        public AdvancedPasteAdditionalActions AdditionalActions => _additionalActions;

        public static IEnumerable<AIServiceTypeMetadata> AvailableProviders => AIServiceTypeRegistry.GetAvailableServiceTypes();

        /// <summary>
        /// Gets available AI providers filtered by GPO policies.
        /// Only returns providers that are not explicitly disabled by GPO.
        /// </summary>
        public IEnumerable<AIServiceTypeMetadata> AvailableProvidersFilteredByGPO =>
            AvailableProviders.Where(metadata => IsServiceTypeAllowedByGPO(metadata.ServiceType));

        public bool IsAIEnabled => _advancedPasteSettings.Properties.IsAIEnabled && !IsOnlineAIModelsDisallowedByGPO;

        private PasswordCredential TryGetLegacyOpenAICredential()
        {
            try
            {
                PasswordVault vault = new();
                var credential = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
                credential?.RetrievePassword();
                return credential;
            }
            catch (Exception)
            {
                return null;
            }
        }

        private void RemoveLegacyOpenAICredential()
        {
            try
            {
                PasswordVault vault = new();
                TryRemoveCredential(vault, "https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
            }
            catch (Exception)
            {
            }
        }

        public bool IsEnabledGpoConfigured
        {
            get => _enabledStateIsGPOConfigured;
        }

        public bool IsOnlineAIModelsDisallowedByGPO
        {
            get => _onlineAIModelsDisallowedByGPO || _enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled;
        }

        public bool ShowOnlineAIModelsGpoConfiguredInfoBar
        {
            get => _onlineAIModelsDisallowedByGPO && _isEnabled;
        }

        private bool IsClipboardHistoryEnabled()
        {
            string registryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Clipboard\";
            try
            {
                int enableClipboardHistory = (int)Registry.GetValue(registryKey, "EnableClipboardHistory", false);
                return enableClipboardHistory != 0;
            }
            catch (Exception)
            {
                return false;
            }
        }

        private bool IsClipboardHistoryDisabledByGPO()
        {
            string registryKey = @"HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\System\";
            try
            {
                object allowClipboardHistory = Registry.GetValue(registryKey, "AllowClipboardHistory", null);
                if (allowClipboardHistory != null)
                {
                    return (int)allowClipboardHistory == 0;
                }
                else
                {
                    return false;
                }
            }
            catch (Exception)
            {
                return false;
            }
        }

        private void SetClipboardHistoryEnabled(bool value)
        {
            string registryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Clipboard\";
            try
            {
                Registry.SetValue(registryKey, "EnableClipboardHistory", value ? 1 : 0);
            }
            catch (Exception)
            {
            }
        }

        public bool ClipboardHistoryEnabled
        {
            get => IsClipboardHistoryEnabled();
            set
            {
                if (IsClipboardHistoryEnabled() != value)
                {
                    SetClipboardHistoryEnabled(value);
                }
            }
        }

        public bool ClipboardHistoryDisabledByGPO
        {
            get => IsClipboardHistoryDisabledByGPO();
        }

        public bool ShowClipboardHistoryIsGpoConfiguredInfoBar
        {
            get => IsClipboardHistoryDisabledByGPO() && _isEnabled;
        }

        public HotkeySettings AdvancedPasteUIShortcut
        {
            get => _advancedPasteSettings.Properties.AdvancedPasteUIShortcut;
            set
            {
                if (_advancedPasteSettings.Properties.AdvancedPasteUIShortcut != value)
                {
                    _advancedPasteSettings.Properties.AdvancedPasteUIShortcut = value ?? AdvancedPasteProperties.DefaultAdvancedPasteUIShortcut;
                    OnPropertyChanged(nameof(IsConflictingCopyShortcut));
                    OnPropertyChanged(nameof(AdvancedPasteUIShortcut));
                    SaveAndNotifySettings();
                }
            }
        }

        public HotkeySettings PasteAsPlainTextShortcut
        {
            get => _advancedPasteSettings.Properties.PasteAsPlainTextShortcut;
            set
            {
                if (_advancedPasteSettings.Properties.PasteAsPlainTextShortcut != value)
                {
                    _advancedPasteSettings.Properties.PasteAsPlainTextShortcut = value ?? AdvancedPasteProperties.DefaultPasteAsPlainTextShortcut;
                    OnPropertyChanged(nameof(IsConflictingCopyShortcut));
                    OnPropertyChanged(nameof(PasteAsPlainTextShortcut));
                    SaveAndNotifySettings();
                }
            }
        }

        public HotkeySettings PasteAsMarkdownShortcut
        {
            get => _advancedPasteSettings.Properties.PasteAsMarkdownShortcut;
            set
            {
                if (_advancedPasteSettings.Properties.PasteAsMarkdownShortcut != value)
                {
                    _advancedPasteSettings.Properties.PasteAsMarkdownShortcut = value ?? new HotkeySettings();
                    OnPropertyChanged(nameof(IsConflictingCopyShortcut));
                    OnPropertyChanged(nameof(PasteAsMarkdownShortcut));
                    SaveAndNotifySettings();
                }
            }
        }

        public HotkeySettings PasteAsJsonShortcut
        {
            get => _advancedPasteSettings.Properties.PasteAsJsonShortcut;
            set
            {
                if (_advancedPasteSettings.Properties.PasteAsJsonShortcut != value)
                {
                    _advancedPasteSettings.Properties.PasteAsJsonShortcut = value ?? new HotkeySettings();
                    OnPropertyChanged(nameof(IsConflictingCopyShortcut));
                    OnPropertyChanged(nameof(PasteAsJsonShortcut));
                    SaveAndNotifySettings();
                }
            }
        }

        public PasteAIConfiguration PasteAIConfiguration
        {
            get
            {
                // Ensure PasteAIConfiguration is never null for XAML binding
                _advancedPasteSettings.Properties.PasteAIConfiguration ??= new PasteAIConfiguration();
                return _advancedPasteSettings.Properties.PasteAIConfiguration;
            }

            set
            {
                if (!ReferenceEquals(value, _advancedPasteSettings.Properties.PasteAIConfiguration))
                {
                    UnsubscribeFromPasteAIConfiguration(_advancedPasteSettings.Properties.PasteAIConfiguration);

                    var newValue = value ?? new PasteAIConfiguration();
                    _advancedPasteSettings.Properties.PasteAIConfiguration = newValue;
                    SubscribeToPasteAIConfiguration(newValue);

                    OnPropertyChanged(nameof(PasteAIConfiguration));
                    SaveAndNotifySettings();
                }
            }
        }

        public PasteAIProviderDefinition PasteAIProviderDraft
        {
            get => _pasteAIProviderDraft;
            private set
            {
                if (!ReferenceEquals(_pasteAIProviderDraft, value))
                {
                    _pasteAIProviderDraft = value;
                    OnPropertyChanged(nameof(PasteAIProviderDraft));
                    OnPropertyChanged(nameof(ShowPasteAIProviderGpoConfiguredInfoBar));
                }
            }
        }

        public bool ShowPasteAIProviderGpoConfiguredInfoBar
        {
            get
            {
                if (_pasteAIProviderDraft is null)
                {
                    return false;
                }

                var serviceType = _pasteAIProviderDraft.ServiceType.ToAIServiceType();
                return !IsServiceTypeAllowedByGPO(serviceType);
            }
        }

        public bool IsEditingPasteAIProvider => _editingPasteAIProvider is not null;

        public bool ShowCustomPreview
        {
            get => _advancedPasteSettings.Properties.ShowCustomPreview;
            set
            {
                if (value != _advancedPasteSettings.Properties.ShowCustomPreview)
                {
                    _advancedPasteSettings.Properties.ShowCustomPreview = value;
                    NotifySettingsChanged();
                }
            }
        }

        public bool CloseAfterLosingFocus
        {
            get => _advancedPasteSettings.Properties.CloseAfterLosingFocus;
            set
            {
                if (value != _advancedPasteSettings.Properties.CloseAfterLosingFocus)
                {
                    _advancedPasteSettings.Properties.CloseAfterLosingFocus = value;
                    NotifySettingsChanged();
                }
            }
        }

        public bool EnableClipboardPreview
        {
            get => _advancedPasteSettings.Properties.EnableClipboardPreview;
            set
            {
                if (value != _advancedPasteSettings.Properties.EnableClipboardPreview)
                {
                    _advancedPasteSettings.Properties.EnableClipboardPreview = value;
                    NotifySettingsChanged();
                }
            }
        }

        public bool AutoCopySelectionForCustomActionHotkey
        {
            get => _advancedPasteSettings.Properties.AutoCopySelectionForCustomActionHotkey;
            set
            {
                if (value != _advancedPasteSettings.Properties.AutoCopySelectionForCustomActionHotkey)
                {
                    _advancedPasteSettings.Properties.AutoCopySelectionForCustomActionHotkey = value;
                    NotifySettingsChanged();
                }
            }
        }

        public bool IsConflictingCopyShortcut =>
            _customActions.Select(customAction => customAction.Shortcut)
                          .Concat([PasteAsPlainTextShortcut, AdvancedPasteUIShortcut, PasteAsMarkdownShortcut, PasteAsJsonShortcut])
                          .Any(hotkey => WarnHotkeys.Contains(hotkey.ToString()));

        public bool IsAdditionalActionConflictingCopyShortcut =>
            _additionalActions.GetAllActions()
                              .OfType<AdvancedPasteAdditionalAction>()
                              .Select(additionalAction => additionalAction.Shortcut)
                              .Any(hotkey => WarnHotkeys.Contains(hotkey.ToString()));

        private void NotifySettingsChanged()
        {
            // Using InvariantCulture as this is an IPC message
            SendConfigMSG(
                string.Format(
                    CultureInfo.InvariantCulture,
                    "{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
                    AdvancedPasteSettings.ModuleName,
                    JsonSerializer.Serialize(_advancedPasteSettings, SourceGenerationContextContext.Default.AdvancedPasteSettings)));
        }

        public void RefreshEnabledState()
        {
            InitializeEnabledValue();
            MigrateLegacyAIEnablement();
            OnPropertyChanged(nameof(IsEnabled));
            OnPropertyChanged(nameof(ShowOnlineAIModelsGpoConfiguredInfoBar));
            OnPropertyChanged(nameof(ShowClipboardHistoryIsGpoConfiguredInfoBar));
            OnPropertyChanged(nameof(IsAIEnabled));
        }

        public void BeginAddPasteAIProvider(string serviceType)
        {
            var normalizedServiceType = NormalizeServiceType(serviceType, out var persistedServiceType);

            var metadata = AIServiceTypeRegistry.GetMetadata(normalizedServiceType);
            var provider = new PasteAIProviderDefinition
            {
                ServiceType = persistedServiceType,
                ModelName = PasteAIProviderDefaults.GetDefaultModelName(normalizedServiceType),
                EndpointUrl = string.Empty,
                ApiVersion = string.Empty,
                DeploymentName = string.Empty,
                ModelPath = string.Empty,
                SystemPrompt = string.Empty,
                ModerationEnabled = normalizedServiceType == AIServiceType.OpenAI,
                IsLocalModel = metadata.IsLocalModel,
            };

            if (normalizedServiceType is AIServiceType.FoundryLocal or AIServiceType.Onnx or AIServiceType.ML)
            {
                provider.ModelName = string.Empty;
            }

            _editingPasteAIProvider = null;
            PasteAIProviderDraft = provider;
        }

        private static AIServiceType NormalizeServiceType(string serviceType, out string persistedServiceType)
        {
            if (string.IsNullOrWhiteSpace(serviceType))
            {
                persistedServiceType = AIServiceType.OpenAI.ToConfigurationString();
                return AIServiceType.OpenAI;
            }

            var trimmed = serviceType.Trim();
            var serviceTypeKind = trimmed.ToAIServiceType();

            if (serviceTypeKind == AIServiceType.Unknown)
            {
                persistedServiceType = AIServiceType.OpenAI.ToConfigurationString();
                return AIServiceType.OpenAI;
            }

            persistedServiceType = trimmed;
            return serviceTypeKind;
        }

        public bool IsServiceTypeAllowedByGPO(AIServiceType serviceType)
        {
            var metadata = AIServiceTypeRegistry.GetMetadata(serviceType);

            // Check if this is an online service
            if (metadata.IsOnlineService)
            {
                // For online services, first check the global online AI models GPO
                if (_onlineAIModelsGpoRuleConfiguration == GpoRuleConfigured.Disabled)
                {
                    // If global online AI is disabled, all online services are blocked
                    return false;
                }

                // If global online AI is enabled or not configured, check individual endpoint GPO
                var individualGpoRule = serviceType switch
                {
                    AIServiceType.OpenAI => GPOWrapper.GetAllowedAdvancedPasteOpenAIValue(),
                    AIServiceType.AzureOpenAI => GPOWrapper.GetAllowedAdvancedPasteAzureOpenAIValue(),
                    AIServiceType.AzureAIInference => GPOWrapper.GetAllowedAdvancedPasteAzureAIInferenceValue(),
                    AIServiceType.Mistral => GPOWrapper.GetAllowedAdvancedPasteMistralValue(),
                    AIServiceType.Google => GPOWrapper.GetAllowedAdvancedPasteGoogleValue(),
                    _ => GpoRuleConfigured.Unavailable,
                };

                // If individual GPO is explicitly disabled, block it
                return individualGpoRule != GpoRuleConfigured.Disabled;
            }
            else
            {
                // For local models, only check their individual GPO (not affected by online AI GPO)
                var localGpoRule = serviceType switch
                {
                    AIServiceType.Ollama => GPOWrapper.GetAllowedAdvancedPasteOllamaValue(),
                    AIServiceType.FoundryLocal => GPOWrapper.GetAllowedAdvancedPasteFoundryLocalValue(),
                    _ => GpoRuleConfigured.Unavailable,
                };

                // If local model GPO is explicitly disabled, block it
                return localGpoRule != GpoRuleConfigured.Disabled;
            }
        }

        public void BeginEditPasteAIProvider(PasteAIProviderDefinition provider)
        {
            ArgumentNullException.ThrowIfNull(provider);

            _editingPasteAIProvider = provider;
            var draft = provider.Clone();
            var storedEndpoint = GetPasteAIEndpoint(draft.Id, draft.ServiceType);
            if (!string.IsNullOrWhiteSpace(storedEndpoint))
            {
                draft.EndpointUrl = storedEndpoint;
            }

            PasteAIProviderDraft = draft;
        }

        public void CancelPasteAIProviderDraft()
        {
            PasteAIProviderDraft = null;
            _editingPasteAIProvider = null;
        }

        public void CommitPasteAIProviderDraft(string apiKey, string endpoint)
        {
            if (PasteAIProviderDraft is null)
            {
                return;
            }

            var config = PasteAIConfiguration ?? new PasteAIConfiguration();
            if (_advancedPasteSettings.Properties.PasteAIConfiguration is null)
            {
                PasteAIConfiguration = config;
            }

            var draft = PasteAIProviderDraft;
            draft.EndpointUrl = endpoint?.Trim() ?? string.Empty;

            SavePasteAIApiKey(draft.Id, draft.ServiceType, apiKey);

            if (_editingPasteAIProvider is null)
            {
                config.Providers.Add(draft);
                config.ActiveProviderId ??= draft.Id;
            }
            else
            {
                UpdateProviderFromDraft(_editingPasteAIProvider, draft);
                _editingPasteAIProvider = null;
            }

            PasteAIProviderDraft = null;
            SaveAndNotifySettings();
            OnPropertyChanged(nameof(PasteAIConfiguration));
        }

        public void RemovePasteAIProvider(PasteAIProviderDefinition provider)
        {
            if (provider is null)
            {
                return;
            }

            var config = PasteAIConfiguration;
            if (config?.Providers is null)
            {
                return;
            }

            if (config.Providers.Remove(provider))
            {
                RemovePasteAICredentials(provider.Id, provider.ServiceType);
                SaveAndNotifySettings();
                OnPropertyChanged(nameof(PasteAIConfiguration));
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    UnsubscribeFromPasteAIConfiguration(_advancedPasteSettings?.Properties.PasteAIConfiguration);

                    foreach (var action in _additionalActions.GetAllActions())
                    {
                        action.PropertyChanged -= OnAdditionalActionPropertyChanged;
                    }

                    foreach (var customAction in _customActions)
                    {
                        customAction.PropertyChanged -= OnCustomActionPropertyChanged;
                    }

                    _customActions.CollectionChanged -= OnCustomActionsCollectionChanged;
                    _settingsWatcher?.Dispose();
                    _settingsWatcher = null;
                }

                _disposed = true;
            }

            base.Dispose(disposing);
        }

        internal void DisableAI()
        {
            try
            {
                bool stateChanged = false;

                if (_advancedPasteSettings.Properties.IsAIEnabled)
                {
                    _advancedPasteSettings.Properties.IsAIEnabled = false;
                    stateChanged = true;
                }

                if (stateChanged)
                {
                    SaveAndNotifySettings();
                }
                else
                {
                    NotifySettingsChanged();
                }

                OnPropertyChanged(nameof(IsAIEnabled));
            }
            catch (Exception)
            {
            }
        }

        internal void EnableAI()
        {
            try
            {
                if (IsOnlineAIModelsDisallowedByGPO)
                {
                    return;
                }

                bool stateChanged = false;

                if (!_advancedPasteSettings.Properties.IsAIEnabled)
                {
                    _advancedPasteSettings.Properties.IsAIEnabled = true;
                    stateChanged = true;
                }

                if (stateChanged)
                {
                    SaveAndNotifySettings();
                }
                else
                {
                    NotifySettingsChanged();
                }

                OnPropertyChanged(nameof(IsAIEnabled));
            }
            catch (Exception)
            {
            }
        }

        internal void SavePasteAIApiKey(string providerId, string serviceType, string apiKey)
        {
            try
            {
                apiKey = apiKey?.Trim() ?? string.Empty;
                serviceType = string.IsNullOrWhiteSpace(serviceType) ? "OpenAI" : serviceType;
                providerId ??= string.Empty;

                string credentialResource = GetAICredentialResource(serviceType);
                string credentialUserName = GetPasteAICredentialUserName(providerId, serviceType);
                string endpointCredentialUserName = GetPasteAIEndpointCredentialUserName(providerId, serviceType);
                PasswordVault vault = new();
                TryRemoveCredential(vault, credentialResource, credentialUserName);
                TryRemoveCredential(vault, credentialResource, endpointCredentialUserName);

                bool storeApiKey = RequiresCredentialStorage(serviceType) && !string.IsNullOrWhiteSpace(apiKey);
                if (storeApiKey)
                {
                    PasswordCredential cred = new(credentialResource, credentialUserName, apiKey);
                    vault.Add(cred);
                }

                OnPropertyChanged(nameof(IsAIEnabled));
                NotifySettingsChanged();
            }
            catch (Exception)
            {
            }
        }

        internal string GetPasteAIApiKey(string providerId, string serviceType)
        {
            serviceType = string.IsNullOrWhiteSpace(serviceType) ? "OpenAI" : serviceType;
            providerId ??= string.Empty;
            return RetrieveCredentialValue(
                GetAICredentialResource(serviceType),
                GetPasteAICredentialUserName(providerId, serviceType));
        }

        internal string GetPasteAIEndpoint(string providerId, string serviceType)
        {
            providerId ??= string.Empty;
            var providers = PasteAIConfiguration?.Providers;
            if (providers is null)
            {
                return string.Empty;
            }

            var provider = providers.FirstOrDefault(p => string.Equals(p.Id ?? string.Empty, providerId, StringComparison.OrdinalIgnoreCase));
            if (provider is null && !string.IsNullOrWhiteSpace(serviceType))
            {
                provider = providers.FirstOrDefault(p => string.Equals(p.ServiceType, serviceType, StringComparison.OrdinalIgnoreCase));
            }

            return provider?.EndpointUrl?.Trim() ?? string.Empty;
        }

        private string GetAICredentialResource(string serviceType)
        {
            serviceType = string.IsNullOrWhiteSpace(serviceType) ? "OpenAI" : serviceType;
            return serviceType.ToLowerInvariant() switch
            {
                "openai" => "https://platform.openai.com/api-keys",
                "azureopenai" => "https://azure.microsoft.com/products/ai-services/openai-service",
                "azureaiinference" => "https://azure.microsoft.com/products/ai-services/ai-inference",
                "mistral" => "https://console.mistral.ai/account/api-keys",
                "google" => "https://ai.google.dev/",
                "ollama" => "https://ollama.com/",
                _ => "https://platform.openai.com/api-keys",
            };
        }

        private string GetPasteAICredentialUserName(string providerId, string serviceType)
        {
            serviceType = string.IsNullOrWhiteSpace(serviceType) ? "OpenAI" : serviceType;
            providerId ??= string.Empty;

            string service = serviceType.ToLowerInvariant();
            string normalizedId = NormalizeProviderIdentifier(providerId);

            return $"PowerToys_AdvancedPaste_PasteAI_{service}_{normalizedId}";
        }

        private string GetPasteAIEndpointCredentialUserName(string providerId, string serviceType)
        {
            return GetPasteAICredentialUserName(providerId, serviceType) + "_Endpoint";
        }

        private static void UpdateProviderFromDraft(PasteAIProviderDefinition target, PasteAIProviderDefinition source)
        {
            if (target is null || source is null)
            {
                return;
            }

            target.ServiceType = source.ServiceType;
            target.ModelName = source.ModelName;
            target.EndpointUrl = source.EndpointUrl;
            target.ApiVersion = source.ApiVersion;
            target.DeploymentName = source.DeploymentName;
            target.ModelPath = source.ModelPath;
            target.SystemPrompt = source.SystemPrompt;
            target.ModerationEnabled = source.ModerationEnabled;
            target.EnableAdvancedAI = source.EnableAdvancedAI;
            target.IsLocalModel = source.IsLocalModel;
        }

        private void RemovePasteAICredentials(string providerId, string serviceType)
        {
            try
            {
                serviceType = string.IsNullOrWhiteSpace(serviceType) ? "OpenAI" : serviceType;
                providerId ??= string.Empty;

                string credentialResource = GetAICredentialResource(serviceType);
                PasswordVault vault = new();
                TryRemoveCredential(vault, credentialResource, GetPasteAICredentialUserName(providerId, serviceType));
                TryRemoveCredential(vault, credentialResource, GetPasteAIEndpointCredentialUserName(providerId, serviceType));
            }
            catch (Exception)
            {
            }
        }

        private static string NormalizeProviderIdentifier(string providerId)
        {
            if (string.IsNullOrWhiteSpace(providerId))
            {
                return "default";
            }

            var filtered = new string(providerId.Where(char.IsLetterOrDigit).ToArray());
            return string.IsNullOrWhiteSpace(filtered) ? "default" : filtered.ToLowerInvariant();
        }

        private static bool RequiresCredentialStorage(string serviceType)
        {
            var serviceTypeKind = serviceType.ToAIServiceType();

            return serviceTypeKind switch
            {
                AIServiceType.Onnx => false,
                AIServiceType.Ollama => false,
                AIServiceType.FoundryLocal => false,
                AIServiceType.ML => false,
                _ => true,
            };
        }

        private static void TryRemoveCredential(PasswordVault vault, string credentialResource, string credentialUserName)
        {
            try
            {
                PasswordCredential existingCred = vault.Retrieve(credentialResource, credentialUserName);
                vault.Remove(existingCred);
            }
            catch (Exception)
            {
                // Credential doesn't exist, which is fine
            }
        }

        internal AdvancedPasteCustomAction GetNewCustomAction(string namePrefix)
        {
            ArgumentException.ThrowIfNullOrEmpty(namePrefix);

            var maxUsedPrefix = _customActions.Select(customAction => customAction.Name)
                                  .Where(name => name.StartsWith(namePrefix, StringComparison.InvariantCulture))
                                  .Select(name => int.TryParse(name.AsSpan(namePrefix.Length), out int number) ? number : 0)
                                  .DefaultIfEmpty(0)
                                  .Max();

            var maxUsedId = _customActions.Select(customAction => customAction.Id)
                                          .DefaultIfEmpty(-1)
                                          .Max();
            return new()
            {
                Id = maxUsedId + 1,
                Name = $"{namePrefix} {maxUsedPrefix + 1}",
                IsShown = true,
            };
        }

        internal void AddCustomAction(AdvancedPasteCustomAction customAction)
        {
            if (_customActions.Any(existingCustomAction => existingCustomAction.Id == customAction.Id))
            {
                throw new ArgumentException("Duplicate custom action", nameof(customAction));
            }

            _customActions.Add(customAction);
        }

        internal void DeleteCustomAction(AdvancedPasteCustomAction customAction) => _customActions.Remove(customAction);

        private void SaveCustomActions() => SaveAndNotifySettings();

        private void SaveAndNotifySettings()
        {
            if (_suppressSave)
            {
                return;
            }

            _settingsUtils.SaveSettings(_advancedPasteSettings.ToJsonString(), AdvancedPasteSettings.ModuleName);
            NotifySettingsChanged();
        }

        private void OnAdditionalActionPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            SaveAndNotifySettings();

            if (e.PropertyName == nameof(AdvancedPasteAdditionalAction.Shortcut))
            {
                OnPropertyChanged(nameof(IsAdditionalActionConflictingCopyShortcut));
            }
        }

        private void OnCustomActionPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (!string.IsNullOrEmpty(e.PropertyName) && !CustomActionNonPersistedProperties.Contains(e.PropertyName))
            {
                SaveCustomActions();
            }

            if (e.PropertyName == nameof(AdvancedPasteCustomAction.Shortcut))
            {
                OnPropertyChanged(nameof(IsConflictingCopyShortcut));
            }
        }

        private void OnCustomActionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            void AddRange(System.Collections.IList items)
            {
                foreach (AdvancedPasteCustomAction item in items)
                {
                    item.PropertyChanged += OnCustomActionPropertyChanged;
                }
            }

            void RemoveRange(System.Collections.IList items)
            {
                foreach (AdvancedPasteCustomAction item in items)
                {
                    item.PropertyChanged -= OnCustomActionPropertyChanged;
                }
            }

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    AddRange(e.NewItems);
                    break;

                case NotifyCollectionChangedAction.Remove:
                    RemoveRange(e.OldItems);
                    break;

                case NotifyCollectionChangedAction.Replace:
                    AddRange(e.NewItems);
                    RemoveRange(e.OldItems);
                    break;

                case NotifyCollectionChangedAction.Move:
                    break;

                default:
                    throw new ArgumentException($"Unsupported {nameof(e.Action)} {e.Action}", nameof(e));
            }

            OnPropertyChanged(nameof(IsConflictingCopyShortcut));
            UpdateCustomActionsCanMoveUpDown();
            SaveCustomActions();
        }

        private void AttachConfigurationHandlers()
        {
            SubscribeToPasteAIConfiguration(_advancedPasteSettings.Properties.PasteAIConfiguration);
        }

        private void SetupSettingsFileWatcher()
        {
            _settingsWatcher = Helper.GetFileWatcher(AdvancedPasteSettings.ModuleName, SettingsUtils.DefaultFileName, OnSettingsFileChanged);
        }

        private void OnSettingsFileChanged()
        {
            if (_disposed)
            {
                return;
            }

            void Handler()
            {
                ApplyExternalSettings();
            }

            if (_dispatcherQueue is not null && !_dispatcherQueue.HasThreadAccess)
            {
                _dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Normal, Handler);
            }
            else
            {
                Handler();
            }
        }

        private void ApplyExternalSettings()
        {
            if (_disposed)
            {
                return;
            }

            AdvancedPasteSettings latestSettings;

            try
            {
                latestSettings = _settingsUtils.GetSettingsOrDefault<AdvancedPasteSettings>(AdvancedPasteSettings.ModuleName);
            }
            catch
            {
                return;
            }

            if (latestSettings?.Properties is null)
            {
                return;
            }

            try
            {
                _suppressSave = true;
                ApplyExternalProperties(latestSettings.Properties);
            }
            finally
            {
                _suppressSave = false;
            }
        }

        private void ApplyExternalProperties(AdvancedPasteProperties source)
        {
            var target = _advancedPasteSettings?.Properties;

            if (target is null || source is null)
            {
                return;
            }

            if (target.IsAIEnabled != source.IsAIEnabled)
            {
                target.IsAIEnabled = source.IsAIEnabled;
                OnPropertyChanged(nameof(IsAIEnabled));
            }

            if (target.ShowCustomPreview != source.ShowCustomPreview)
            {
                target.ShowCustomPreview = source.ShowCustomPreview;
                OnPropertyChanged(nameof(ShowCustomPreview));
            }

            if (target.CloseAfterLosingFocus != source.CloseAfterLosingFocus)
            {
                target.CloseAfterLosingFocus = source.CloseAfterLosingFocus;
                OnPropertyChanged(nameof(CloseAfterLosingFocus));
            }

            if (target.EnableClipboardPreview != source.EnableClipboardPreview)
            {
                target.EnableClipboardPreview = source.EnableClipboardPreview;
                OnPropertyChanged(nameof(EnableClipboardPreview));
            }

            if (target.AutoCopySelectionForCustomActionHotkey != source.AutoCopySelectionForCustomActionHotkey)
            {
                target.AutoCopySelectionForCustomActionHotkey = source.AutoCopySelectionForCustomActionHotkey;
                OnPropertyChanged(nameof(AutoCopySelectionForCustomActionHotkey));
            }

            var incomingConfig = source.PasteAIConfiguration ?? new PasteAIConfiguration();
            if (ShouldReplacePasteAIConfiguration(target.PasteAIConfiguration, incomingConfig))
            {
                PasteAIConfiguration = incomingConfig;
            }
        }

        private static bool ShouldReplacePasteAIConfiguration(PasteAIConfiguration current, PasteAIConfiguration incoming)
        {
            if (incoming is null)
            {
                return false;
            }

            if (current is null)
            {
                return true;
            }

            if (!string.Equals(current.ActiveProviderId ?? string.Empty, incoming.ActiveProviderId ?? string.Empty, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }

            var currentProviders = current.Providers ?? new ObservableCollection<PasteAIProviderDefinition>();
            var incomingProviders = incoming.Providers ?? new ObservableCollection<PasteAIProviderDefinition>();

            if (currentProviders.Count != incomingProviders.Count)
            {
                return true;
            }

            for (int i = 0; i < currentProviders.Count; i++)
            {
                var existing = currentProviders[i];
                var updated = incomingProviders[i];

                if (!string.Equals(existing?.Id ?? string.Empty, updated?.Id ?? string.Empty, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }

                if (!string.Equals(existing?.ServiceType ?? string.Empty, updated?.ServiceType ?? string.Empty, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }

                if (!string.Equals(existing?.ModelName ?? string.Empty, updated?.ModelName ?? string.Empty, StringComparison.Ordinal))
                {
                    return true;
                }

                if (!string.Equals(existing?.EndpointUrl ?? string.Empty, updated?.EndpointUrl ?? string.Empty, StringComparison.Ordinal))
                {
                    return true;
                }

                if (!string.Equals(existing?.DeploymentName ?? string.Empty, updated?.DeploymentName ?? string.Empty, StringComparison.Ordinal))
                {
                    return true;
                }

                if (!string.Equals(existing?.ApiVersion ?? string.Empty, updated?.ApiVersion ?? string.Empty, StringComparison.Ordinal))
                {
                    return true;
                }

                if (!string.Equals(existing?.SystemPrompt ?? string.Empty, updated?.SystemPrompt ?? string.Empty, StringComparison.Ordinal))
                {
                    return true;
                }

                if (existing?.ModerationEnabled != updated?.ModerationEnabled || existing?.EnableAdvancedAI != updated?.EnableAdvancedAI || existing?.IsActive != updated?.IsActive)
                {
                    return true;
                }
            }

            return false;
        }

        private void SubscribeToPasteAIConfiguration(PasteAIConfiguration configuration)
        {
            if (configuration is not null)
            {
                configuration.PropertyChanged += OnPasteAIConfigurationPropertyChanged;
                SubscribeToPasteAIProviders(configuration);
            }
        }

        private void UnsubscribeFromPasteAIConfiguration(PasteAIConfiguration configuration)
        {
            if (configuration is not null)
            {
                configuration.PropertyChanged -= OnPasteAIConfigurationPropertyChanged;
                UnsubscribeFromPasteAIProviders(configuration);
            }
        }

        private void SubscribeToPasteAIProviders(PasteAIConfiguration configuration)
        {
            if (configuration?.Providers is null)
            {
                return;
            }

            configuration.Providers.CollectionChanged -= OnPasteAIProvidersCollectionChanged;
            configuration.Providers.CollectionChanged += OnPasteAIProvidersCollectionChanged;

            foreach (var provider in configuration.Providers)
            {
                provider.PropertyChanged -= OnPasteAIProviderPropertyChanged;
                provider.PropertyChanged += OnPasteAIProviderPropertyChanged;
            }
        }

        private void UnsubscribeFromPasteAIProviders(PasteAIConfiguration configuration)
        {
            if (configuration?.Providers is null)
            {
                return;
            }

            configuration.Providers.CollectionChanged -= OnPasteAIProvidersCollectionChanged;

            foreach (var provider in configuration.Providers)
            {
                provider.PropertyChanged -= OnPasteAIProviderPropertyChanged;
            }
        }

        private void OnPasteAIProvidersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e?.NewItems is not null)
            {
                foreach (PasteAIProviderDefinition provider in e.NewItems)
                {
                    provider.PropertyChanged += OnPasteAIProviderPropertyChanged;
                }
            }

            if (e?.OldItems is not null)
            {
                foreach (PasteAIProviderDefinition provider in e.OldItems)
                {
                    provider.PropertyChanged -= OnPasteAIProviderPropertyChanged;
                }
            }

            var pasteConfig = _advancedPasteSettings?.Properties?.PasteAIConfiguration;

            OnPropertyChanged(nameof(PasteAIConfiguration));
            SaveAndNotifySettings();
        }

        private void OnPasteAIProviderPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (sender is PasteAIProviderDefinition provider)
            {
                // When service type changes we may need to update credentials entry names.
                if (string.Equals(e.PropertyName, nameof(PasteAIProviderDefinition.ServiceType), StringComparison.Ordinal))
                {
                    SaveAndNotifySettings();
                    return;
                }

                SaveAndNotifySettings();
            }
        }

        private void OnPasteAIConfigurationPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (string.Equals(e.PropertyName, nameof(PasteAIConfiguration.Providers), StringComparison.Ordinal))
            {
                SubscribeToPasteAIProviders(PasteAIConfiguration);
                SaveAndNotifySettings();
                return;
            }

            if (string.Equals(e.PropertyName, nameof(PasteAIConfiguration.ActiveProviderId), StringComparison.Ordinal))
            {
                SaveAndNotifySettings();
            }
        }

        private void InitializePasteAIProviderState()
        {
            var pasteConfig = _advancedPasteSettings?.Properties?.PasteAIConfiguration;
            if (pasteConfig is null)
            {
                _advancedPasteSettings.Properties.PasteAIConfiguration = new PasteAIConfiguration();
                pasteConfig = _advancedPasteSettings.Properties.PasteAIConfiguration;
            }

            pasteConfig.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();

            SubscribeToPasteAIProviders(pasteConfig);
        }

        private static string RetrieveCredentialValue(string credentialResource, string credentialUserName)
        {
            if (string.IsNullOrWhiteSpace(credentialResource) || string.IsNullOrWhiteSpace(credentialUserName))
            {
                return string.Empty;
            }

            try
            {
                PasswordVault vault = new();
                PasswordCredential existingCred = vault.Retrieve(credentialResource, credentialUserName);
                existingCred?.RetrievePassword();
                return existingCred?.Password?.Trim() ?? string.Empty;
            }
            catch (Exception)
            {
                return string.Empty;
            }
        }

        private void UpdateCustomActionsCanMoveUpDown()
        {
            for (int index = 0; index < _customActions.Count; index++)
            {
                var customAction = _customActions[index];
                customAction.CanMoveUp = index != 0;
                customAction.CanMoveDown = index != _customActions.Count - 1;
            }
        }
    }
}
