// FuckCapsLock — Permanently disable the vestigial Caps Lock key.
// Copyright (c) 2026 Llama Chile Shop. MIT License.
//
// This application was pair-programmed by:
//   gramps@llamachile.shop (senior developer, direct supervision)
//   opencode (AI pair programmer)
//
// Under direct, senior supervision, paired programming can be an
// effective and efficient tool.
//
// Installs a low-level keyboard hook (WH_KEYBOARD_LL) that swallows
// VK_CAPITAL (0x14) before it reaches any window. Runs in the system
// tray until the user exits via the context menu.
//
// System calls reference:
//   SetWindowsHookEx / UnhookWindowsHookEx / CallNextHookEx — user32.dll
//   GetModuleHandle — kernel32.dll
//   Registry.CurrentUser\...\Run — auto-start on boot

using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32;

static class Program
{
    /// <summary>Low-level keyboard hook identifier — intercepts keys before any window.</summary>
    const int WH_KEYBOARD_LL = 13;

    /// <summary>Virtual-key code for Caps Lock — the only key this app blocks.</summary>
    const int VK_CAPITAL = 0x14;

    /// <summary>Callback signature for LowLevelKeyboardProc used by SetWindowsHookEx.</summary>
    /// <param name="nCode">Hook code — >= 0 means the hook should process the message.</param>
    /// <param name="wParam">Message type (WM_KEYDOWN, WM_KEYUP, etc.).</param>
    /// <param name="lParam">Pointer to a KBDLLHOOKSTRUCT with the raw key data.</param>
    /// <returns>1 to swallow the key, or CallNextHookEx to let it through.</returns>
    delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

    /// <summary>Installs a low-level keyboard hook into the global hook chain.</summary>
    /// <param name="idHook">Hook type — WH_KEYBOARD_LL (13) for keyboard events.</param>
    /// <param name="lpfn">Pointer to the callback function.</param>
    /// <param name="hMod">Module handle for the current process.</param>
    /// <param name="dwThreadId">0 = hook applies globally to all threads in this desktop.</param>
    /// <returns>Hook handle on success, IntPtr.Zero on failure.</returns>
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

    /// <summary>Removes a previously installed hook from the hook chain.</summary>
    /// <param name="hhk">Hook handle returned by SetWindowsHookEx.</param>
    /// <returns>True on success.</returns>
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool UnhookWindowsHookEx(IntPtr hhk);

    /// <summary>Passes the hook event to the next hook in the chain.</summary>
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

    /// <summary>Retrieves the module handle for the calling process — required by SetWindowsHookEx.</summary>
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern IntPtr GetModuleHandle(string lpModuleName);

    /// <summary>Structure deserialized from the lParam pointer in the hook callback.</summary>
    [StructLayout(LayoutKind.Sequential)]
    struct KBDLLHOOKSTRUCT
    {
        public uint vkCode;
        public uint scanCode;
        public uint flags;
        public uint time;
        public IntPtr dwExtraInfo;
    }

    /// <summary>Cached delegate — prevents garbage collection of the callback.</summary>
    static LowLevelKeyboardProc _proc = HookCallback;

    /// <summary>Active hook handle returned by SetWindowsHookEx.</summary>
    static IntPtr _hookId = IntPtr.Zero;

    /// <summary>System tray icon — visible while the app is running.</summary>
    static NotifyIcon? _trayIcon;

    /// <summary>
    /// Entry point. Registers auto-start, enforces a single instance via a
    /// named mutex, installs the keyboard hook, and runs the WinForms message
    /// loop. The app lives in the system tray until Exit is selected.
    /// </summary>
    [STAThread]
    static void Main()
    {
        RegisterStartup();

        // Named mutex prevents multiple instances — second launch exits silently.
        using var mutex = new Mutex(true, @"Global\FuckCapsLock", out var createdNew);
        if (!createdNew)
            return;

        // Install the low-level hook; bail if it fails (e.g., insufficient permissions).
        _hookId = SetHook(_proc);
        if (_hookId == IntPtr.Zero)
            return;

        ApplicationConfiguration.Initialize();

        // Hidden window — required to pump messages and keep the hook alive.
        using var form = new Form();
        form.WindowState = FormWindowState.Minimized;
        form.ShowInTaskbar = false;
        form.Load += (_, _) => form.Hide();

        // Tray icon with Exit context menu to cleanly unhook and quit.
        _trayIcon = new NotifyIcon
        {
            Icon = SystemIcons.Application,
            Text = "FuckCapsLock",
            ContextMenuStrip = new ContextMenuStrip()
        };
        _trayIcon.ContextMenuStrip.Items.Add("Exit", null, (_, _) =>
        {
            _trayIcon.Visible = false;
            Application.Exit();
        });
        _trayIcon.Visible = true;

        // Blocks until the form closes (Exit is clicked or process is killed).
        Application.Run(form);

        // Remove the hook when the app exits.
        UnhookWindowsHookEx(_hookId);
    }

    /// <summary>
    /// Writes the current executable path to HKCU\Software\Microsoft\Windows\
    /// CurrentVersion\Run so the app auto-starts on login. Silently ignores
    /// errors (no admin rights needed for HKCU).
    /// </summary>
    static void RegisterStartup()
    {
        try
        {
            using var key = Registry.CurrentUser.OpenSubKey(
                @"Software\Microsoft\Windows\CurrentVersion\Run", true);
            if (key is null) return;

            var path = Environment.ProcessPath;
            if (path is null) return;

            // Quote the path if it contains spaces (standard Windows practice).
            var value = path.Contains(' ') ? $"\"{path}\"" : path;

            // Only write if the path has changed — avoids unnecessary registry churn.
            var existing = key.GetValue("FuckCapsLock") as string;
            if (!string.Equals(existing, value, StringComparison.OrdinalIgnoreCase))
                key.SetValue("FuckCapsLock", value);
        }
        catch { }
    }

    /// <summary>
    /// Installs the low-level keyboard hook using SetWindowsHookEx.
    /// </summary>
    /// <param name="proc">Delegate to the callback that processes each key event.</param>
    /// <returns>Hook handle or IntPtr.Zero if installation failed.</returns>
    static IntPtr SetHook(LowLevelKeyboardProc proc)
    {
        using var curProcess = Process.GetCurrentProcess();
        using var curModule = curProcess.MainModule;
        return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
            GetModuleHandle(curModule!.ModuleName), 0);
    }

    /// <summary>
    /// Hook callback invoked for every keyboard event. If the key is VK_CAPITAL (Caps Lock),
    /// the event is swallowed by returning 1 without calling CallNextHookEx. All other keys
    /// pass through normally.
    /// </summary>
    /// <param name="nCode">>= 0 means the hook should process the event.</param>
    /// <param name="wParam">Window message type (WM_KEYDOWN, WM_SYSKEYUP, etc.).</param>
    /// <param name="lParam">Pointer to a KBDLLHOOKSTRUCT with the raw key data.</param>
    /// <returns>1 if Caps Lock was blocked; otherwise the result of CallNextHookEx.</returns>
    static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0)
        {
            var hookStruct = Marshal.PtrToStructure<KBDLLHOOKSTRUCT>(lParam);
            if (hookStruct.vkCode == VK_CAPITAL)
                return (IntPtr)1; // Swallow the key — never forward it.
        }
        return CallNextHookEx(_hookId, nCode, wParam, lParam);
    }
}
