using System;
using System.Runtime.InteropServices;

namespace Hua.Todo.Avalonia.Services.Platforms;

/// <summary>
/// Windows 全局键盘事件处理器。
/// 用于监听按键并向上层暴露事件（例如 Esc），以保证在 WebView 获得焦点时依旧可触发窗口级交互。
/// </summary>
public sealed class WindowsKeyboardHandler : IDisposable
{
    private readonly KeyboardHook _keyboardHook;
    private bool _isDisposed;

    /// <summary>
    /// 当检测到 Esc 键抬起时触发。
    /// </summary>
    public event EventHandler? EscKeyPressed;

    /// <summary>
    /// 创建 <see cref="WindowsKeyboardHandler"/>。
    /// </summary>
    public WindowsKeyboardHandler()
    {
        _keyboardHook = new KeyboardHook();
        _keyboardHook.KeyPressed += OnKeyPressed;
    }

    /// <summary>
    /// 开始监听键盘事件。
    /// </summary>
    public void Start()
    {
        _keyboardHook.Hook();
    }

    private void OnKeyPressed(object? sender, KeyPressedEventArgs e)
    {
        if (e.Key == 0x1B && !e.IsKeyDown)
        {
            EscKeyPressed?.Invoke(this, EventArgs.Empty);
        }
    }

    /// <inheritdoc />
    public void Dispose()
    {
        if (_isDisposed) return;
        _isDisposed = true;

        _keyboardHook.Unhook();
        _keyboardHook.KeyPressed -= OnKeyPressed;
    }

    private sealed class KeyboardHook : IDisposable
    {
        private const int WhKeyboardLl = 13;
        private const int WmKeyDown = 0x0100;
        private const int WmKeyUp = 0x0101;
        private const int WmSysKeyDown = 0x0104;
        private const int WmSysKeyUp = 0x0105;

        private readonly LowLevelKeyboardProc _proc;
        private IntPtr _hookId = IntPtr.Zero;
        private bool _isDisposed;

        public event EventHandler<KeyPressedEventArgs>? KeyPressed;

        public KeyboardHook()
        {
            _proc = HookCallback;
        }

        public void Hook()
        {
            if (_hookId != IntPtr.Zero) return;
            _hookId = SetWindowsHookEx(WhKeyboardLl, _proc, GetModuleHandle(null), 0);
        }

        public void Unhook()
        {
            if (_hookId == IntPtr.Zero) return;
            UnhookWindowsHookEx(_hookId);
            _hookId = IntPtr.Zero;
        }

        private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0)
            {
                var vkCode = Marshal.ReadInt32(lParam);
                var isKeyDown = wParam == (IntPtr)WmKeyDown || wParam == (IntPtr)WmSysKeyDown;
                var isKeyUp = wParam == (IntPtr)WmKeyUp || wParam == (IntPtr)WmSysKeyUp;

                if (isKeyDown || isKeyUp)
                {
                    KeyPressed?.Invoke(this, new KeyPressedEventArgs(vkCode, isKeyDown));
                }
            }

            return CallNextHookEx(_hookId, nCode, wParam, lParam);
        }

        public void Dispose()
        {
            if (_isDisposed) return;
            _isDisposed = true;
            Unhook();
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string? lpModuleName);
    }

    private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

    private sealed class KeyPressedEventArgs : EventArgs
    {
        public int Key { get; }
        public bool IsKeyDown { get; }

        public KeyPressedEventArgs(int key, bool isKeyDown)
        {
            Key = key;
            IsKeyDown = isKeyDown;
        }
    }
}

