local Action = {
    name = '',
    duration = 0,
    label = '',
    useWhileDead = false,
    canCancel = true,
    disarm = true,
    controlDisables = {
        disableMovement = false,
        disableCarMovement = false,
        disableMouse = false,
        disableCombat = false,
    },
    animation = {
        animDict = nil,
        anim = nil,
        flags = 0,
        task = nil,
    },
    prop = {
        model = nil,
        bone = nil,
        coords = vec3(0.0, 0.0, 0.0),
        rotation = vec3(0.0, 0.0, 0.0),
    },
    propTwo = {
        model = nil,
        bone = nil,
        coords = vec3(0.0, 0.0, 0.0),
        rotation = vec3(0.0, 0.0, 0.0),
    },
}

local isDoingAction = false
local wasCancelled = false
local prop_net = nil
local propTwo_net = nil
local isAnim = false
local isProp = false
local isPropTwo = false

local controls = {
    disableMouse = { 1, 2, 106 },
    disableMovement = { 30, 31, 36, 21, 75 },
    disableCarMovement = { 63, 64, 71, 72 },
    disableCombat = { 24, 25, 37, 47, 58, 140, 141, 142, 143, 263, 264, 257 }
}

-- Functions

local function loadAnimDict(dict)
    RequestAnimDict(dict)
    while not HasAnimDictLoaded(dict) do
        Wait(5)
    end
end

local function loadModel(model)
    RequestModel(model)
    while not HasModelLoaded(model) do
        Wait(5)
    end
end

local function createAndAttachProp(prop, ped)
    loadModel(prop.model)
    local coords = GetOffsetFromEntityInWorldCoords(ped, 0.0, 0.0, 0.0)
    local propEntity = CreateObject(GetHashKey(prop.model), coords.x, coords.y, coords.z, true, true, true)
    local netId = ObjToNet(propEntity)
    SetNetworkIdExistsOnAllMachines(netId, true)
    NetworkUseHighPrecisionBlending(netId, true)
    SetNetworkIdCanMigrate(netId, false)
    local boneIndex = GetPedBoneIndex(ped, prop.bone or 60309)
    AttachEntityToEntity(
        propEntity, ped, boneIndex,
        prop.coords.x, prop.coords.y, prop.coords.z,
        prop.rotation.x, prop.rotation.y, prop.rotation.z,
        true, true, false, true, 0, true
    )
    return netId
end

local function disableControls()
    CreateThread(function()
        while isDoingAction do
            for disableType, isEnabled in pairs(Action.controlDisables) do
                if isEnabled and controls[disableType] then
                    for _, control in ipairs(controls[disableType]) do
                        DisableControlAction(0, control, true)
                    end
                end
            end
            if Action.controlDisables.disableCombat then
                DisablePlayerFiring(PlayerId(), true)
            end
            Wait(0)
        end
    end)
end

local function StartActions()
    local ped = PlayerPedId()
    if isDoingAction then
        if not isAnim and Action.animation then
            if Action.animation.task then
                TaskStartScenarioInPlace(ped, Action.animation.task, 0, true)
            else
                local anim = Action.animation
                if anim.animDict and anim.anim and DoesEntityExist(ped) and not IsEntityDead(ped) then
                    loadAnimDict(anim.animDict)
                    TaskPlayAnim(ped, anim.animDict, anim.anim, 3.0, 3.0, -1, anim.flags or 1, 0, false, false, false)
                end
            end
            isAnim = true
        end
        if not isProp and Action.prop and Action.prop.model then
            prop_net = createAndAttachProp(Action.prop, ped)
            isProp = true
        end
        if not isPropTwo and Action.propTwo and Action.propTwo.model then
            propTwo_net = createAndAttachProp(Action.propTwo, ped)
            isPropTwo = true
        end
        disableControls()
    end
end

local function StartProgress(action, onStart, onTick, onFinish)
    local playerPed = PlayerPedId()
    local isPlayerDead = IsEntityDead(playerPed)
    if (not isPlayerDead or action.useWhileDead) and not isDoingAction then
        isDoingAction = true
        LocalPlayer.state:set('inv_busy', true, true)
        Action = action
        SendNUIMessage({
            action = 'progress',
            duration = action.duration,
            label = action.label
        })
        StartActions()
        CreateThread(function()
            if onStart then onStart() end
            while isDoingAction do
                Wait(1)
                if onTick then onTick() end
                if IsControlJustPressed(0, 200) and action.canCancel then
                    TriggerEvent('progressbar:client:cancel')
                    wasCancelled = true
                    break
                end
                if IsEntityDead(playerPed) and not action.useWhileDead then
                    TriggerEvent('progressbar:client:cancel')
                    wasCancelled = true
                    break
                end
            end
            if onFinish then onFinish(wasCancelled) end
            isDoingAction = false
        end)
    end
end

local function ActionCleanup()
    local ped = PlayerPedId()
    if Action.animation then
        if Action.animation.task or (Action.animation.animDict and Action.animation.anim) then
            StopAnimTask(ped, Action.animation.animDict, Action.animation.anim, 1.0)
            ClearPedSecondaryTask(ped)
        else
            ClearPedTasks(ped)
        end
    end
    if prop_net then
        DetachEntity(NetToObj(prop_net), true, true)
        DeleteObject(NetToObj(prop_net))
    end
    if propTwo_net then
        DetachEntity(NetToObj(propTwo_net), true, true)
        DeleteObject(NetToObj(propTwo_net))
    end
    prop_net = nil
    propTwo_net = nil
    isDoingAction = false
    wasCancelled = false
    isAnim = false
    isProp = false
    isPropTwo = false
    LocalPlayer.state:set('inv_busy', false, true)
end

-- Events

RegisterNetEvent('progressbar:client:ToggleBusyness', function(bool)
    isDoingAction = bool
end)

RegisterNetEvent('progressbar:client:progress', function(action, finish)
    StartProgress(action, nil, nil, finish)
end)

RegisterNetEvent('progressbar:client:ProgressWithStartEvent', function(action, start, finish)
    StartProgress(action, start, nil, finish)
end)

RegisterNetEvent('progressbar:client:ProgressWithTickEvent', function(action, tick, finish)
    StartProgress(action, nil, tick, finish)
end)

RegisterNetEvent('progressbar:client:ProgressWithStartAndTick', function(action, start, tick, finish)
    StartProgress(action, start, tick, finish)
end)

RegisterNetEvent('progressbar:client:cancel', function()
    ActionCleanup()
    SendNUIMessage({
        action = 'cancel'
    })
end)

-- NUI Callback

RegisterNUICallback('FinishAction', function(data, cb)
    ActionCleanup()
    cb('ok')
end)

-- Exports

local function Progress(action, finish)
    StartProgress(action, nil, nil, finish)
end
exports('Progress', Progress)

local function ProgressWithStartEvent(action, start, finish)
    StartProgress(action, start, nil, finish)
end
exports('ProgressWithStartEvent', ProgressWithStartEvent)

local function ProgressWithTickEvent(action, tick, finish)
    StartProgress(action, nil, tick, finish)
end
exports('ProgressWithTickEvent', ProgressWithTickEvent)

local function ProgressWithStartAndTick(action, start, tick, finish)
    StartProgress(action, start, tick, finish)
end
exports('ProgressWithStartAndTick', ProgressWithStartAndTick)

local function isDoingSomething()
    return isDoingAction
end
exports('isDoingSomething', isDoingSomething)