// 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.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json.Serialization;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;

namespace Microsoft.PowerToys.Settings.UI.Library
{
    public record HotkeySettings : ICmdLineRepresentable, INotifyPropertyChanged
    {
        private const int VKTAB = 0x09;
        private bool _hasConflict;
        private string _conflictDescription;
        private bool _isSystemConflict;
        private bool _ignoreConflict;

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public HotkeySettings()
        {
            Win = false;
            Ctrl = false;
            Alt = false;
            Shift = false;
            Code = 0;

            HasConflict = false;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="HotkeySettings"/> class.
        /// </summary>
        /// <param name="win">Should Windows key be used</param>
        /// <param name="ctrl">Should Ctrl key be used</param>
        /// <param name="alt">Should Alt key be used</param>
        /// <param name="shift">Should Shift key be used</param>
        /// <param name="code">Go to https://learn.microsoft.com/windows/win32/inputdev/virtual-key-codes to see list of v-keys</param>
        public HotkeySettings(bool win, bool ctrl, bool alt, bool shift, int code)
        {
            Win = win;
            Ctrl = ctrl;
            Alt = alt;
            Shift = shift;
            Code = code;
            HasConflict = false;
        }

        [JsonIgnore]
        public bool IgnoreConflict
        {
            get => _ignoreConflict;
            set
            {
                if (_ignoreConflict != value)
                {
                    _ignoreConflict = value;
                    OnPropertyChanged();
                }
            }
        }

        [JsonIgnore]
        public bool HasConflict
        {
            get => _hasConflict;
            set
            {
                if (_hasConflict != value)
                {
                    _hasConflict = value;
                    OnPropertyChanged();
                }
            }
        }

        [JsonIgnore]
        public string ConflictDescription
        {
            get => _ignoreConflict ? null : _conflictDescription;
            set
            {
                if (_conflictDescription != value)
                {
                    _conflictDescription = value;
                    OnPropertyChanged();
                }
            }
        }

        [JsonIgnore]
        public bool IsSystemConflict
        {
            get => _isSystemConflict;
            set
            {
                if (_isSystemConflict != value)
                {
                    _isSystemConflict = value;
                    OnPropertyChanged();
                }
            }
        }

        public virtual void UpdateConflictStatus()
        {
            Logger.LogInfo($"{this.ToString()}");
        }

        [JsonPropertyName("win")]
        public bool Win { get; set; }

        [JsonPropertyName("ctrl")]
        public bool Ctrl { get; set; }

        [JsonPropertyName("alt")]
        public bool Alt { get; set; }

        [JsonPropertyName("shift")]
        public bool Shift { get; set; }

        [JsonPropertyName("code")]
        public int Code { get; set; }

        // This is currently needed for FancyZones, we need to unify these two objects
        // see src\common\settings_objects.h
        [JsonPropertyName("key")]
        public string Key { get; set; } = string.Empty;

        public override string ToString()
        {
            StringBuilder output = new StringBuilder();

            if (Win)
            {
                output.Append("Win + ");
            }

            if (Ctrl)
            {
                output.Append("Ctrl + ");
            }

            if (Alt)
            {
                output.Append("Alt + ");
            }

            if (Shift)
            {
                output.Append("Shift + ");
            }

            if (Code > 0)
            {
                var localKey = Helper.GetKeyName((uint)Code);
                output.Append(localKey);
            }
            else if (output.Length >= 2)
            {
                output.Remove(output.Length - 2, 2);
            }

            return output.ToString();
        }

        public List<object> GetKeysList()
        {
            List<object> shortcutList = new List<object>();

            if (Win)
            {
                shortcutList.Add(92); // The Windows key or button.
            }

            if (Ctrl)
            {
                shortcutList.Add("Ctrl");
            }

            if (Alt)
            {
                shortcutList.Add("Alt");
            }

            if (Shift)
            {
                shortcutList.Add(16); // The Shift key or button.
            }

            if (Code > 0)
            {
                switch (Code)
                {
                    // https://learn.microsoft.com/uwp/api/windows.system.virtualkey?view=winrt-20348
                    case 38: // The Up Arrow key or button.
                    case 40: // The Down Arrow key or button.
                    case 37: // The Left Arrow key or button.
                    case 39: // The Right Arrow key or button.
                             // case 8: // The Back key or button.
                             // case 13: // The Enter key or button.
                        shortcutList.Add(Code);
                        break;
                    default:
                        var localKey = Helper.GetKeyName((uint)Code);
                        shortcutList.Add(localKey);
                        break;
                }
            }

            return shortcutList;
        }

        public bool IsValid()
        {
            if (IsAccessibleShortcut())
            {
                return false;
            }

            return (Alt || Ctrl || Win || Shift) && Code != 0;
        }

        public bool IsEmpty()
        {
            return !Alt && !Ctrl && !Win && !Shift && Code == 0;
        }

        public bool IsAccessibleShortcut()
        {
            // Shift+Tab and Tab are accessible shortcuts
            if ((!Alt && !Ctrl && !Win && Shift && Code == VKTAB)
                || (!Alt && !Ctrl && !Win && !Shift && Code == VKTAB))
            {
                return true;
            }

            return false;
        }

        public static bool TryParseFromCmd(string cmd, out object result)
        {
            bool win = false, ctrl = false, alt = false, shift = false;
            int code = 0;

            var parts = cmd.Split('+');
            foreach (var part in parts)
            {
                switch (part.Trim().ToLower(CultureInfo.InvariantCulture))
                {
                    case "win":
                        win = true;
                        break;
                    case "ctrl":
                        ctrl = true;
                        break;
                    case "alt":
                        alt = true;
                        break;
                    case "shift":
                        shift = true;
                        break;
                    default:
                        if (!TryParseKeyCode(part, out code))
                        {
                            result = null;
                            return false;
                        }

                        break;
                }
            }

            result = new HotkeySettings(win, ctrl, alt, shift, code);
            return true;
        }

        private static bool TryParseKeyCode(string key, out int keyCode)
        {
            // ASCII symbol
            if (key.Length == 1 && char.IsLetterOrDigit(key[0]))
            {
                keyCode = char.ToUpper(key[0], CultureInfo.InvariantCulture);
                return true;
            }

            // VK code
            else if (key.Length == 4 && key.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
            {
                return int.TryParse(key.AsSpan(2), NumberStyles.HexNumber, null, out keyCode);
            }

            // Alias
            else
            {
                keyCode = (int)Utilities.Helper.GetKeyValue(key);
                return keyCode != 0;
            }
        }

        public bool TryToCmdRepresentable(out string result)
        {
            result = ToString();
            result = result.Replace(" ", null);
            return true;
        }
    }
}
