local loaded = {}

package = {
    loaded = setmetatable({}, {
        __index = loaded,
        __newindex = noop,
        __metatable = false,
    }),
    path = './?.lua;'
}

local _require = require

---@param modpath string
---@param modname? string
---@return string, string?
local function getModuleInfo(modpath, modname)
    local resourceSrc

    if not modpath:find('^@') then
        local idx = 1

        while true do
            local di = debug.getinfo(idx, 'S')

            if di then
                if not di.short_src:find('^@ox_lib/imports/require') and not di.short_src:find('^%[C%]') and not di.short_src:find('^citizen') and di.short_src ~= '?' then
                    resourceSrc = di.short_src:gsub('^@(.-)/.+', '%1')
                    break
                end
            else
                resourceSrc = cache.resource
                break
            end

            idx += 1
        end

        if modname and resourceSrc ~= cache.resource then
            modname = ('@%s.%s'):format(resourceSrc, modname)
        end
    end

    return resourceSrc, modname
end

---@param filePath string
---@param env? table
---@return any
---Loads and runs a Lua file at the given path. Unlike require, the chunk is not cached for future use.
function lib.load(filePath, env)
    local resourceSrc
    local modpath = filePath:gsub('%.', '/')

    if not modpath:find('^@') then
        resourceSrc = getModuleInfo(modpath)
    end

    if not resourceSrc then
        resourceSrc = modpath:gsub('^@(.-)/.+', '%1')
        modpath = modpath:sub(#resourceSrc + 3)
    end

    for path in package.path:gmatch('[^;]+') do
        local scriptPath = path:gsub('?', modpath):gsub('%.+%/+', '')
        local resourceFile = LoadResourceFile(resourceSrc, scriptPath)

        if resourceFile then

            local chunk, err = load(resourceFile, ('@@%s/%s'):format(resourceSrc, modpath), 't', env or _ENV)

            if not chunk or err then
                error(err or 'an unknown error occurred', 2)
            end

            return chunk()
        end
    end

    error(('cannot load file at path %s'):format(modpath))
end

---@param filePath string
---@return table
---Loads and decodes a json file at the given path.
function lib.loadJson(filePath)
    local resourceSrc
    local modpath = filePath:gsub('%.', '/')

    if not modpath:find('^@') then
        resourceSrc = getModuleInfo(modpath)
    end

    if not resourceSrc then
        resourceSrc = modpath:gsub('^@(.-)/.+', '%1')
        modpath = modpath:sub(#resourceSrc + 3)
    end

    local scriptPath = ('%s.json'):format(modpath)
    local resourceFile = LoadResourceFile(resourceSrc, scriptPath)

    if resourceFile then
        return json.decode(resourceFile)
    end

    error(('cannot load json file at path %s'):format(modpath))
end

---Loads the given module inside the current resource, returning any values returned by the file or `true` when `nil`.
---@param modname string
---@return unknown?
function lib.require(modname)
    if type(modname) ~= 'string' then return end

    local modpath = modname:gsub('%.', '/')
    local module = loaded[modname]

    if module then return module end

    local success, result = pcall(_require, modname)

    if success then
        loaded[modname] = result
        return result
    end

    local resourceSrc

    if not modpath:find('^@') then
        resourceSrc, modname = getModuleInfo(modpath, modname) --[[@as string]]
    end

    if not module then
        if module == false then
            error(("^1circular-dependency occurred when loading module '%s'^0"):format(modname), 2)
        end

        if not resourceSrc then
            resourceSrc = modpath:gsub('^@(.-)/.+', '%1')
            modpath = modpath:sub(#resourceSrc + 3)
        end

        for path in package.path:gmatch('[^;]+') do
            local scriptPath = path:gsub('?', modpath):gsub('%.+%/+', '')
            local resourceFile = LoadResourceFile(resourceSrc, scriptPath)

            if resourceFile then
                loaded[modname] = false
                scriptPath = ('@@%s/%s'):format(resourceSrc, scriptPath)

                local chunk, err = load(resourceFile, scriptPath)

                if err or not chunk then
                    loaded[modname] = nil
                    return error(err or ("unable to load module '%s'"):format(modname), 3)
                end

                module = chunk(modname) or true
                loaded[modname] = module

                return module
            end
        end

        return error(result, 2)
    end

    return module
end

return lib.require
