-- IsDuplicityVersion - is used to determine if the function is called by the server or the client (true == from server)

--------------------- SHARED FUNCTIONS ---------------------
local Core = nil
--- @return table Core The core object of the framework
function GetCoreObject()
    if not Core then
        if Config.Framework == "esx" then
            Core = exports["es_extended"]:getSharedObject()
        elseif Config.Framework == "qb" or Config.Framework == "qbx" then
            Core = exports["qb-core"]:GetCoreObject()
        end
    end
    return Core
end

Core = Config.Framework ~= "none" and GetCoreObject() or nil

--- @param text string The text to show in the notification
--- @param notificationType string The type of notification to show ex: 'success', 'error', 'info'
--- @param src - number|nil The source of the player - only required when called from server side
function Notify(text, notificationType, src)
    if IsDuplicityVersion() then
        if Config.Notify == "esx" then
            TriggerClientEvent("esx:showNotification", src, text)
        elseif Config.Notify == "qb" then
            TriggerClientEvent("QBCore:Notify", src, text, notificationType)
        elseif Config.Notify == "ox" then
            TriggerClientEvent("ox_lib:notify", src, {
                description = text,
                type = notificationType,
            })
        end
    else
        if Config.Notify == "esx" then
            Core.ShowNotification(text)
        elseif Config.Notify == "qb" then
            Core.Functions.Notify(text, notificationType)
        elseif Config.Notify == "ox" then
            lib.notify({
                description = text,
                type = notificationType,
            })
        end
    end
end

--- @param source number|nil The source of the player
--- @return table PlayerData The player data of the player
function GetPlayerData(source)
    local Core = GetCoreObject()
    if IsDuplicityVersion() then
        if Config.Framework == "esx" then
            -- TODO: does esx not use citizenId? need to find what it uses
            return Core.GetPlayerFromId(source)
        elseif Config.Framework == "qb" then
            return Core.Functions.GetPlayer(source).PlayerData
        elseif Config.Framework == "qbx" then
            return exports.qbx_core:GetPlayer(source).PlayerData
        end
    else
        if Config.Framework == "esx" then
            return Core.GetPlayerData()
        elseif Config.Framework == "qb" then
            return Core.Functions.GetPlayerData()
        elseif Config.Framework == "qbx" then
            return exports.qbx_core:GetPlayerData()
        end
    end
end

--------------------- CLIENT FUNCTIONS ---------------------

-- Triggers a progressbar on the client
-- This uses the same method signature as QBCore Progressbar
--- @param name string The name of the progressbar
--- @param label string The label to show on the progressbar
--- @param duration number The duration of the progressbar
--- @param useWhileDead boolean Whether or not the progressbar should be used while dead
--- @param canCancel boolean Whether or not the progressbar can be cancelled
--- @param disableControls table Contains the controls to disable while the progressbar is active (disableMovement, disableCarMovement, disableMouse, disableCombat)
--- @param animation table Contains the animation to play while the progressbar is active (animDict, anim, flags)
--- @param prop table Contains the prop to show while the progressbar is active (model, bone, coords, rotation)
--- @param propTwo table Contains the prop to show while the progressbar is active (model, bone, coords, rotation)
--- @param onFinish function The callback function to call when the progressbar finishes
--- @param onCancel function The callback function to call when the progressbar is cancelled
function Progressbar(name, label, duration, useWhileDead, canCancel, disableControls, animation, prop, propTwo, onFinish, onCancel)
    if IsDuplicityVersion() then return end
    if Config.ProgessBar == "none" then
        onFinish()
    elseif Config.ProgessBar == "qb" then
        return Core.Functions.Progressbar(name, label, duration, useWhileDead, canCancel, disableControls, animation, prop, propTwo, onFinish, onCancel)
    elseif Config.ProgessBar == "ox" then
        local props = {}
        if prop then
            props[1] = {
                model = prop.model,
                bone = prop.bone,
                coords = prop.coords,
                rotation = prop.rotation,
            }
        end
        if propTwo then
            props[2] = {
                model = propTwo.model,
                bone = propTwo.bone,
                coords = propTwo.coords,
                rotation = propTwo.rotation,
            }
        end

        if lib.progressBar({
                name = name,
                label = label,
                duration = duration,
                useWhileDead = useWhileDead,
                canCancel = canCancel,
                disable = {
                    move = disableControls.disableMovement,
                    car = disableControls.disableCarMovement,
                    mouse = disableControls.disableMouse,
                    combat = disableControls.disableCombat,
                },
                anim = {
                    dict = animation.animDict,
                    clip = animation.anim,
                    flag = animation.flags,
                },
                prop = props,
            }) then
            onFinish()
        else
            onCancel()
        end
    else
        warn("Invalid Config.ProgessBar: <" .. tostring(Config.ProgessBar) .. ">. Update Progressbar in framework.lua.")
    end
end

-- Takes a prop model and the targetOptions and adds it to the target script so that this prop model is targetable
--- @param model string The name of the prop model to target
--- @param targetOptions table The options to pass to the target script
---        ex: targetOptions = { distance = num, options: { offset = { x = 0.0, y = 0.0, z = 0.0 }, rotation = { x = 0.0, y = 0.0, z = 0.0 } , animationDict = "", animationName = "" } }
function AddTargetModel(modelName, targetOptions)
    if IsDuplicityVersion() then return end
    if Config.Target == "qb" then
        exports["qb-target"]:AddTargetModel(modelName, targetOptions)
    elseif Config.Target == "ox" then
        -- ox target expects each option to have a distance on it,
        -- Append the distance value to each option
        for _, option in pairs(targetOptions.options) do
            option.distance = targetOptions.distance
        end
        exports.ox_target:addModel(modelName, targetOptions.options)
    else
        warn("Invalid Config.Target: <" .. tostring(Config.Target) .. ">. Update AddTargetModel in framework.lua.")
    end
end

-- Creates a log entry when an item is placed or picked up
--- @param itemName string The name of the item that was placed or picked up
--- @param isItemPlaced boolean True if the item was placed, false if the item was picked up
function CreateLog(itemName, isItemPlaced)
    if IsDuplicityVersion() or Config.Log == "none" then return end

    local playerId = PlayerId()
    local playerName = GetPlayerName(playerId)
    local PlayerData = GetPlayerData()
    local color = isItemPlaced and "red" or "green"
    local action = isItemPlaced and "Placed" or "Picked up"
    local logMessage = "**" .. playerName .. "** (" .. PlayerData.charinfo.firstname .. " " .. PlayerData.charinfo.lastname .. ") | CitizenId: " .. PlayerData.citizenid .. "\n Item Name: " .. itemName

    if Config.Log == "none" then
        return
    elseif Config.Log == "qb" then
        -- Be sure to add the 'itemplacement' entry to the qb-log config
        TriggerServerEvent("qb-log:server:CreateLog", "itemplacement", "Item " .. action .. " By:", color, logMessage)
    else
        warn("Invalid Config.Log: <" .. tostring(Config.Log) .. ">. Update CreateLog in framework.lua.")
    end
end

--------------------- SERVER FUNCTIONS ---------------------

-- Registers a useable item
--- @param itemName string The name of the item to register
--- @param callbackFn function The function to call when the item is used
function CreateUseableItem(itemName, callbackFn)
    if not IsDuplicityVersion() then return end
    if Config.Framework == "esx" then
        -- ESX returns the itemName as the second parameter, itemdata as the third parameter when calling the callback function
        -- We are interested in the itemData for our callback
        local function ESXCallback(source, itemName, itemData)
            callbackFn(source, itemData)
        end

        return Core.RegisterUsableItem(itemName, ESXCallback)
    elseif Config.Framework == "qb" then
        return Core.Functions.CreateUseableItem(itemName, callbackFn)
    elseif Config.Framework == "qbx" then
        return exports.qbx_core:CreateUseableItem(itemName, callbackFn)
    else
        warn("Invalid Config.Framework: <" .. tostring(Config.Framework) .. ">. Update CreateUseableItem in framework.lua.")
    end
end

-- Adds item to the players inventory
--- @param source number The source of the player
--- @param itemName string The name of the item to add
--- @param amount number The amount of the item to add
--- @param info table Metadata to add to the item
function AddItem(source, itemName, amount, info)
    if not IsDuplicityVersion() then return end
    if Config.Inventory == "esx" then
        local xPlayer = Core.GetPlayerFromId(source)
        return xPlayer.addInventoryItem(itemName, amount)
    elseif Config.Inventory == "qb" then
        local Player = Core.Functions.GetPlayer(source)
        TriggerClientEvent("inventory:client:ItemBox", source, Core.Shared.Items[itemName], "add")
        return Player.Functions.AddItem(itemName, amount, nil, info)
    elseif Config.Inventory == "ox" then
        exports.ox_inventory:AddItem(source, itemName, amount, info)
    else
        warn("Invalid Config.Inventory: <" .. tostring(Config.Inventory) .. ">. Update AddItem in framework.lua.")
    end
end

-- Removes item from the players inventory
--- @param source number The source of the player
--- @param itemName string The name of the item to remove
--- @param amount number The number of items to remove
--- @param slot number The slot of the item to remove from
function RemoveItem(source, itemName, amount, slot)
    if not IsDuplicityVersion() then return end
    if Config.Inventory == "esx" then
        local xPlayer = Core.GetPlayerFromId(source)
        return xPlayer.removeInventoryItem(itemName, amount)
    elseif Config.Inventory == "qb" then
        local Player = Core.Functions.GetPlayer(source)
        TriggerClientEvent("inventory:client:ItemBox", source, Core.Shared.Items[itemName], "remove")
        return Player.Functions.RemoveItem(itemName, amount, slot)
    elseif Config.Inventory == "ox" then
        exports.ox_inventory:RemoveItem(source, itemName, amount, nil, slot)
    else
        warn("Invalid Config.Inventory: <" .. tostring(Config.Inventory) .. ">. Update RemoveItem in framework.lua.")
    end
end

--- This function checks if any OX scripts are being used in the configuration
--- and throws an error if `ox_lib` is not properly enabled in the `fxmanifest.lua`.
--- 
--- **Usage**: Call this function on the server inside an `onResourceStart` event handler.
--- 
--- **Example**:
--- ```lua
--- AddEventHandler("onResourceStart", function(resourceName)
---     if GetCurrentResourceName() == resourceName then
---         Wait(100) -- Give the script some time to start
---         ValidateOxLibUsage()
---     end
--- end)
--- ```
function ValidateOxLibUsage()
    if not IsDuplicityVersion() then return end

    local isUsingOxScripts =
        Config.Notify == "ox"
        or Config.Target == "ox"
        or Config.Inventory == "ox"
        or Config.ProgessBar == "ox"

    -- Ensure ox_lib is not commented out in the fxmanifest/shared_script section if any OX scripts are used in the Config.
    -- If ox_lib is commented out, display an error as the script will not function correctly.
    if isUsingOxScripts then
        local filePath = GetResourcePath(GetCurrentResourceName()) .. "/fxmanifest.lua"
        local file, _errorMsg = io.open(filePath, "r")
        if not file then return end

        -- Read through the fxmanifest file
        -- Find the line with "@ox_lib/init.lua" and check if it is commented out
        local isOxLibCommentedOut = false
        for line in file:lines() do
            if line:find("@ox_lib/init.lua") then
                -- Check if the line is commented out
                if line:match("^%s*%-%-") then
                    isOxLibCommentedOut = true
                end
                break
            end
        end

        file:close()

        if isOxLibCommentedOut then
            error(
                "\n=====================================\n\n" ..

                "YOU ARE USING OX SCRIPTS AND DID NOT UNCOMMENT OX_LIB IN THE FXMANIFEST!\n\n" ..

                "The script will not work until you uncomment it from the fxmanifest.\n\n" ..

                "=====================================\n"
            )
        end
    end
end