-- ---------------------------------------------------------------------------
--  Server half of the request/response bridge.
--  Include with:  server_scripts { '@rp_core/lib/callbacks_server.lua', ... }
--
--      Core.RegisterCallback('auth:login', function(src, payload)
--          return result, err          -- err ~= nil is reported to the client
--      end)
--
--  Handlers run in their own thread so they may block on the database.
--  Every handler is rate limited per player; a client that floods is ignored
--  rather than allowed to queue unbounded database work.
-- ---------------------------------------------------------------------------

Core = Core or {}

local RES      = GetCurrentResourceName()
local handlers = {}
local buckets  = {}          -- [src] = { tokens = n, last = ms }

local BUCKET_MAX  = 15       -- burst
local BUCKET_RATE = 5        -- refilled per second

local function allow(src)
  local now = GetGameTimer()
  local b = buckets[src]
  if not b then
    b = { tokens = BUCKET_MAX, last = now }
    buckets[src] = b
  end
  local elapsed = (now - b.last) / 1000
  b.last = now
  b.tokens = math.min(BUCKET_MAX, b.tokens + elapsed * BUCKET_RATE)
  if b.tokens < 1 then return false end
  b.tokens = b.tokens - 1
  return true
end

AddEventHandler('playerDropped', function()
  buckets[source] = nil
end)

function Core.RegisterCallback(name, fn)
  if handlers[name] then
    print(('^3[rp_core]^7 callback %q registered twice in %s'):format(name, RES))
  end
  handlers[name] = fn
end

RegisterNetEvent('rp:cb:' .. RES, function(fromResource, name, token, payload)
  local src = source

  -- A client controls fromResource/token, so they are only ever echoed back to
  -- that same client. They are never used to look anything up on the server.
  if type(fromResource) ~= 'string' or type(name) ~= 'string' then return end

  local reply = function(result, err)
    TriggerClientEvent('rp:cb:res:' .. fromResource, src, token, result, err)
  end

  if not allow(src) then
    return reply(nil, 'slow down')
  end

  local fn = handlers[name]
  if not fn then
    print(('^3[rp_core]^7 %s: unknown callback %q from %d'):format(RES, name, src))
    return reply(nil, 'unknown request')
  end

  CreateThread(function()
    local ok, result, err = pcall(fn, src, payload)
    if not ok then
      print(('^1[rp_core]^7 callback %q errored: %s'):format(name, tostring(result)))
      return reply(nil, 'internal error')
    end
    reply(result, err)
  end)
end)
