// 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.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.WinUI;
using Microsoft.CmdPal.UI.ViewModels.Services;
using Microsoft.UI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
using Windows.UI.ViewManagement;

namespace Microsoft.CmdPal.UI.ViewModels;

public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDisposable
{
    internal static readonly Color DefaultTintColor = Color.FromArgb(255, 0, 120, 212);

    internal static readonly ObservableCollection<Color> WindowsColorSwatches = [

        // row 0
        Color.FromArgb(255, 255, 185, 0), // #ffb900
        Color.FromArgb(255, 255, 140, 0), // #ff8c00
        Color.FromArgb(255, 247, 99, 12), // #f7630c
        Color.FromArgb(255, 202, 80, 16), // #ca5010
        Color.FromArgb(255, 218, 59, 1), // #da3b01
        Color.FromArgb(255, 239, 105, 80), // #ef6950

        // row 1
        Color.FromArgb(255, 209, 52, 56), // #d13438
        Color.FromArgb(255, 255, 67, 67), // #ff4343
        Color.FromArgb(255, 231, 72, 86), // #e74856
        Color.FromArgb(255, 232, 17, 35), // #e81123
        Color.FromArgb(255, 234, 0, 94), // #ea005e
        Color.FromArgb(255, 195, 0, 82), // #c30052

        // row 2
        Color.FromArgb(255, 227, 0, 140), // #e3008c
        Color.FromArgb(255, 191, 0, 119), // #bf0077
        Color.FromArgb(255, 194, 57, 179), // #c239b3
        Color.FromArgb(255, 154, 0, 137), // #9a0089
        Color.FromArgb(255, 0, 120, 212), // #0078d4
        Color.FromArgb(255, 0, 99, 177), // #0063b1

        // row 3
        Color.FromArgb(255, 142, 140, 216), // #8e8cd8
        Color.FromArgb(255, 107, 105, 214), // #6b69d6
        Color.FromArgb(255, 135, 100, 184), // #8764b8
        Color.FromArgb(255, 116, 77, 169), // #744da9
        Color.FromArgb(255, 177, 70, 194), // #b146c2
        Color.FromArgb(255, 136, 23, 152), // #881798

        // row 4
        Color.FromArgb(255, 0, 153, 188), // #0099bc
        Color.FromArgb(255, 45, 125, 154), // #2d7d9a
        Color.FromArgb(255, 0, 183, 195), // #00b7c3
        Color.FromArgb(255, 3, 131, 135), // #038387
        Color.FromArgb(255, 0, 178, 148), // #00b294
        Color.FromArgb(255, 1, 133, 116), // #018574

        // row 5
        Color.FromArgb(255, 0, 204, 106), // #00cc6a
        Color.FromArgb(255, 16, 137, 62), // #10893e
        Color.FromArgb(255, 122, 117, 116), // #7a7574
        Color.FromArgb(255, 93, 90, 88), // #5d5a58
        Color.FromArgb(255, 104, 118, 138), // #68768a
        Color.FromArgb(255, 81, 92, 107), // #515c6b

        // row 6
        Color.FromArgb(255, 86, 124, 115), // #567c73
        Color.FromArgb(255, 72, 104, 96), // #486860
        Color.FromArgb(255, 73, 130, 5), // #498205
        Color.FromArgb(255, 16, 124, 16), // #107c10
        Color.FromArgb(255, 118, 118, 118), // #767676
        Color.FromArgb(255, 76, 74, 72), // #4c4a48

        // row 7
        Color.FromArgb(255, 105, 121, 126), // #69797e
        Color.FromArgb(255, 74, 84, 89), // #4a5459
        Color.FromArgb(255, 100, 124, 100), // #647c64
        Color.FromArgb(255, 82, 94, 84), // #525e54
        Color.FromArgb(255, 132, 117, 69), // #847545
        Color.FromArgb(255, 126, 115, 95), // #7e735f
    ];

    private readonly ISettingsService _settingsService;

    private readonly UISettings _uiSettings;
    private readonly IThemeService _themeService;
    private readonly DispatcherQueueTimer _saveTimer = DispatcherQueue.GetForCurrentThread().CreateTimer();
    private readonly DispatcherQueue _uiDispatcher = DispatcherQueue.GetForCurrentThread();

    private ElementTheme? _elementThemeOverride;
    private Color _currentSystemAccentColor;

    public ObservableCollection<Color> Swatches => WindowsColorSwatches;

    public int ThemeIndex
    {
        get => (int)_settingsService.Settings.Theme;
        set => Theme = (UserTheme)value;
    }

    public UserTheme Theme
    {
        get => _settingsService.Settings.Theme;
        set
        {
            if (_settingsService.Settings.Theme != value)
            {
                _settingsService.UpdateSettings(s => s with { Theme = value });
                OnPropertyChanged();
                OnPropertyChanged(nameof(ThemeIndex));
                DebouncedReapply();
            }
        }
    }

    public ColorizationMode ColorizationMode
    {
        get => _settingsService.Settings.ColorizationMode;
        set
        {
            if (_settingsService.Settings.ColorizationMode != value)
            {
                _settingsService.UpdateSettings(s => s with { ColorizationMode = value });
                OnPropertyChanged();
                OnPropertyChanged(nameof(ColorizationModeIndex));
                OnPropertyChanged(nameof(IsCustomTintVisible));
                OnPropertyChanged(nameof(IsColorIntensityVisible));
                OnPropertyChanged(nameof(IsImageTintIntensityVisible));
                OnPropertyChanged(nameof(EffectiveTintIntensity));
                OnPropertyChanged(nameof(IsBackgroundControlsVisible));
                OnPropertyChanged(nameof(IsNoBackgroundVisible));
                OnPropertyChanged(nameof(IsAccentColorControlsVisible));
                OnPropertyChanged(nameof(IsResetButtonVisible));

                if (value == ColorizationMode.WindowsAccentColor)
                {
                    ThemeColor = _currentSystemAccentColor;
                }

                IsColorizationDetailsExpanded = value != ColorizationMode.None;

                DebouncedReapply();
            }
        }
    }

    public int ColorizationModeIndex
    {
        get => (int)_settingsService.Settings.ColorizationMode;
        set => ColorizationMode = (ColorizationMode)value;
    }

    public Color ThemeColor
    {
        get => _settingsService.Settings.CustomThemeColor;
        set
        {
            if (_settingsService.Settings.CustomThemeColor != value)
            {
                _settingsService.UpdateSettings(s => s with { CustomThemeColor = value });

                OnPropertyChanged();

                if (ColorIntensity == 0)
                {
                    ColorIntensity = 100;
                }

                DebouncedReapply();
            }
        }
    }

    public int ColorIntensity
    {
        get => _settingsService.Settings.CustomThemeColorIntensity;
        set
        {
            _settingsService.UpdateSettings(s => s with { CustomThemeColorIntensity = value });
            OnPropertyChanged();
            OnPropertyChanged(nameof(EffectiveTintIntensity));
            DebouncedReapply();
        }
    }

    public int BackgroundImageTintIntensity
    {
        get => _settingsService.Settings.BackgroundImageTintIntensity;
        set
        {
            _settingsService.UpdateSettings(s => s with { BackgroundImageTintIntensity = value });
            OnPropertyChanged();
            OnPropertyChanged(nameof(EffectiveTintIntensity));
            DebouncedReapply();
        }
    }

    public string BackgroundImagePath
    {
        get => _settingsService.Settings.BackgroundImagePath ?? string.Empty;
        set
        {
            if (_settingsService.Settings.BackgroundImagePath != value)
            {
                _settingsService.UpdateSettings(s => s with { BackgroundImagePath = value });
                OnPropertyChanged();

                if (BackgroundImageOpacity == 0)
                {
                    BackgroundImageOpacity = 100;
                }

                DebouncedReapply();
            }
        }
    }

    public int BackgroundImageOpacity
    {
        get => _settingsService.Settings.BackgroundImageOpacity;
        set
        {
            if (_settingsService.Settings.BackgroundImageOpacity != value)
            {
                _settingsService.UpdateSettings(s => s with { BackgroundImageOpacity = value });
                OnPropertyChanged();
                DebouncedReapply();
            }
        }
    }

    public int BackgroundImageBrightness
    {
        get => _settingsService.Settings.BackgroundImageBrightness;
        set
        {
            if (_settingsService.Settings.BackgroundImageBrightness != value)
            {
                _settingsService.UpdateSettings(s => s with { BackgroundImageBrightness = value });
                OnPropertyChanged();
                DebouncedReapply();
            }
        }
    }

    public int BackgroundImageBlurAmount
    {
        get => _settingsService.Settings.BackgroundImageBlurAmount;
        set
        {
            if (_settingsService.Settings.BackgroundImageBlurAmount != value)
            {
                _settingsService.UpdateSettings(s => s with { BackgroundImageBlurAmount = value });
                OnPropertyChanged();
                DebouncedReapply();
            }
        }
    }

    public BackgroundImageFit BackgroundImageFit
    {
        get => _settingsService.Settings.BackgroundImageFit;
        set
        {
            if (_settingsService.Settings.BackgroundImageFit != value)
            {
                _settingsService.UpdateSettings(s => s with { BackgroundImageFit = value });
                OnPropertyChanged();
                OnPropertyChanged(nameof(BackgroundImageFitIndex));
                DebouncedReapply();
            }
        }
    }

    public int BackgroundImageFitIndex
    {
        // Naming between UI facing string and enum is a bit confusing, but the enum fields
        // are based on XAML Stretch enum values. So I'm choosing to keep the confusion here, close
        // to the UI.
        // - BackgroundImageFit.Fill corresponds to "Stretch"
        // - BackgroundImageFit.UniformToFill corresponds to "Fill"
        get => BackgroundImageFit switch
        {
            BackgroundImageFit.Fill => 1,
            _ => 0,
        };
        set => BackgroundImageFit = value switch
        {
            1 => BackgroundImageFit.Fill,
            _ => BackgroundImageFit.UniformToFill,
        };
    }

    public int BackdropOpacity
    {
        get => _settingsService.Settings.BackdropOpacity;
        set
        {
            if (_settingsService.Settings.BackdropOpacity != value)
            {
                _settingsService.UpdateSettings(s => s with { BackdropOpacity = value });
                OnPropertyChanged();
                OnPropertyChanged(nameof(EffectiveBackdropStyle));
                OnPropertyChanged(nameof(EffectiveImageOpacity));
                DebouncedReapply();
            }
        }
    }

    public int BackdropStyleIndex
    {
        get => (int)_settingsService.Settings.BackdropStyle;
        set
        {
            var newStyle = (BackdropStyle)value;
            if (_settingsService.Settings.BackdropStyle != newStyle)
            {
                _settingsService.UpdateSettings(s => s with { BackdropStyle = newStyle });

                OnPropertyChanged();
                OnPropertyChanged(nameof(IsBackdropOpacityVisible));
                OnPropertyChanged(nameof(IsMicaBackdropDescriptionVisible));
                OnPropertyChanged(nameof(IsBackgroundSettingsEnabled));
                OnPropertyChanged(nameof(IsBackgroundNotAvailableVisible));

                if (!IsBackgroundSettingsEnabled)
                {
                    IsColorizationDetailsExpanded = false;
                }

                DebouncedReapply();
            }
        }
    }

    /// <summary>
    /// Gets a value indicating whether the backdrop opacity slider should be visible.
    /// </summary>
    public bool IsBackdropOpacityVisible =>
        BackdropStyles.Get(_settingsService.Settings.BackdropStyle).SupportsOpacity;

    /// <summary>
    /// Gets a value indicating whether the backdrop description (for styles without options) should be visible.
    /// </summary>
    public bool IsMicaBackdropDescriptionVisible =>
        !BackdropStyles.Get(_settingsService.Settings.BackdropStyle).SupportsOpacity;

    /// <summary>
    /// Gets a value indicating whether background/colorization settings are available.
    /// </summary>
    public bool IsBackgroundSettingsEnabled =>
        BackdropStyles.Get(_settingsService.Settings.BackdropStyle).SupportsColorization;

    /// <summary>
    /// Gets a value indicating whether the "not available" message should be shown (inverse of IsBackgroundSettingsEnabled).
    /// </summary>
    public bool IsBackgroundNotAvailableVisible =>
        !BackdropStyles.Get(_settingsService.Settings.BackdropStyle).SupportsColorization;

    public BackdropStyle? EffectiveBackdropStyle
    {
        get
        {
            // Return style when transparency/blur is visible (not fully opaque Acrylic)
            // - Clear/Mica/MicaAlt/AcrylicThin always show their effect
            // - Acrylic shows effect only when opacity < 100
            if (_settingsService.Settings.BackdropStyle != BackdropStyle.Acrylic || _settingsService.Settings.BackdropOpacity < 100)
            {
                return _settingsService.Settings.BackdropStyle;
            }

            return null;
        }
    }

    public double EffectiveImageOpacity =>
        EffectiveBackdropStyle is not null
            ? (BackgroundImageOpacity / 100f) * Math.Sqrt(_settingsService.Settings.BackdropOpacity / 100.0)
            : (BackgroundImageOpacity / 100f);

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

    public bool IsCustomTintVisible => _settingsService.Settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.Image;

    public bool IsColorIntensityVisible => _settingsService.Settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor;

    public bool IsImageTintIntensityVisible => _settingsService.Settings.ColorizationMode is ColorizationMode.Image;

    /// <summary>
    /// Gets the effective tint intensity for the preview, based on the current colorization mode.
    /// </summary>
    public int EffectiveTintIntensity => _settingsService.Settings.ColorizationMode is ColorizationMode.Image
        ? _settingsService.Settings.BackgroundImageTintIntensity
        : _settingsService.Settings.CustomThemeColorIntensity;

    public bool IsBackgroundControlsVisible => _settingsService.Settings.ColorizationMode is ColorizationMode.Image;

    public bool IsNoBackgroundVisible => _settingsService.Settings.ColorizationMode is ColorizationMode.None;

    public bool IsAccentColorControlsVisible => _settingsService.Settings.ColorizationMode is ColorizationMode.WindowsAccentColor;

    public bool IsResetButtonVisible => _settingsService.Settings.ColorizationMode is ColorizationMode.Image;

    public BackdropParameters EffectiveBackdrop { get; private set; } = new(Colors.Black, Colors.Black, 0.5f, 0.5f);

    public ElementTheme EffectiveTheme => _elementThemeOverride ?? _themeService.Current.Theme;

    public Color EffectiveThemeColor =>
        !BackdropStyles.Get(_settingsService.Settings.BackdropStyle).SupportsColorization
            ? Colors.Transparent
            : ColorizationMode switch
            {
                ColorizationMode.WindowsAccentColor => _currentSystemAccentColor,
                ColorizationMode.CustomColor or ColorizationMode.Image => ThemeColor,
                _ => Colors.Transparent,
            };

    // Since the blur amount is absolute, we need to scale it down for the preview (which is smaller than full screen).
    public int EffectiveBackgroundImageBlurAmount => (int)Math.Round(BackgroundImageBlurAmount / 4f);

    public double EffectiveBackgroundImageBrightness => BackgroundImageBrightness / 100.0;

    public ImageSource? EffectiveBackgroundImageSource =>
        !BackdropStyles.Get(_settingsService.Settings.BackdropStyle).SupportsBackgroundImage
            ? null
            : ColorizationMode is ColorizationMode.Image
              && !string.IsNullOrWhiteSpace(BackgroundImagePath)
              && Uri.TryCreate(BackgroundImagePath, UriKind.RelativeOrAbsolute, out var uri)
                ? new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(uri)
                : null;

    public AppearanceSettingsViewModel(IThemeService themeService, ISettingsService settingsService)
    {
        _themeService = themeService;
        _themeService.ThemeChanged += ThemeServiceOnThemeChanged;
        _settingsService = settingsService;

        _uiSettings = new UISettings();
        _uiSettings.ColorValuesChanged += UiSettingsOnColorValuesChanged;
        UpdateAccentColor(_uiSettings);

        Reapply();

        IsColorizationDetailsExpanded = _settingsService.Settings.ColorizationMode != ColorizationMode.None && IsBackgroundSettingsEnabled;
    }

    private void UiSettingsOnColorValuesChanged(UISettings sender, object args) => _uiDispatcher.TryEnqueue(() => UpdateAccentColor(sender));

    private void UpdateAccentColor(UISettings sender)
    {
        _currentSystemAccentColor = sender.GetColorValue(UIColorType.Accent);
        if (ColorizationMode == ColorizationMode.WindowsAccentColor)
        {
            ThemeColor = _currentSystemAccentColor;
        }
    }

    private void ThemeServiceOnThemeChanged(object? sender, ThemeChangedEventArgs e)
    {
        _saveTimer.Debounce(Reapply, TimeSpan.FromMilliseconds(200));
    }

    private void DebouncedReapply()
    {
        _saveTimer.Debounce(Reapply, TimeSpan.FromMilliseconds(200));
    }

    private void Reapply()
    {
        // Theme services recalculates effective color and opacity based on current settings.
        EffectiveBackdrop = _themeService.Current.BackdropParameters;
        OnPropertyChanged(nameof(EffectiveBackdrop));
        OnPropertyChanged(nameof(EffectiveBackdropStyle));
        OnPropertyChanged(nameof(EffectiveImageOpacity));
        OnPropertyChanged(nameof(EffectiveBackgroundImageBrightness));
        OnPropertyChanged(nameof(EffectiveBackgroundImageSource));
        OnPropertyChanged(nameof(EffectiveThemeColor));
        OnPropertyChanged(nameof(EffectiveBackgroundImageBlurAmount));

        // LOAD BEARING:
        // We need to cycle through the EffectiveTheme property to force reload of resources.
        _elementThemeOverride = ElementTheme.Light;
        OnPropertyChanged(nameof(EffectiveTheme));
        _elementThemeOverride = ElementTheme.Dark;
        OnPropertyChanged(nameof(EffectiveTheme));
        _elementThemeOverride = null;
        OnPropertyChanged(nameof(EffectiveTheme));
    }

    [RelayCommand]
    private void ResetBackgroundImageProperties()
    {
        BackgroundImageBrightness = 0;
        BackgroundImageBlurAmount = 0;
        BackgroundImageFit = BackgroundImageFit.UniformToFill;
        BackgroundImageOpacity = 100;
        BackgroundImageTintIntensity = 0;
    }

    [RelayCommand]
    private void ResetAppearanceSettings()
    {
        // Reset theme
        Theme = UserTheme.Default;

        // Reset backdrop settings
        BackdropStyleIndex = (int)BackdropStyle.Acrylic;
        BackdropOpacity = 100;

        // Reset background image settings
        BackgroundImagePath = string.Empty;
        ResetBackgroundImageProperties();

        // Reset colorization
        ColorizationMode = ColorizationMode.None;
        ThemeColor = DefaultTintColor;
        ColorIntensity = 100;
        BackgroundImageTintIntensity = 0;
    }

    public void Dispose()
    {
        _uiSettings.ColorValuesChanged -= UiSettingsOnColorValuesChanged;
        _themeService.ThemeChanged -= ThemeServiceOnThemeChanged;
    }
}
