using System.Runtime.InteropServices;

namespace TodoList.Maui.Platforms.Windows
{
    public class WindowsKeyboardHandler : IDisposable
    {
        private KeyboardHook _keyboardHook;
        private bool _isDisposed;

        public event EventHandler? EscKeyPressed;

        public WindowsKeyboardHandler()
        {
            _keyboardHook = new KeyboardHook();
            _keyboardHook.KeyPressed += OnKeyPressed;
        }

        public void Start()
        {
            _keyboardHook.Hook();
        }

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

        public void Dispose()
        {
            if (!_isDisposed)
            {
                _keyboardHook.Unhook();
                _keyboardHook.KeyPressed -= OnKeyPressed;
                _isDisposed = true;
            }
        }
    }

    internal class KeyboardHook : IDisposable
    {
        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;
        private const int WM_KEYUP = 0x0101;
        private const int WM_SYSKEYDOWN = 0x0104;
        private const int WM_SYSKEYUP = 0x0105;

        private IntPtr _hookID = IntPtr.Zero;
        private LowLevelKeyboardProc _proc;
        private bool _isDisposed;

        public event EventHandler<KeyPressedEventArgs>? KeyPressed;

        public KeyboardHook()
        {
            _proc = HookCallback;
        }

        public void Hook()
        {
            _hookID = SetWindowsHookEx(WH_KEYBOARD_LL, _proc, GetModuleHandle("user32"), 0);
        }

        public void Unhook()
        {
            if (_hookID != IntPtr.Zero)
            {
                UnhookWindowsHookEx(_hookID);
                _hookID = IntPtr.Zero;
            }
        }

        private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0)
            {
                int vkCode = Marshal.ReadInt32(lParam);
                bool isKeyDown = wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN;
                bool isKeyUp = wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP;

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

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

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

        [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)]
        [return: MarshalAs(UnmanagedType.Bool)]
        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);
    }

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

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

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