Skip to main content
return {

    -- ⚠️ WARNING: When you are working with this script, never do "restart lation_chopshop"
    -- ⚠️ This will cause issues, data loss & more! You must restart the script like this:
    -- ⚠️ "stop lation_chopshop" ..wait a couple seconds.. then "ensure lation_chopshop"

    ----------------------------------------------
    --        🛠️ Setup the basics below
    ----------------------------------------------

    setup = {
        -- Use only if needed, directed by support or know what you're doing
        -- Notice: enabling debug features will significantly increase resmon
        -- And should always be disabled in production
        debug = false,
        -- Set your interaction system below
        -- Available options are: 'ox_target', 'qb-target', 'interact' & 'custom'
        -- 'custom' needs to be added to client/functions.lua
        interact = 'ox_target',
        -- Set your notification system below
        -- Available options are: 'lation_ui', 'ox_lib', 'esx', 'qb', 'okok', 'sd-notify', 'wasabi_notify' & 'custom'
        -- 'custom' needs to be added to client/functions.lua
        notify = 'ox_lib',
        -- Set your progress bar system below
        -- Available options are: 'lation_ui', 'ox_lib', 'qbcore' & 'custom'
        -- 'custom' needs to be added to client/functions.lua
        -- Any custom progress bar must also support animations
        progress = 'ox_lib',
        -- Set your minigame (skillcheck) system below
        -- Available options are: 'lation_ui', 'ox_lib' & 'custom'
        minigame = 'ox_lib',
        -- Do you want to use Lith Studios Bolt Minigame?
        -- This is a free, interactive minigame for removing wheels
        -- Learn more here: https://lith.store/package/6174416
        ls_bolt_minigame = false,
        -- Set your context menu system below
        -- Available options are: 'lation_ui', 'ox_lib' & 'custom'
        menu = 'ox_lib',
        -- Set your alert & input dialog system below
        -- Available options are: 'lation_ui', 'ox_lib' & 'custom'
        dialogs = 'ox_lib',
        -- Do you want to hide player names in the group menu?
        -- If true, names will instead be replaced with their Player IDs
        -- If false it will display their character names as normal
        hideNames = false,
        -- Do you want to be notified via server console if an update is available?
        -- True if yes, false if no
        version = true,
        -- Input all your police jobs below
        police = { 'police', 'sheriff' }
    },

    ----------------------------------------------
    --       📍 Activity start settings
    ----------------------------------------------

    start = {
        -- Where to spawn the main ped to start chopping
        -- If you wish to disable the starting ped, set coords = false
        coords = vec4(-169.0171, -1352.3877, 29.9817, 91.8764),
        -- The ped model used
        -- More models: https://docs.fivem.net/docs/game-references/ped-models/
        model = 'a_m_m_bevhills_01',
        -- The scenario assigned to the ped (or scenario = false for no scenario)
        -- More scenarios: https://github.com/DioneB/gtav-scenarios
        scenario = 'WORLD_HUMAN_CLIPBOARD',
        -- You can limit the hours at which the ped is available here
        -- By default, this ped is available 24/7
        -- Min is the earliest the ped is available (in 24hr format)
        -- Max is the latest the ped is available (in 24hr format)
        -- For example, if you want the ped only available during daytime set min = 6 & max = 21
        hours = { min = 0, max = 24 },
        -- How many police must be online in order to start a chop job?
        police = 0,
        -- How long (in seconds) until a vehicle is assigned after requesting a job
        -- Set min & max to 0 to disable the cooldown and instantly assign a vehicle
        cooldown = { min = 5, max = 25 },
        -- When a chop job is completed, do you want to display the "Continue Chopping?"
        -- Dialog to the group owner? True if yes, false if no
        continue = true,
        -- Easy mode is an optional mode that highlights matching vehicle models nearby
        -- Radius is the distance from the player to search for & highlight vehicles
        -- 424 is the maximum "focus zone" with FiveM OneSync anything higher than this will not work
        easyMode = { enable = false, radius = 424 },
        -- This option will REMOVE the ability to start the chopping activity from the main menu
        -- This is useful if you want to use your own custom method to start the chop job
        -- Do not set this option to true if you do not plan to implement a custom method
        exportOnly = false,
        -- Do you want to allow players to chop owned vehicles?
        -- If true, players can chop all vehicles, including owned vehicles
        -- If false, players can only chop unowned vehicles
        allowOwned = true,
        -- ⛔ DANGER: THIS IS A DESTRUCTIVE SETTING
        -- ⛔ DANGER: deleteOwned is only used if allowOwned = true above
        -- ⛔ DANGER: This will delete (permanently remove) an owned vehicle when chopped
        -- ℹ️ INFO: Deleted vehicles are stored in the "deleted_vehicles.sql" file
        -- ℹ️ INFO: This is merely a backup safety measure used for restoration if needed
        deleteOwned = false
    },

    ----------------------------------------------
    --       📈 Customize the XP system
    ----------------------------------------------

    experience = {
        -- The number in these [brackets] are the level
        -- The number after = is the exp required to reach that level
        -- Be sure levels *always* start at level 1 with 0 exp
        [1] = 0,
        [2] = 5000,
        [3] = 10000,
        [4] = 25000,
        [5] = 75000,
        -- You can add or remove levels as you wish
    },

    ----------------------------------------------
    --       🚗 Customize chop vehicles
    ----------------------------------------------

    vehicles = {
        "alpha", "asea", "baller", "banshee", "bjxl", "buccaneer", "bullet", "carbonizzare", "cavalcade2", "coquette",
        "dubsta", "dukes", "emperor", "exemplar", "f620", "felon", "felon2", "furoregt", "futo", "glendale",
        "huntley", "ingot", "intruder", "jackal", "jester", "manana", "massacro", "ninef", "patriot", "peyote", "phoenix",
        "picador", "premier", "primo", "radi", "rapidgt", "rapidgt2", "regina", "rhapsody", "rocoto", "sabregt", "schafter2",
        "schwarzer", "sentinel", "stanier", "stratum", "sultan", "superd", "surano", "tornado", "vigero", "voltic"
    },

    ----------------------------------------------
    --        🔨 Customize chopping
    ----------------------------------------------

    chopping = {
        wheels = {
            [0] = { -- Front left tire
                difficulty = { 'easy', 'easy', 'easy', 'easy' },
                inputs = { 'E' },
                duration = 10000
            },
            [1] = { -- Front right tire
                difficulty = { 'easy', 'easy', 'easy', 'easy' },
                inputs = { 'E' },
                duration = 10000
            },
            [2] = { -- Rear left tire
                difficulty = { 'easy', 'easy', 'easy', 'easy' },
                inputs = { 'E' },
                duration = 10000
            },
            [3] = { -- Rear right tire
                difficulty = { 'easy', 'easy', 'easy', 'easy' },
                inputs = { 'E' },
                duration = 10000
            }
        },
        doors = {
            [0] = { -- Front driver door
                difficulty = { 'easy', 'easy', 'easy', 'easy', 'easy' },
                inputs = { 'E' },
                duration = 12500
            },
            [1] = { -- Front passenger door
                difficulty = { 'easy', 'easy', 'easy', 'easy', 'easy' },
                inputs = { 'E' },
                duration = 12500
            },
            [2] = { -- Rear driver door
                difficulty = { 'easy', 'easy', 'easy', 'easy', 'easy' },
                inputs = { 'E' },
                duration = 12500
            },
            [3] = { -- Rear passenger door
                difficulty = { 'easy', 'easy', 'easy', 'easy', 'easy' },
                inputs = { 'E' },
                duration = 12500
            },
            [4] = { -- Hood
                difficulty = { 'easy', 'easy', 'easy', 'easy', 'easy' },
                inputs = { 'E' },
                duration = 15000
            },
            [5] = { -- Trunk
                difficulty = { 'easy', 'easy', 'easy', 'easy', 'easy' },
                inputs = { 'E' },
                duration = 15000
            },
        },
        frame = { -- The frame/chassis
            difficulty = { 'easy', 'easy', 'easy', 'easy', 'easy', 'easy' },
            inputs = { 'E' },
            duration = 25000
        }
    },

    ----------------------------------------------
    --       💰 Customize chop rewards
    ----------------------------------------------

    rewards = {
        -- The number in these [brackets] are the player level
        [1] = {
            -- The min/max amount of "ls_auto_parts" rewarded for each part chopped
            wheels = { min = 1, max = 3 },
            doors = { min = 2, max = 4 },
            frame = { min = 5, max = 10 },
            -- The min/max amount of XP rewarded when completing a chop job
            xp = { min = 25, max = 35 },
            -- The percentage at which chopping parts duration is reduced for this level
            speed = 0
        },
        [2] = {
            wheels = { min = 2, max = 4 },
            doors = { min = 3, max = 5 },
            frame = { min = 6, max = 12 },
            xp = { min = 35, max = 50 },
            speed = 15
        },
        [3] = {
            wheels = { min = 3, max = 5 },
            doors = { min = 4, max = 6 },
            frame = { min = 7, max = 14 },
            xp = { min = 50, max = 75 },
            speed = 30
        },
        [4] = {
            wheels = { min = 4, max = 6 },
            doors = { min = 5, max = 7 },
            frame = { min = 8, max = 16 },
            xp = { min = 75, max = 100 },
            speed = 45
        },
        [5] = {
            wheels = { min = 5, max = 7 },
            doors = { min = 6, max = 8 },
            frame = { min = 9, max = 18 },
            xp = { min = 100, max = 150 },
            speed = 60
        }
    },

    ----------------------------------------------
    --          🗺️ Assign chop zones
    ----------------------------------------------

    zones = {
        vec3(1565.1211, -2161.1277, 77.5340),
        vec3(1134.0985, -793.7753, 57.5917),
        vec3(-84.7814, -2225.9697, 7.8117),
        vec3(-467.7876, -1678.4623, 19.0395),
        vec3(1596.7920, -1709.7660, 88.1285),
        vec3(833.9869, -1405.5132, 26.1511),
        vec3(970.2102, -1632.1747, 30.1107),
        vec3(248.3347, 380.5432, 105.5951),
        vec3(-69.7967, 83.2294, 71.5020),
        vec3(-1315.3870, -1257.1395, 4.5771),
        vec3(-443.0953, -2282.7065, 7.6081),
        vec3(-1597.3135, -1008.8568, 7.6894),
        -- You can add or remove locations as you wish
    },

    ----------------------------------------------
    --      🔨 Customize chop shop items
    ----------------------------------------------

    items = {
        -- The main item rewarded when a vehicle part is chopped
        auto_parts = 'ls_auto_parts',
        -- The torch item used to remove doors/frame from vehicle
        torch = {
            -- The item spawn name
            item = 'ls_torch',
            -- Do you want to require the player have a torch to chop?
            require = true,
            -- Should this item be removed on each use?
            remove = false,
            -- Percentage chance this item breaks on failed skillcheck
            -- Set chance to 0 to disable break chance
            break_chance = 20
        },
        lug_wrench = {
            -- The item spawn name
            item = 'ls_lug_wrench',
            -- Do you want to require the player have a lug wrench to chop?
            require = true,
            -- Should this item be removed on each use?
            remove = false,
            -- Percentage chance this item breaks on failed skillcheck
            -- Set chance to 0 to disable break chance
            break_chance = 20
        },
        vehicle_finder = {
            -- The item spawn name
            item = 'ls_vehicle_finder',
            -- Should this item be removed on each use?
            remove = false,
            -- How far the detector searches for vehicles
            -- 424 is the maximum "focus zone" with FiveM OneSync
            -- Anything higher than this will not work
            radius = 424,
            -- How long (in seconds) the vehicle finder lasts
            duration = 60,
            -- Customize blip related settings
            blips = {
                -- The blip sprite ID
                sprite = 225,
                -- The blip color ID
                color = 1,
                -- The blip scale
                scale = 0.8,
                -- The blip name
                name = 'Discovered Vehicle',
            }
        }
    },

    ----------------------------------------------
    --          🛒 Customize shops
    ----------------------------------------------

    shops = {
        -- ⚠️ The shops below are added to the main chop shop menu when enabled!
        -- The swap shop is a shop where players can exchange their ls_auto_parts
        -- For whatever items you wish, such as materials, illegal items, etc
        swap = {
            -- Optionally disable this shop if you wish
            enable = true,
            -- This shop specifically only accepts an item as payment
            -- You cannot use traditional methods of cash/bank/etc
            account = 'ls_auto_parts',
            -- Items available for swapping in this shop
            items = {
                -- item: item spawn name
                -- price: price of item in ls_auto_parts
                -- quantity: amount of item given for price
                -- icon: icon for item
                -- level: optional level requirement to buy item
                -- metadata: optional metadata for item
                [1] = { item = 'plastic', price = 1, quantity = 1, icon = 'recycle' },
                [2] = { item = 'aluminium', price = 1, quantity = 1, icon = 'recycle' },
                [3] = { item = 'copper', price = 1, quantity = 1, icon = 'recycle' },
                -- Add or remove items as you wish
            }
        },
        tool = {
            -- Optionally disable this shop if you wish
            enable = true,
            -- Use cash or bank when purchasing here?
            account = 'cash',
            -- Items available for sale in this shop
            items = {
                -- item: item spawn name
                -- price: price of item
                -- icon: icon for item
                -- level: optional level requirement to buy item
                -- metadata: optional metadata for item
                [1] = { item = 'ls_torch', price = 1250, icon = 'fire-flame-curved' },
                [2] = { item = 'ls_lug_wrench', price = 750, icon = 'wrench' },
                [3] = { item = 'ls_vehicle_finder', price = 4500, icon = 'satellite-dish' },
                -- Add or remove items as you wish
            }
        }
    },

}
-- Initialize global variables to store framework & inventory
Framework, Inventory = nil, nil

-- Initialize global player variables
PlayerLoaded, PlayerData = false, {}

-- Get framework
local function InitializeFramework()
    if GetResourceState('es_extended') == 'started' then
        ESX = exports['es_extended']:getSharedObject()
        Framework = 'esx'

        RegisterNetEvent('esx:playerLoaded', function(xPlayer)
            PlayerData = xPlayer
            PlayerLoaded = true
            TriggerEvent('lation_chopshop:playerLoaded')
        end)

        RegisterNetEvent('esx:onPlayerLogout', function()
            table.wipe(PlayerData)
            PlayerLoaded = false
            TriggerEvent('lation_chopshop:playerDropped')
        end)

        AddEventHandler('onResourceStart', function(resourceName)
            if GetCurrentResourceName() ~= resourceName then return end
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_chopshop:playerLoaded')
        end)

    elseif GetResourceState('qbx_core') == 'started' then
        Framework = 'qbx'

        AddEventHandler('QBCore:Client:OnPlayerLoaded', function()
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_chopshop:playerLoaded')
        end)

        RegisterNetEvent('qbx_core:client:playerLoggedOut', function()
            table.wipe(PlayerData)
            PlayerLoaded = false
            TriggerEvent('lation_chopshop:playerDropped')
        end)

        AddEventHandler('onResourceStart', function(resourceName)
            if GetCurrentResourceName() ~= resourceName then return end
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_chopshop:playerLoaded')
        end)
    elseif GetResourceState('qb-core') == 'started' then
        QBCore = exports['qb-core']:GetCoreObject()
        Framework = 'qb'

        AddEventHandler('QBCore:Client:OnPlayerLoaded', function()
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_chopshop:playerLoaded')
        end)

        RegisterNetEvent('QBCore:Client:OnPlayerUnload', function()
            table.wipe(PlayerData)
            PlayerLoaded = false
            TriggerEvent('lation_chopshop:playerDropped')
        end)

        AddEventHandler('onResourceStart', function(resourceName)
            if GetCurrentResourceName() ~= resourceName then return end
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_chopshop:playerLoaded')
        end)
    elseif GetResourceState('ox_core') == 'started' then
        Ox = require '@ox_core.lib.init'
        Framework = 'ox'

        AddEventHandler('ox:playerLoaded', function()
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_chopshop:playerLoaded')
        end)

        AddEventHandler('ox:playerLogout', function()
            table.wipe(PlayerData)
            PlayerLoaded = false
            TriggerEvent('lation_chopshop:playerDropped')
        end)

        AddEventHandler('onResourceStart', function(resourceName)
            if GetCurrentResourceName() ~= resourceName then return end
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_chopshop:playerLoaded')
        end)
    else
        -- Add custom framework here
    end
end

-- Get inventory
local function InitializeInventory()
    if GetResourceState('ox_inventory') == 'started' then
        Inventory = 'ox_inventory'
    elseif GetResourceState('qb-inventory') == 'started' then
        Inventory = 'qb-inventory'
    elseif GetResourceState('qs-inventory') == 'started' then
        Inventory = 'qs-inventory'
    elseif GetResourceState('ps-inventory') == 'started' then
        Inventory = 'ps-inventory'
    elseif GetResourceState('origen_inventory') == 'started' then
        Inventory = 'origen_inventory'
    elseif GetResourceState('codem-inventory') == 'started' then
        Inventory = 'codem-inventory'
    elseif GetResourceState('core_inventory') == 'started' then
        Inventory = 'core_inventory'
    else
        -- Add custom inventory here
    end
end

-- Returns player data
function GetPlayerData()
    if Framework == 'esx' then
        return ESX.GetPlayerData()
    elseif Framework == 'qb' then
        return QBCore.Functions.GetPlayerData()
    elseif Framework == 'qbx' then
        return exports.qbx_core:GetPlayerData()
    elseif Framework == 'ox' then
        return Ox.GetPlayer()
    else
        -- Add custom framework here
    end
end

-- Returns player job
function GetJob()
    local player = GetPlayerData()
    if not player then return end
    if Framework == 'esx' then
        return player?.job?.name
    elseif Framework == 'qb' then
        return player.job.name
    elseif Framework == 'qbx' then
        return player.job.name
    elseif Framework == 'ox' then
        return player.getGroupByType('job')
    else
        -- Add custom framework here
    end
    return 'unemployed'
end

-- Return data for item
--- @param item string
function GetItemData(item)
    if not item then return end
    if Inventory then
        if Inventory == 'ox_inventory' then
            return exports[Inventory]:Items(item)
        elseif Inventory == 'qb-inventory' or Inventory == 'ps-inventory' then
            return QBCore.Shared.Items[item]
        elseif Inventory == 'qs-inventory' then
            local items = exports[Inventory]:GetItemList()
            if not items then return end
            return items[item]
        elseif Inventory == 'origen_inventory' then
            local items = exports[Inventory]:GetItems()
            if not items then return end
            return items[item]
        elseif Inventory == 'codem-inventory' then
            local items = exports[Inventory]:GetItemList()
            if not items then return end
            return items[item]
        elseif Inventory == 'core_inventory' then
            -- No available client-side export to get item list
            if Framework == 'qb' then
                return QBCore.Shared.Items[item]
            else
                print('^1[ERROR]: An issue has occured, please contact support at: https://discord.gg/9EbY4nM5uu^0')
            end
        else
            -- Add custom inventory here
        end
    else
        if Framework == 'esx' then
            -- Unlikely to need anything here but.. just in case..
            print('^1[ERROR]: An error has occured with lation_chopshop - please contact support^0')
        elseif Framework == 'qb' then
            return QBCore.Shared.Items[item]
        elseif Framework == 'qbx' then
            -- Unlikely to need anything here but.. just in case..
            print('^1[ERROR]: Are you really not using ox_inventory? Contact support please lul.^0')
        end
    end
end

-- Returns boolean if player has specified amount of item
--- @param item string
--- @param amount number
--- @return boolean
function HasItem(item, amount)
    if not item or not amount then return false end
    if Inventory then
        if Inventory == 'ox_inventory' then
            return exports[Inventory]:Search('count', item) >= amount
        elseif Inventory == 'core_inventory' then
            return exports[Inventory]:hasItem(item, amount)
        else
            return exports[Inventory]:HasItem(item, amount)
        end
    else
        local playerData = GetPlayerData()
        if not playerData then return false end
        local inventory = Framework == 'esx' and playerData.inventory or playerData.items
        if not inventory then return false end
        for _, itemData in pairs(inventory) do
            if itemData and itemData.name == item then
                local count = itemData.amount or itemData.count or 0
                if count >= amount then
                    return true
                end
            end
        end
        return false
    end
end

-- Disables access to open/view inventory
function DisableInventory()
    if Inventory == 'ox_inventory' then
        LocalPlayer.state.invBusy = true
    elseif Inventory == 'qb-inventory' then
        LocalPlayer.state.inv_busy = true
    elseif Inventory == 'qs-inventory' then
        exports[Inventory]:setInventoryDisabled(true)
    elseif Inventory == 'core_inventory' then
        exports[Inventory]:lockInventory()
    else
        -- Add custom inventory here
    end
end

-- Enables access to open/view inventory
function EnableInventory()
    if Inventory == 'ox_inventory' then
        LocalPlayer.state.invBusy = false
    elseif Inventory == 'qb-inventory' then
        LocalPlayer.state.inv_busy = false
    elseif Inventory == 'qs-inventory' then
        exports[Inventory]:setInventoryDisabled(false)
    elseif Inventory == 'core_inventory' then
        exports[Inventory]:unlockInventory()
    else
        -- Add custom inventory here
    end
end

-- Initialize defaults
InitializeFramework()
InitializeInventory()
-- Initialize global variable(s) to store phone
Phone = nil

-- Initialize config(s)
local shared = require 'config.shared'

-- Localize export
local chopshop = exports.lation_chopshop

-- You can change the textUI script here
-- Options: 'lation_ui', 'ox_lib', 'jg-textui', 'okokTextUI', 'qbcore' & 'custom'
local textui = 'ox_lib'

-- Check if ls_bolt_minigame is installed/started
local ls_bolt_minigame = GetResourceState('ls_bolt_minigame') == 'started'

-- Get phone
local function InitializePhone()
    if GetResourceState('lb-phone') == 'started' then
        Phone = 'lb-phone'
    elseif GetResourceState('qb-phone') == 'started' then
        Phone = 'qb-phone'
    elseif GetResourceState('qs-smartphone-pro') == 'started' then
        Phone = 'qs-smartphone-pro'
    elseif GetResourceState('qs-smartphone') == 'started' then
        Phone = 'qs-smartphone'
    elseif GetResourceState('gksphone') == 'started' then
        Phone = 'gksphone'
    elseif GetResourceState('roadphone') == 'started' then
        Phone = 'roadphone'
    elseif GetResourceState('npwd') == 'started' then
        Phone = 'npwd'
    elseif GetResourceState('yseries') == 'started' then
        Phone = 'yseries'
    elseif GetResourceState('okokPhone') == 'started' then
        Phone = 'okokPhone'
    else
        -- Add custom phone here
    end
end

-- Display a notification
--- @param message string
--- @param type any
function ShowNotification(message, type)
    if shared.setup.notify == 'lation_ui' then
        exports.lation_ui:notify({ title = 'Chop Shop', message = message, type = type or 'info', icon = 'fas fa-car' })
    elseif shared.setup.notify == 'ox_lib' then
        lib.notify({ description = message, type = type or 'inform', position = 'top', icon = 'fas fa-car' })
    elseif shared.setup.notify == 'esx' then
        ESX.ShowNotification(message)
    elseif shared.setup.notify == 'qb' then
        QBCore.Functions.Notify(message, type or 'primary')
    elseif shared.setup.notify == 'okok' then
        exports['okokNotify']:Alert('Chop Shop', message, 5000, type or 'info', false)
    elseif shared.setup.notify == 'sd-notify' then
        exports['sd-notify']:Notify('Chop Shop', message, type or 'primary')
    elseif shared.setup.notify == 'wasabi_notify' then
        exports.wasabi_notify:notify('Chop Shop', message, 5000, type or 'info', false, 'fas fa-car')
    elseif shared.setup.notify == 'custom' then
        -- Add custom notification export/event here
    end
end

-- Display notifications from server
--- @param message string
--- @param type string
RegisterNetEvent('lation_chopshop:notify', function(message, type)
    ShowNotification(message, type)
end)

-- Return true or false if player can start chopping
--- @param model string Model name
--- @param entity number Entity ID
--- @return boolean
function CanStartChopping(model, entity)
    if not model or not entity then return false end

    if not shared.start.allowOwned then
        local plate = GetVehicleNumberPlateText(entity)
        if not plate or plate == '' then return false end

        local isOwned = lib.callback.await('lation_chopshop:isOwnedVehicle', false, plate)

        if isOwned then
            ShowNotification(locale('notify.owned-vehicle'), 'error')
            return false
        end
    end

    return true
end

-- Send email
--- @param message string
local function SendEmail(message)
    if not message then return end
    if Phone == 'qs-smartphone' then
        TriggerServerEvent('qs-smartphone:server:sendNewMail', {
            sender = 'Salvage Specialist',
            subject = 'Chop Shop',
            message = message
        })
    elseif Phone == 'roadphone' then
        exports[Phone]:sendMail({
            sender = 'Salvage Specialist',
            subject = 'Chop Shop',
            message = message
        })
    else
        lib.alertDialog({
            header = 'Chop Shop',
            content = message,
            centered = true,
            cancel = true
        })
    end
end

-- Display a progress bar
--- @param data table
function ProgressBar(data)
    if shared.setup.progress == 'lation_ui' then
        if exports.lation_ui:progressBar({
            label = data.label,
            description = data.description or nil,
            icon = data.icon or nil,
            duration = data.duration,
            useWhileDead = data.useWhileDead,
            canCancel = data.canCancel,
            steps = data.steps or nil,
            disable = data.disable,
            anim = {
                dict = data.anim.dict or nil,
                clip = data.anim.clip or nil,
                flag = data.anim.flag or nil
            },
            prop = {
                model = data.prop.model or nil,
                bone = data.prop.bone or nil,
                pos = data.prop.pos or nil,
                rot = data.prop.rot or nil
            }
        }) then
            return true
        end
        return false
    elseif shared.setup.progress == 'ox_lib' then
        -- Want to use ox_lib's progress circle instead of bar?
        -- Change "progressBar" to "progressCircle" below & done!
        if lib.progressBar({
            label = data.label,
            duration = data.duration,
            position = data.position or 'bottom',
            useWhileDead = data.useWhileDead,
            allowSwimming = data.allowSwimming or false,
            canCancel = data.canCancel,
            disable = data.disable,
            anim = {
                dict = data.anim.dict or nil,
                clip = data.anim.clip or nil,
                flag = data.anim.flag or nil
            },
            prop = {
                model = data.prop.model or nil,
                bone = data.prop.bone or nil,
                pos = data.prop.pos or nil,
                rot = data.prop.rot or nil
            }
        }) then
            return true
        end
        return false
    elseif shared.setup.progress == 'qbcore' then
        local p = promise.new()
        QBCore.Functions.Progressbar(data.label, data.label, data.duration, data.useWhileDead, data.canCancel, {
            disableMovement = data.disable.move,
            disableCarMovement = data.disable.car,
            disableMouse = false,
            disableCombat = data.disable.combat
        }, {
            animDict = data.anim.dict or nil,
            anim = data.anim.clip or nil,
            flags = data.anim.flag or nil
        }, {
            model = data.prop.model or nil,
            bone = data.prop.bone or nil,
            coords = data.prop.pos or nil,
            rotation = data.prop.rot or nil
        }, {},
        function()
            ClearPedTasks(cache.ped)
            p:resolve(true)
        end,
        function()
            ClearPedTasks(cache.ped)
            p:resolve(false)
        end)
        return Citizen.Await(p)
    else
        -- Add 'custom' progress bar here
    end
end

-- Register menu
--- @param data table
function RegisterMenu(data)
    if shared.setup.menu == 'lation_ui' then
        exports.lation_ui:registerMenu(data)
    elseif shared.setup.menu == 'ox_lib' then
        lib.registerContext(data)
    elseif shared.setup.menu == 'custom' then
        -- Add 'custom' menu system here
    end
end

-- Show menu
--- @param menuId string
function ShowMenu(menuId)
    if shared.setup.menu == 'lation_ui' then
        exports.lation_ui:showMenu(menuId)
    elseif shared.setup.menu == 'ox_lib' then
        lib.showContext(menuId)
    elseif shared.setup.menu == 'custom' then
        -- Add 'custom' menu system here
    end
end

-- Display an alert dialog
--- @param data table
function ShowAlert(data)
    if shared.setup.dialogs == 'lation_ui' then
        return exports.lation_ui:alert(data)
    elseif shared.setup.dialogs == 'ox_lib' then
        return lib.alertDialog(data)
    elseif shared.setup.dialogs == 'custom' then
        -- Add your custom alert dialog here
    end
end

-- Display an input dialog
--- @param data table
function ShowInput(data)
    if shared.setup.dialogs == 'lation_ui' then
        return exports.lation_ui:input({ title = data.title, options = data.options })
    elseif shared.setup.dialogs == 'ox_lib' then
        return lib.inputDialog(data.title, data.options)
    elseif shared.setup.dialogss == 'custom' then
        -- Add your custom input dialog here
    end
end

-- Display TextUI
--- @param text string 
--- @param icon string
function ShowTextUI(text, icon)
    if textui == 'lation_ui' then
        exports.lation_ui:showText({
            description = text,
            icon = icon,
            iconAnimation = 'beat'
        })
    elseif textui == 'ox_lib' then
        lib.showTextUI(text, {
            position = 'left-center',
            icon = icon,
            iconAnimation = 'beat'
        })
    elseif textui == 'jg-textui' then
        exports['jg-textui']:DrawText(text)
    elseif textui == 'okokTextUI' then
        exports['okokTextUI']:Open(text, 'lightblue ', 'left', false)
    elseif textui == 'qbcore' then
        exports['qb-core']:DrawText(text, 'left')
    else
        -- Add custom textUI here
    end
end

-- Hide TextUI
--- @param label string
function HideTextUI(label)
    if textui == 'lation_ui' then
        local isOpen, text = exports.lation_ui:isOpen()
        if isOpen and text == label then
            exports.lation_ui:hideText()
        end
    elseif textui == 'ox_lib' then
        local isOpen, text = lib.isTextUIOpen()
        if isOpen and text == label then
            lib.hideTextUI()
        end
    elseif textui == 'jg-textui' then
        exports['jg-textui']:HideText()
    elseif textui == 'okokTextUI' then
        exports['okokTextUI']:Close()
    elseif textui == 'qbcore' then
        exports['qb-core']:HideText()
    else
        -- Add custom textUI here
    end
end

-- Display skillcheck
--- @param data table .difficulty, .inputs
function ShowSkillcheck(data)
    if shared.setup.minigame == 'lation_ui' then
        if exports.lation_ui:skillCheck('Chopping', data.difficulty, data.inputs) then
            return true
        end
        return false
    elseif shared.setup.minigame == 'ox_lib' then
        if lib.skillCheck(data.difficulty, data.inputs) then
            return true
        end
        return false
    elseif shared.setup.minigame == 'custom' then
        -- Add custom minigame here
    end
    return false
end

-- Show Lith Studios Bolt Minigame
--- @param entity number Entity number
--- @param wheelId number Wheel index
--- @param mount boolean Mount wheel
--- @param goToWheel boolean Go to wheel
--- @param coords any
function ShowLsBoltMinigame(entity, wheelId, mount, goToWheel, coords)
    if shared.setup.ls_bolt_minigame and not ls_bolt_minigame then
        print('^1[ERROR]: ls_bolt_minigame resource is not installed (or started correctly)^0')
        print('^1[ERROR]: You either have not installed it or the start order is incorrect^0')
        print('^1[ERROR]: If installed, ensure the resource is started BEFORE lation_chopshop^0')
        print('^4[INFO]: Don\'t have ls_bolt_minigame yet? Get it here for free: https://lith.store/package/6174416^0')
        return false
    end

    return exports['ls_bolt_minigame']:BoltMinigame(entity, wheelId, mount, goToWheel, coords)
end

-- Add entity target
--- @param entity number Entity number
--- @param data table Options table
function AddTargetEntity(entity, data)
    if shared.setup.interact == 'ox_target' then
        exports.ox_target:addLocalEntity(entity, data)
    elseif shared.setup.interact == 'qb-target' then
        exports['qb-target']:AddTargetEntity(entity, {options = data, distance = 1.75})
    elseif shared.setup.interact == 'interact' then
        exports.interact:AddLocalEntityInteraction({
            entity = entity,
            interactDst = 1.75,
            offset = vec3(0.0, 0.0, 1.0),
            options = data
        })
    elseif shared.setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('^1[ERROR]: No interaction system was detected - please visit config/shared "setup" section^0')
    end
end

-- Add target to model
--- @param model string Model
--- @param data table Options table
function AddTargetModel(model, data)
    if shared.setup.interact == 'ox_target' then
        exports.ox_target:addModel(model, data)
    elseif shared.setup.interact == 'qb-target' then
        exports['qb-target']:AddTargetModel(model, {options = data, distance = 1.75})
    elseif shared.setup.interact == 'interact' then
        exports.interact:AddModelInteraction({
            model = model,
            offset = vec3(0.0, 0.0, 0.0),
            id = model,
            interactDst = 1.75,
            options = data
        })
    elseif shared.setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('^1[ERROR]: No interaction system was detected - please visit config/shared "setup" section^0')
    end
end

-- Add target to entity bone
--- @param entity number
--- @param bone string
--- @param data table
function AddTargetBone(entity, bone, data)
    if shared.setup.interact == 'ox_target' then
        exports.ox_target:addLocalEntity(entity, data)
    elseif shared.setup.interact == 'qb-target' then
        exports['qb-target']:AddTargetBone(bone, {options = data, distance = 1.75})
    elseif shared.setup.interact == 'interact' then
        exports.interact:AddLocalEntityInteraction({
            entity = entity,
            interactDst = 1.75,
            bone = bone,
            options = data
        })
    elseif shared.setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('^1[ERROR]: No interaction system was detected - please visit config/shared "setup" section^0')
    end
end

-- Remove target from entity
--- @param entity any|number
--- @param data table|string
function RemoveTargetEntity(entity, data)
    if shared.setup.interact == 'ox_target' then
        exports.ox_target:removeLocalEntity(entity, data)
    elseif shared.setup.interact == 'qb-target' then
        exports['qb-target']:RemoveTargetEntity(entity, data)
    elseif shared.setup.interact == 'interact' then
        exports.interact:RemoveLocalEntityInteraction(entity, data)
    elseif shared.setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('^1[ERROR]: No interaction system was detected - please visit config/shared "setup" section^0')
    end
end

-- Remove target model
--- @param model string Model
function RemoveTargetModel(model)
    if shared.setup.interact == 'ox_target' then
        exports.ox_target:removeModel(model, nil)
    elseif shared.setup.interact == 'qb-target' then
        exports['qb-target']:RemoveTargetModel(model, nil)
    elseif shared.setup.interact == 'interact' then
        exports.interact:RemoveModelInteraction(model, model)
    elseif shared.setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('^1[ERROR]: No interaction system was detected - please visit config/shared "setup" section^0')
    end
end

-- Remove target from entity bone
--- @param entity any|number
--- @param bone string
--- @param data table|string
function RemoveTargetBone(entity, bone, data)
    if shared.setup.interact == 'ox_target' then
        exports.ox_target:removeLocalEntity(entity, data)
    elseif shared.setup.interact == 'qb-target' then
        exports['qb-target']:RemoveTargetBone(bone, data)
    elseif shared.setup.interact == 'interact' then
        exports.interact:RemoveLocalEntityInteraction(entity, data)
    elseif shared.setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('^1[ERROR]: No interaction system was detected - please visit config/shared "setup" section^0')
    end
end

-- Empty function triggered when "Start Chopping" is selected
--- @param entity number Entity ID
--- @param model string Model name
function StartedChopping(entity, model)
    -- print(('Chopping has started on entity/model: %s/%s'):format(entity, model))
end

-- Assign waypoint to random zone upon vehicle entry
--- @param value number
--- @param _ any
lib.onCache('vehicle', function(value, _)
    if not value then return end

    local model = modelHashes[GetEntityModel(value)]
    if not model then return end

    local assignedModel = chopshop:getAssignedModel()
    if not assignedModel then return end

    if model ~= assignedModel then return end

    local zone = shared.zones[math.random(#shared.zones)]
    if not zone then return end

    SetNewWaypoint(zone.x, zone.y)
    TriggerServerEvent('lation_chopshop:foundModel')
end)

-- Function to spawn NPCs
--- @param model string
--- @param position vector4
function SpawnPed(model, position)
    lib.requestModel(model)
    local ped = CreatePed(0, model, position.x, position.y, position.z - 1.0, position.w, false, true)
    FreezeEntityPosition(ped, true)
    SetBlockingOfNonTemporaryEvents(ped, true)
    SetEntityInvincible(ped, true)
    return ped
end

-- Print debug message
--- @param message string
function Debug(message)
    if not shared.setup.debug then return end
    print(('^2[DEBUG]:^0 %s'):format(message))
end

-- Register net event(s)
RegisterNetEvent('lation_chopshop:sendEmail', SendEmail)

-- Initialize default(s)
InitializePhone()