// ==========================================================
// hover_watcher.cpp - Native Mouse Dwell & Clipboard Item Inspector Hook
// Poe2VisualTool v2.0.0 (100% C++23 Native Fusion)
// ==========================================================

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif

#include <windows.h>
#include "inspector/hover_watcher.hpp"
#include "common/window_utils.hpp"
#include "common/string_utils.hpp"

#include <chrono>
#include <cmath>

namespace inspector {

HoverWatcher::HoverWatcher() = default;

HoverWatcher::~HoverWatcher() {
    Stop();
}

void HoverWatcher::SetCallbacks(ItemInspectedCallback onInspected, CursorMovedCallback onMoved) {
    onInspected_ = std::move(onInspected);
    onMoved_ = std::move(onMoved);
}

bool HoverWatcher::Start() {
    if (isRunning_.load()) {
        return true;
    }
    isRunning_.store(true);
    workerThread_ = std::thread(&HoverWatcher::WorkerLoop, this);
    return true;
}

void HoverWatcher::Stop() {
    if (!isRunning_.load()) {
        return;
    }
    isRunning_.store(false);
    if (workerThread_.joinable()) {
        workerThread_.join();
    }
}

namespace {

struct ClipboardCloser {
    bool opened = false;
    explicit ClipboardCloser(HWND hwnd = nullptr) {
        for (int i = 0; i < 5; ++i) {
            if (OpenClipboard(hwnd)) {
                opened = true;
                break;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(5));
        }
    }
    ~ClipboardCloser() {
        if (opened) CloseClipboard();
    }
};

std::wstring GetCurrentClipboardWide() {
    ClipboardCloser closer;
    if (!closer.opened) return L"";
    HANDLE hData = GetClipboardData(CF_UNICODETEXT);
    if (!hData) return L"";
    const wchar_t* pText = static_cast<const wchar_t*>(GlobalLock(hData));
    if (!pText) return L"";
    std::wstring str(pText);
    GlobalUnlock(hData);
    return str;
}

void SetCurrentClipboardWide(const std::wstring& text) {
    if (text.empty()) return;
    ClipboardCloser closer;
    if (!closer.opened) return;
    EmptyClipboard();
    const size_t bytes = (text.size() + 1) * sizeof(wchar_t);
    HGLOBAL hGlob = GlobalAlloc(GMEM_MOVEABLE, bytes);
    if (!hGlob) return;
    void* pDest = GlobalLock(hGlob);
    if (!pDest) {
        GlobalFree(hGlob);
        return;
    }
    std::memcpy(pDest, text.c_str(), bytes);
    GlobalUnlock(hGlob);
    HANDLE hRes = SetClipboardData(CF_UNICODETEXT, hGlob);
    if (!hRes) {
        GlobalFree(hGlob);
    }
}

} // namespace

std::string HoverWatcher::ReadClipboardText() {
    std::wstring wstr = GetCurrentClipboardWide();
    if (wstr.empty()) return "";

    int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
    if (utf8Len <= 0) return "";

    std::string utf8Str(utf8Len - 1, '\0');
    WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, utf8Str.data(), utf8Len, nullptr, nullptr);
    return utf8Str;
}

void HoverWatcher::WorkerLoop() {
    POINT lastPos{ -9999, -9999 };
    auto lastMoveTime = std::chrono::steady_clock::now();
    bool inspectedThisDwell = false;

    while (isRunning_.load()) {
        POINT curPos{};
        if (!GetCursorPos(&curPos)) {
            std::this_thread::sleep_for(std::chrono::milliseconds(25));
            continue;
        }

        const int dx = curPos.x - lastPos.x;
        const int dy = curPos.y - lastPos.y;
        const double dist = std::sqrt(dx * dx + dy * dy);

        auto now = std::chrono::steady_clock::now();

        if (dist > 6.0) {
            // Cursor moved
            lastPos = curPos;
            lastMoveTime = now;
            inspectedThisDwell = false;

            if (onMoved_) {
                onMoved_();
            }
        } else {
            // Cursor is dwelling at current location
            auto dwellElapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastMoveTime).count();

            if (dwellElapsed >= dwellTimeMs_ && !inspectedThisDwell) {
                // Safety Interlock: NEVER send input if user is actively clicking or holding mouse buttons (moving / channelling)
                const bool userClicking = ((GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0) ||
                                         ((GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0) ||
                                         ((GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0);

                HWND poeHwnd = common::FindPoe2Window();
                HWND fgHwnd = GetForegroundWindow();

                // Focus interlock: only inspect when Poe2 is active and user is not clicking
                if (!userClicking && poeHwnd && (fgHwnd == poeHwnd || IsChild(poeHwnd, fgHwnd))) {
                    // Backup user's existing clipboard
                    std::wstring userClipboardBackup = GetCurrentClipboardWide();
                    const DWORD seqBefore = GetClipboardSequenceNumber();

                    // Send Ctrl+C via SendInput (respecting physical Ctrl state)
                    const bool physicalCtrlDown = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
                    INPUT inputs[4] = {};
                    int inputCount = 0;

                    if (!physicalCtrlDown) {
                        inputs[inputCount].type = INPUT_KEYBOARD;
                        inputs[inputCount].ki.wVk = VK_CONTROL;
                        inputCount++;
                    }

                    inputs[inputCount].type = INPUT_KEYBOARD;
                    inputs[inputCount].ki.wVk = 'C';
                    inputCount++;

                    inputs[inputCount].type = INPUT_KEYBOARD;
                    inputs[inputCount].ki.wVk = 'C';
                    inputs[inputCount].ki.dwFlags = KEYEVENTF_KEYUP;
                    inputCount++;

                    if (!physicalCtrlDown) {
                        inputs[inputCount].type = INPUT_KEYBOARD;
                        inputs[inputCount].ki.wVk = VK_CONTROL;
                        inputs[inputCount].ki.dwFlags = KEYEVENTF_KEYUP;
                        inputCount++;
                    }

                    SendInput(inputCount, inputs, sizeof(INPUT));
                    std::this_thread::sleep_for(std::chrono::milliseconds(45));

                    // Only process clipboard if SendInput (Ctrl+C) actually caused an item to be copied
                    // (seqAfter != seqBefore). If user hovered over ground or non-item, sequence number
                    // does not change and we MUST NOT inspect stale clipboard content.
                    const DWORD seqAfter = GetClipboardSequenceNumber();
                    if (seqAfter != seqBefore) {
                        std::string clip = ReadClipboardText();
                        if (!clip.empty() && (clip.find("Item Class:") != std::string::npos || clip.find("Rarity:") != std::string::npos)) {
                            ItemInspectionReport report = ItemParser::Parse(clip);
                            if (!report.itemName.empty() && onInspected_) {
                                onInspected_(report, curPos.x, curPos.y);
                            }
                        }

                        // Restore user's original clipboard text (non-destructive inspection)
                        if (!userClipboardBackup.empty()) {
                            SetCurrentClipboardWide(userClipboardBackup);
                        }
                    }
                }
                inspectedThisDwell = true;
            }
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(25));
    }
}

} // namespace inspector
