--[[
  @srp-core.dll FFI api wrapper
  Copyright Samp-Rp.Ru 2025
]]

local M = {
  apiVersion = 1
}

local ffi = require("ffi")
local bit = require("bit")

ffi.cdef[[
uintptr_t __stdcall GetModuleHandleA(const char*);

struct _SRP_StreamingStats {
  uint32_t structSize; // sizeof(SRP_StreamingStats)
  uint32_t streamMemoryMax;
  uint32_t streamMemoryUsage;
  struct {
    uint32_t totalMax;
    uint32_t totalUsage;
    uint32_t atomicsCount;
    uint32_t loadedAtomicsCount;
    uint32_t timesCount;
    uint32_t loadedTimesCount;
    uint32_t weaponsCount;
    uint32_t loadedWeaponsCount;
    uint32_t clumpsCount;
    uint32_t loadedClumpsCount;
    uint32_t vehiclesCount;
    uint32_t loadedVehiclesCount;
    uint32_t pedsCount;
    uint32_t loadedPedsCount;
    uint32_t lodsCount;
    uint32_t loadedLodsCount;
  } modelInfos;
};
typedef struct _SRP_StreamingStats SRP_StreamingStats;

/// General
uint32_t    __cdecl SRP_GetVersion();
const char* __cdecl SRP_GetGameVersion();
uint32_t    __cdecl SRP_GetLuaApiVersion();
uint32_t    __cdecl SRP_GetSampVersion();

/// Discord
size_t      __cdecl SRP_GetDiscordStatus(char* outStatusUTF8, size_t capacity);
void        __cdecl SRP_SetDiscordStatus(const char* statusUTF8);

/// Stats
bool        __cdecl SRP_GetStreamingStats(SRP_StreamingStats* out);

/// UI
void        __cdecl SRP_DisplayNotification(uint32_t type, const char* titleUTF8, const char* textUTF8, uint32_t timeMs);
]]

local Kernel32 = ffi.load("Kernel32.dll")
local lib

if Kernel32.GetModuleHandleA("@srp-core.dll") ~= 0 then
  lib = ffi.load("@srp-core.dll")

  local dllApiVersion = lib.SRP_GetLuaApiVersion()
  if dllApiVersion ~= M.apiVersion then
    print(("[WARN] SAMP-RP Lua api version differs in DLL and lua (DLL: %d, Lua: %d)"):format(dllApiVersion, M.apiVersion))
  end
end

M._lib = lib

---@return boolean status Returns true if this api is available (running in samp rp launcher)
function M.isUsingSampRpLauncher()
  return lib ~= nil
end

---@class CoreVersion
---@field raw number Raw version representation
---@field major number Major version part
---@field minor number Minor version part
---@field patch number Patch version part
---@field tweak number Tweak version part

---@return CoreVersion version @srp-core.dll version
function M.getCoreVersion()
  if lib == nil then
    return {
      raw = 0,
      major = 0,
      minor = 0,
      patch = 0,
      tweak = 0
    }
  end

  local raw = lib.SRP_GetVersion()

  return {
    raw = raw,
    major = bit.band(bit.rshift(raw, 24), 0xFF),
    minor = bit.band(bit.rshift(raw, 16), 0xFF),
    patch = bit.band(bit.rshift(raw, 8), 0xFF),
    tweak = bit.band(raw, 0xFF)
  }
end

function M.getGameVersion()
  return ffi.string(lib.SRP_GetGameVersion())
end

---@enum SampVersion
M.SAMP_VERSION = {
  V037_R1   = 0,
  V037_R3_1 = 1
}

---@return SampVersion version
function M.getSampVersion()
  return lib.SRP_GetSampVersion()
end

---@return string status Current discord status in UTF-8
function M.getDiscordStatus()
  if lib == nil then return "" end

  local length = lib.SRP_GetDiscordStatus(nil, 0)
  local buffer = ffi.new("char[?]", length + 1)

  lib.SRP_GetDiscordStatus(buffer, length + 1)
  return ffi.string(buffer)
end

---@param status string New discord status in UTF-8
function M.setDiscordStatus(status)
  if lib == nil then return end

  -- todo? Check if status in ANSI and convert it to UTF-8

  lib.SRP_SetDiscordStatus(status)
end

---@return any|nil stats Game streaming statistics
function M.getStreamingStats()
  if lib == nil then return nil end

  local struct = ffi.new("SRP_StreamingStats")
  struct.structSize = ffi.sizeof("SRP_StreamingStats")

  local success = lib.SRP_FetchStreamingStats(struct)

  if not success then
    return nil
  end

  return struct
end

---@alias NotificationType
---| "success"
---| "error"
---| "info"

local NOTF_TYPE_MAP = {
  success = 0,
  error = 1,
  info = 2
}

---@param type NotificationType Type of notification. Any unexpected value will be treated as 'info'
---@param title string Title in UTF-8
---@param text string Text in UTF-8
---@param timeMs number Display time in milliseconds
function M.displayNotification(type, title, text, timeMs)
  if lib == nil then return end

  local typeNum = NOTF_TYPE_MAP[type]
  if typeNum == nil then
    typeNum = 2 -- info
  end

  lib.SRP_DisplayNotification(typeNum, title, text, timeMs)
end

return M
