-- ---------------------------------------------------------------------------
--  DB - Lua front end for rp_db.
--
--  Loaded *into* the consuming resource with:
--      server_scripts { '@rp_db/lib/db.lua', 'server/whatever.lua' }
--
--  so that Citizen.Await suspends the caller's own coroutine instead of trying
--  to yield across a cross-resource export call.
--
--  Every function comes in two flavours:
--      DB.Query(sql, params)          -> rows, err      (blocking, inside a thread)
--      DB.QueryAsync(sql, params, cb) -> cb(rows, err)  (callback, anywhere)
--
--  Values are always passed as `?` parameters. Never build SQL by concatenation.
--  Use DB.NULL where you need a literal SQL NULL (Lua nil cannot survive a table).
-- ---------------------------------------------------------------------------

DB = {}

DB.NULL = 'RP_NULL'

local backend = exports.rp_db

local function async(method)
  return function(sql, params, cb)
    backend[method](backend, sql, params or {}, cb)
  end
end

local function blocking(method)
  return function(sql, params)
    local p = promise.new()
    backend[method](backend, sql, params or {}, function(result, err)
      -- wrapped: a promise resolved with nil would never wake the coroutine
      p:resolve({ result = result, err = err })
    end)
    local r = Citizen.Await(p)
    if r.err then return nil, r.err end
    return r.result
  end
end

--- Returns all rows as an array of tables (empty table when nothing matched).
DB.Query  = blocking('query')
--- Returns the first row as a table, or nil.
DB.Single = blocking('single')
--- Returns the first column of the first row, or nil.
DB.Scalar = blocking('scalar')
--- Returns the AUTO_INCREMENT id produced by an INSERT.
DB.Insert = blocking('insert')
--- Returns the number of rows changed by an UPDATE/DELETE.
DB.Update = blocking('update')

DB.QueryAsync  = async('query')
DB.SingleAsync = async('single')
DB.ScalarAsync = async('scalar')
DB.InsertAsync = async('insert')
DB.UpdateAsync = async('update')

--- All statements commit together or none of them do.
--- statements: { { query = 'UPDATE ..', values = { 1, 2 } }, ... }
function DB.Transaction(statements)
  local p = promise.new()
  backend:transaction(statements, function(ok, err)
    p:resolve({ ok = ok, err = err })
  end)
  local r = Citizen.Await(p)
  if not r.ok then return false, r.err end
  return true
end

function DB.TransactionAsync(statements, cb)
  backend:transaction(statements, cb)
end

--- Blocks until the connection pool has at least one live connection.
--- Returns false if the database never came up within `timeout` ms.
function DB.WaitReady(timeout)
  local waited, step = 0, 100
  timeout = timeout or 30000
  while not backend:isReady() do
    if waited >= timeout then return false end
    Citizen.Wait(step)
    waited = waited + step
  end
  return true
end
