// ============================================================================
// DynLibUtils.cpp — Shared dynamic library loading utilities
// ============================================================================

#include "DynLibUtils.h"

#ifndef _WIN32
#  include <sys/stat.h>
#endif

namespace ANSCENTER {
namespace DynLib {

    // ── FileExists ───────────────────────────────────────────────────────────
    bool FileExists(const std::string& path)
    {
#ifdef _WIN32
        DWORD attr = GetFileAttributesA(path.c_str());
        return (attr != INVALID_FILE_ATTRIBUTES) &&
            !(attr & FILE_ATTRIBUTE_DIRECTORY);
#else
        struct stat st {};
        return (::stat(path.c_str(), &st) == 0) && S_ISREG(st.st_mode);
#endif
    }

    // ── JoinPath ─────────────────────────────────────────────────────────────
    std::string JoinPath(const std::string& base, const std::string& component)
    {
#ifdef _WIN32
        const char sep = '\\';
#else
        const char sep = '/';
#endif
        if (base.empty()) return component;
        if (base.back() == sep || base.back() == '/')
            return base + component;
        return base + sep + component;
    }

    // ── InjectDllSearchPath ──────────────────────────────────────────────────
    void InjectDllSearchPath(const std::string& dir)
    {
#ifdef _WIN32
        // Safe UTF-8 → wide-string conversion (handles non-ASCII paths correctly)
        int wlen = MultiByteToWideChar(CP_UTF8, 0, dir.c_str(), -1, nullptr, 0);
        std::wstring wdir(static_cast<size_t>(wlen > 0 ? wlen - 1 : 0), L'\0');
        if (wlen > 0)
            MultiByteToWideChar(CP_UTF8, 0, dir.c_str(), -1, wdir.data(), wlen);
        DLL_DIRECTORY_COOKIE cookie = AddDllDirectory(wdir.c_str());
        if (!cookie)
            std::cerr << "[DynLib] WARNING: AddDllDirectory failed for: "
                << dir << " (error " << GetLastError() << ")" << std::endl;

        char existing_path[32767] = {};
        GetEnvironmentVariableA("PATH", existing_path, sizeof(existing_path));
        std::string new_path = dir + ";" + existing_path;
        if (!SetEnvironmentVariableA("PATH", new_path.c_str()))
            std::cerr << "[DynLib] WARNING: SetEnvironmentVariable PATH failed."
                << std::endl;
#else
        const char* existing = getenv("LD_LIBRARY_PATH");
        std::string new_path = dir +
            (existing ? (":" + std::string(existing)) : "");
        setenv("LD_LIBRARY_PATH", new_path.c_str(), 1);
#endif
        std::cout << "[DynLib] DLL search path injected: " << dir << std::endl;
    }

    // ── LoadLib ──────────────────────────────────────────────────────────────
    LibHandle LoadLib(const std::string& path, bool globalLoad)
    {
#ifdef _WIN32
        (void)globalLoad;
        return LoadLibraryExA(path.c_str(), nullptr,
                              LOAD_WITH_ALTERED_SEARCH_PATH);
#else
        return globalLoad
            ? ::dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL)
            : ::dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
#endif
    }

    // ── FreeLib ──────────────────────────────────────────────────────────────
    void FreeLib(LibHandle h)
    {
        if (!h) return;
#ifdef _WIN32
        ::FreeLibrary(h);
#else
        ::dlclose(h);
#endif
    }

    // ── GetSymbol ────────────────────────────────────────────────────────────
    void* GetSymbol(LibHandle h, const char* sym)
    {
        if (!h) return nullptr;
#ifdef _WIN32
        return reinterpret_cast<void*>(::GetProcAddress(h, sym));
#else
        return ::dlsym(h, sym);
#endif
    }

    // ── FindAndLoad ──────────────────────────────────────────────────────────
    LibHandle FindAndLoad(const std::vector<std::string>& candidates,
                          std::string& loadedPath,
                          bool verbose,
                          bool globalLoad)
    {
        for (const auto& name : candidates) {
            LibHandle h = LoadLib(name, globalLoad);
            if (h) {
                loadedPath = name;
                if (verbose)
                    std::cout << "[DynLib] Loaded: " << name << std::endl;
                return h;
            }
        }
        return LIB_INVALID;
    }

} // namespace DynLib
} // namespace ANSCENTER
