#include "keyboard.h"
#include <atomic>
#include <thread>
#include <chrono>

#ifdef _WIN32
#include <windows.h>
#endif

static Napi::ThreadSafeFunction g_watchCallback;
static std::atomic<bool> g_listening{false};
static HHOOK g_hook = NULL;

// 统一派发事件
static void EmitEvent(int keyCode, const char* type) {
    if (!g_listening || !g_watchCallback) return;

    auto data = new std::pair<int, std::string>{keyCode, type};
    g_watchCallback.BlockingCall(data, [](Napi::Env env, Napi::Function cb, std::pair<int, std::string>* data) {
        Napi::Object obj = Napi::Object::New(env);
        obj.Set("name", data->first);
        obj.Set("type", Napi::String::New(env, data->second));
        cb.Call({obj});
        delete data;
    });
}

// 发送按键
static void SendKey(int code, bool down) {
#ifdef _WIN32
    INPUT input = {0};
    input.type = INPUT_KEYBOARD;
    input.ki.wVk = (WORD)code;
    input.ki.dwFlags = down ? 0 : KEYEVENTF_KEYUP;
    SendInput(1, &input, sizeof(input));
#endif
}

// 全局键盘钩子
#ifdef _WIN32
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode >= 0 && g_listening) {
        KBDLLHOOKSTRUCT* p = (KBDLLHOOKSTRUCT*)lParam;
        int vk = p->vkCode;

        if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
            EmitEvent(vk, "down");
        }
        else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
            EmitEvent(vk, "up");
        }
    }
    return CallNextHookEx(g_hook, nCode, wParam, lParam);
}

static void RunHook() {
    g_hook = SetWindowsHookExA(WH_KEYBOARD_LL, HookProc, GetModuleHandleA(NULL), 0);
    MSG msg;
    while (g_listening && GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    UnhookWindowsHookEx(g_hook);
    g_hook = NULL;
}
#endif

// ==================== API 实现 ====================
// 长按
Napi::Value KeyboardPlugin::down(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    if (!info[0].IsNumber()) return env.Undefined();

    int code = info[0].As<Napi::Number>();
    Napi::Function cb;
    if (info.Length() > 1 && info[1].IsFunction())
        cb = info[1].As<Napi::Function>();

    SendKey(code, true);
    EmitEvent(code, "down");

    if (!cb.IsEmpty()) cb.Call({env.Null()});
    return env.Undefined();
}

// 松开
Napi::Value KeyboardPlugin::up(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    if (!info[0].IsNumber()) return env.Undefined();

    int code = info[0].As<Napi::Number>();
    Napi::Function cb;
    if (info.Length() > 1 && info[1].IsFunction())
        cb = info[1].As<Napi::Function>();

    SendKey(code, false);
    EmitEvent(code, "up");

    if (!cb.IsEmpty()) cb.Call({env.Null()});
    return env.Undefined();
}

// 单击（短暂按下松开）
Napi::Value KeyboardPlugin::once(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    if (!info[0].IsNumber()) return env.Undefined();

    int code = info[0].As<Napi::Number>();
    Napi::Function cb;
    if (info.Length() > 1 && info[1].IsFunction())
        cb = info[1].As<Napi::Function>();

    // 按下 -> 等待 -> 松开
    SendKey(code, true);
    EmitEvent(code, "once");
    std::this_thread::sleep_for(std::chrono::milliseconds(50));
    SendKey(code, false);

    if (!cb.IsEmpty()) cb.Call({env.Null()});
    return env.Undefined();
}

// 监听
Napi::Value KeyboardPlugin::watch(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    if (!info[0].IsFunction()) return env.Undefined();

    g_watchCallback = Napi::ThreadSafeFunction::New(env, info[0].As<Napi::Function>(), "watch", 0, 1);
    g_listening = true;

#ifdef _WIN32
    std::thread(RunHook).detach();
#endif

    return env.Undefined();
}

// 停止
Napi::Value KeyboardPlugin::stop(const Napi::CallbackInfo& info) {
    g_listening = false;
#ifdef _WIN32
    PostQuitMessage(0);
#endif
    if (g_watchCallback) g_watchCallback.Release();
    return info.Env().Undefined();
}

// 导出
Napi::Object KeyboardPlugin::Init(Napi::Env env, Napi::Object exports) {
    Napi::Object key = Napi::Object::New(env);
    key.Set("down", Napi::Function::New(env, KeyboardPlugin::down));
    key.Set("up", Napi::Function::New(env, KeyboardPlugin::up));
    key.Set("once", Napi::Function::New(env, KeyboardPlugin::once));
    key.Set("start", Napi::Function::New(env, KeyboardPlugin::watch));
    key.Set("stop", Napi::Function::New(env, KeyboardPlugin::stop));
    exports.Set("key", key);
    return exports;
}