// 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.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Properties;
using Microsoft.CmdPal.UI.ViewModels.Services;

namespace Microsoft.CmdPal.UI.ViewModels;

public partial class ProviderSettingsViewModel : ObservableObject
{
    private static readonly IconInfoViewModel EmptyIcon = new(null);
    private static readonly CompositeFormat ExtensionSubtextFormat = CompositeFormat.Parse(Resources.builtin_extension_subtext);
    private static readonly CompositeFormat ExtensionSubtextWithFallbackFormat = CompositeFormat.Parse(Resources.builtin_extension_subtext_with_fallback);
    private static readonly CompositeFormat ExtensionSubtextDisabledFormat = CompositeFormat.Parse(Resources.builtin_extension_subtext_disabled);

    private readonly CommandProviderWrapper _provider;
    private readonly ISettingsService _settingsService;
    private readonly Lock _initializeSettingsLock = new();

    private ProviderSettings _providerSettings;

    private Task? _initializeSettingsTask;

    public ProviderSettingsViewModel(
        CommandProviderWrapper provider,
        ProviderSettings providerSettings,
        ISettingsService settingsService)
    {
        _provider = provider;
        _providerSettings = providerSettings;
        _settingsService = settingsService;

        LoadingSettings = _provider.Settings?.HasSettings ?? false;

        BuildFallbackViewModels();
    }

    public string DisplayName => _provider.DisplayName;

    public string ExtensionName => _provider.Extension?.ExtensionDisplayName ?? Resources.builtin_extension_name;

    public string ExtensionSubtext => IsEnabled ?
        HasFallbackCommands ?
            string.Format(CultureInfo.CurrentCulture, ExtensionSubtextWithFallbackFormat, ExtensionName, TopLevelCommands.Count, _provider.FallbackItems?.Length ?? 0) :
            string.Format(CultureInfo.CurrentCulture, ExtensionSubtextFormat, ExtensionName, TopLevelCommands.Count) :
        string.Format(CultureInfo.CurrentCulture, ExtensionSubtextDisabledFormat, ExtensionName, Resources.builtin_disabled_extension);

    [MemberNotNullWhen(true, nameof(Extension))]
    public bool IsFromExtension => _provider.Extension is not null;

    public IExtensionWrapper? Extension => _provider.Extension;

    public string ExtensionVersion => IsFromExtension ? $"{Extension.Version.Major}.{Extension.Version.Minor}.{Extension.Version.Build}.{Extension.Version.Revision}" : string.Empty;

    public IconInfoViewModel Icon => IsEnabled ? _provider.Icon : EmptyIcon;

    [ObservableProperty]
    public partial bool LoadingSettings { get; set; }

    public bool IsEnabled
    {
        get => _providerSettings.IsEnabled;
        set
        {
            if (value != _providerSettings.IsEnabled)
            {
                var newSettings = _providerSettings with { IsEnabled = value };
                _settingsService.UpdateSettings(s => s with
                {

                    ProviderSettings = s.ProviderSettings.SetItem(_provider.ProviderId, newSettings),
                });
                _providerSettings = newSettings;
                WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new());
                OnPropertyChanged(nameof(IsEnabled));
                OnPropertyChanged(nameof(ExtensionSubtext));
                OnPropertyChanged(nameof(Icon));
            }

            if (value == true)
            {
                _provider.CommandsChanged -= Provider_CommandsChanged;
                _provider.CommandsChanged += Provider_CommandsChanged;
            }
        }
    }

    /// <summary>
    /// Gets a value indicating whether returns true if we have a settings page
    /// that's initialized, or we are still working on initializing that
    /// settings page. If we don't have a settings object, or that settings
    /// object doesn't have a settings page, then we'll return false.
    /// </summary>
    public bool HasSettings
    {
        get
        {
            if (_provider.Settings is null)
            {
                return false;
            }

            if (_provider.Settings.Initialized)
            {
                return _provider.Settings.HasSettings;
            }

            // settings still need to be loaded.
            return LoadingSettings;
        }
    }

    /// <summary>
    /// Gets will return the settings page, if we have one, and have initialized it.
    /// If we haven't initialized it, this will kick off a thread to start
    /// initializing it.
    /// </summary>
    public ContentPageViewModel? SettingsPage
    {
        get
        {
            if (_provider.Settings is null)
            {
                return null;
            }

            if (_provider.Settings.Initialized)
            {
                LoadingSettings = false;
                return _provider.Settings.SettingsPage;
            }

            // Don't load the settings if we're already working on it
            lock (_initializeSettingsLock)
            {
                _initializeSettingsTask ??= Task.Run(InitializeSettingsPage);
            }

            return null;
        }
    }

    [field: AllowNull]
    public List<TopLevelViewModel> TopLevelCommands
    {
        get
        {
            if (field is null)
            {
                field = BuildTopLevelViewModels();
            }

            return field;
        }
    }

    private List<TopLevelViewModel> BuildTopLevelViewModels()
    {
        var thisProvider = _provider;
        var providersCommands = thisProvider.TopLevelItems;

        // Remember! This comes in on the UI thread!
        return [.. providersCommands];
    }

    [field: AllowNull]
    public List<FallbackSettingsViewModel> FallbackCommands { get; set; } = [];

    public bool HasFallbackCommands => _provider.FallbackItems?.Length > 0;

    private void BuildFallbackViewModels()
    {
        var thisProvider = _provider;
        var providersFallbackCommands = thisProvider.FallbackItems;

        List<FallbackSettingsViewModel> fallbackViewModels = new(providersFallbackCommands.Length);
        foreach (var fallbackItem in providersFallbackCommands)
        {
            if (_providerSettings.FallbackCommands.TryGetValue(fallbackItem.Id, out var fallbackSettings))
            {
                fallbackViewModels.Add(new FallbackSettingsViewModel(fallbackItem, fallbackSettings, this, _settingsService));
            }
            else
            {
                fallbackViewModels.Add(new FallbackSettingsViewModel(fallbackItem, new(), this, _settingsService));
            }
        }

        FallbackCommands = fallbackViewModels;
    }

    internal void UpdateFallbackSettings(string id, FallbackSettings settings)
    {
        var newProviderSettings = _providerSettings with
        {
            FallbackCommands = _providerSettings.FallbackCommands.SetItem(id, settings),
        };
        _providerSettings = newProviderSettings;
        _settingsService.UpdateSettings(
            s => s with
            {
                ProviderSettings = s.ProviderSettings.SetItem(_provider.ProviderId, newProviderSettings),
            },
            hotReload: false);
    }

    private void InitializeSettingsPage()
    {
        if (_provider.Settings is null)
        {
            return;
        }

        _provider.Settings.SafeInitializeProperties();
        _provider.Settings.DoOnUiThread(() =>
        {
            // Changing these properties will try to update XAML, and that has
            // to be handled on the UI thread, so we need to raise them on the
            // UI thread
            LoadingSettings = false;
            OnPropertyChanged(nameof(HasSettings));
            OnPropertyChanged(nameof(LoadingSettings));
            OnPropertyChanged(nameof(SettingsPage));
        });
    }

    private void Provider_CommandsChanged(CommandProviderWrapper sender, CommandPalette.Extensions.IItemsChangedEventArgs args)
    {
        OnPropertyChanged(nameof(ExtensionSubtext));
        OnPropertyChanged(nameof(TopLevelCommands));
    }
}
