local CALLBACK_RETRIES    = 0

-- Internal: await a callback with a timeout
local function awaitWithTimeout(registerFn, timeoutMs)
    local p = promise.new()
    local finished = false

    registerFn(function(result)
        if finished then return end
        finished = true
        p:resolve(result)
    end)

    -- timeout watchdog
    CreateThread(function()
        Wait(5000)
        if not finished then
            finished = true
            p:reject('timeout')
        end
    end)

    local ok, res = pcall(function() return Citizen.Await(p) end)
    if ok then return res, nil end
    return nil, res
end

--- Registers a callback function with the appropriate framework.
---
--- This function checks which framework is started (e.g., OX, QB, ESX) and registers the callback accordingly.
--- It adapts the callback function to match the expected signature for the framework.
---
---@param callbackName string The name of the callback to register.
---@param funct function The function to be called when the callback is triggered.
---
---@usage
--- ```lua
--- local table = { ["info"] = "HI" }
--- createCallback('myCallback', function(source, ...)
---     return table
--- end)
---
--- createCallback("callback:checkVehicleOwned", function(source, plate)
--- 	local result = isVehicleOwned(plate)
--- 	if result then
---         return true
---      else
---         return false
---     end
--- end)
--- ```
function createCallback(callbackName, funct)
    if isServer() then
        if isStarted(OXLibExport) then
            debugPrint("^6Bridge^7: ^2Registering ^4"..OXLibExport.." ^3Callback^7:", callbackName)
            lib.callback.register(callbackName, funct)
        else
            local adaptedFunction = function(source, cb, ...)
                local result = funct(source, ...)
                cb(result)
            end

            if isStarted(QBExport) then
                debugPrint("^6Bridge^7: ^2Registering ^4"..QBExport.." ^3Callback^7:", callbackName)
                Core = Core or exports[QBExport]:GetCoreObject()
                Core.Functions.CreateCallback(callbackName, adaptedFunction)
            elseif isStarted(VorpExport) then
                debugPrint("^6Bridge^7: ^2Registering ^4"..VorpExport.." ^3Callback^7:", callbackName)
                Core = Core or exports.vorp_core:GetCore()
                Core.Callback.Register(callbackName, adaptedFunction)
            elseif isStarted(ESXExport) then
                debugPrint("^6Bridge^7: ^2Registering ^4"..ESXExport.." ^3Callback^7:", callbackName)
                ESX.RegisterServerCallback(callbackName, adaptedFunction)
            else
                print("^1ERROR^7: ^1Can't find any supported framework to register callback with^7: "..callbackName)
            end
        end
    end
end

--- Triggers a server callback and returns the result.
---
--- This function triggers a server callback using the appropriate framework's method and awaits the result.
---
---@param callbackName string The name of the callback to trigger.
---@param ... any Additional arguments to pass to the callback.
---
---@return any any The result returned by the callback function.
---@return string string The error/success message returned by the callback function.
---
---@usage
--- ```lua
--- local result = triggerCallback('myCallback')
--- jsonPrint(result)
---
--- local result = triggerCallback("callback:checkVehicleOwned", plate)
--- print(result)
--- ```
function triggerCallback(callbackName, ...)
    debugPrint("^6Bridge^7: ^2Triggering ^3Callback^7:", callbackName)
    local args = {...}

    if isStarted(OXLibExport) then
        local ok, res = pcall(function()
            return lib.callback.await(callbackName, false, table.unpack(args))
        end)
        if ok then return res, "nil" end
        return nil, tostring(res)
    end

    local attempts = 0
    local lastErr

    repeat
        attempts = attempts + 1

        if isStarted(QBExport) then
            local res, err = awaitWithTimeout(function(cb)
                Core.Functions.TriggerCallback(callbackName, cb, table.unpack(args))
            end, 5000)
            if res ~= nil then return res, "nil" end
            lastErr = err
            debugPrint(("^6Bridge^7: ^3Callback^7 %s ^1failed^7 (QB) attempt %d: %s"):format(callbackName, attempts, tostring(err)))

        elseif isStarted(VorpExport) then
            local res, err = awaitWithTimeout(function(cb)
                Core.Callback.TriggerAwait(callbackName, cb, table.unpack(args))
            end, 5000)
            if res ~= nil then return res, "nil" end
            lastErr = err
            debugPrint(("^6Bridge^7: ^3Callback^7 %s ^1failed^7 (Vorp) attempt %d: %s"):format(callbackName, attempts, tostring(err)))

        elseif isStarted(ESXExport) then
            local res, err = awaitWithTimeout(function(cb)
                ESX.TriggerServerCallback(callbackName, cb, table.unpack(args))
            end, 5000)
            if res ~= nil then return res, "nil" end
            lastErr = err
            debugPrint(("^6Bridge^7: ^3Callback^7 %s ^1failed^7 (ESX) attempt %d: %s"):format(callbackName, attempts, tostring(err)))

        else
            print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to trigger callback with^7:", callbackName)
            return nil, "no_framework"
        end

        Wait(10)
    until attempts > (1 + CALLBACK_RETRIES)

    return nil, lastErr or "timeout"
end
