-- Prevent running if menu is disabled
if not TX_MENU_ENABLED then return end

-- =============================================
--  This file is for all vehicle-related menu features
-- =============================================

--[[ NUI CALLBACKS ]]

local function getPedVehicle()
    local ped = PlayerPedId()
    local veh
    if IS_REDM and IsPedOnMount(ped) then
        veh = GetMount(ped)
    else
        veh = GetVehiclePedIsIn(ped, false)
    end
    if veh and veh > 0 then
        return veh
    else
        return nil
    end
end

-- NOTE: this is not a complete list, but most others have the type "automobile"
local vehClassNamesEnum = {
    [8] = "bike",
    [11] = "trailer",
    [13] = "bike",
    [14] = "boat",
    [15] = "heli",
    [16] = "plane",
    [21] = "train",
}

-- Since we don't have the vehicle types on the server, we need this translation table
-- NOTE: this list was generated for game build 2802/mpchristmas3
-- How to update the list: https://gist.github.com/tabarra/32ef90524188093ab4218ee7b5121269
local mismatchedTypes = {
    ["airtug"] = "automobile",       -- trailer
    ["avisa"] = "submarine",         -- boat
    ["blimp"] = "heli",              -- plane
    ["blimp2"] = "heli",             -- plane
    ["blimp3"] = "heli",             -- plane
    ["caddy"] = "automobile",        -- trailer
    ["caddy2"] = "automobile",       -- trailer
    ["caddy3"] = "automobile",       -- trailer
    ["chimera"] = "automobile",      -- bike
    ["docktug"] = "automobile",      -- trailer
    ["forklift"] = "automobile",     -- trailer
    ["kosatka"] = "submarine",       -- boat
    ["mower"] = "automobile",        -- trailer
    ["policeb"] = "bike",            -- automobile
    ["ripley"] = "automobile",       -- trailer
    ["rrocket"] = "automobile",      -- bike
    ["sadler"] = "automobile",       -- trailer
    ["sadler2"] = "automobile",      -- trailer
    ["scrap"] = "automobile",        -- trailer
    ["slamtruck"] = "automobile",    -- trailer
    ["Stryder"] = "automobile",      -- bike
    ["submersible"] = "submarine",   -- boat
    ["submersible2"] = "submarine",  -- boat
    ["thruster"] = "heli",           -- automobile
    ["towtruck"] = "automobile",     -- trailer
    ["towtruck2"] = "automobile",    -- trailer
    ["tractor"] = "automobile",      -- trailer
    ["tractor2"] = "automobile",     -- trailer
    ["tractor3"] = "automobile",     -- trailer
    ["trailersmall2"] = "trailer",   -- automobile
    ["utillitruck"] = "automobile",  -- trailer
    ["utillitruck2"] = "automobile", -- trailer
    ["utillitruck3"] = "automobile", -- trailer
}

local function handleSpawnRequestFivem(model)
    if not IsModelAVehicle(model) then
        debugPrint("^1Model provided is not a vehicle: " .. model)
        return false
    end

    --Resolve vehicle type, required for server setter
    --NOTE: check if GetVehicleTypeFromName is already available
    local modelType
    if mismatchedTypes[model] then
        modelType = mismatchedTypes[model]
    else
        local modelClassNumber = GetVehicleClassFromName(model)
        modelType = vehClassNamesEnum[modelClassNumber] or "automobile"
    end

    --Request from server
    TriggerServerEvent('txsv:req:vehicle:spawn:fivem', model, modelType)
    return true
end

local function handleSpawnRequestRedm(model)
    --check if model is valid vehicle or horse (IsThisModelAHorse)
    if not IsModelAVehicle(model) and not Citizen.InvokeNative(0x772A1969F649E902, GetHashKey(model)) then
        debugPrint("^1Model provided is not a vehicle or horse: " .. model)
        return false
    end

    --request
    TriggerServerEvent('txsv:req:vehicle:spawn:redm', model)
    return true
end

local gameSpawnReqHandler = IS_FIVEM and handleSpawnRequestFivem or handleSpawnRequestRedm

RegisterSecureNuiCallback('spawnVehicle', function(data, cb)
    if type(data) ~= 'table' or type(data.model) ~= 'string' then
        error("Invalid spawnVehicle NUI callback data")
    end
    if not IsModelValid(data.model) then
        debugPrint("^1Invalid vehicle/horse model requested: " .. data.model)
        sendSnackbarMessage(
            'error',
            'nui_menu.page_main.vehicle.spawn.dialog_error',
            true,
            { modelName = data.model }
        )
    elseif not gameSpawnReqHandler(data.model) then
        sendSnackbarMessage(
            'error',
            'nui_menu.page_main.vehicle.spawn.dialog_error',
            true,
            { modelName = data.model }
        )
    else
        sendSnackbarMessage(
            'info',
            'nui_menu.page_main.vehicle.spawn.dialog_info',
            true,
            { modelName = data.model }
        )
    end
    cb({})
end)
RegisterCommand('txAdmin:menu:spawnVehicle', function()
    if not menuIsAccessible then return end
    if not DoesPlayerHavePerm(menuPermissions, 'menu.vehicle') then
        return sendSnackbarMessage('error', 'nui_menu.misc.no_perms', true)
    end
    toggleMenuVisibility(true)
    SetNuiFocus(true, true)
    sendMenuMessage('openSpawnVehicleDialog', {})
end)


local function reqVehicleDelete(_, cb)
    local veh = getPedVehicle()
    if veh == nil then
        return sendSnackbarMessage('error', 'nui_menu.page_main.vehicle.not_in_veh_error', true)
    end
    local vehNetId = NetworkGetNetworkIdFromEntity(veh)
    TriggerServerEvent("txsv:req:vehicle:delete", vehNetId)
    if cb then cb({ success = true }) end
end
RegisterSecureNuiCallback('deleteVehicle', reqVehicleDelete)
RegisterCommand('txAdmin:menu:deleteVehicle', function()
    if not menuIsAccessible then return end
    if not DoesPlayerHavePerm(menuPermissions, 'menu.vehicle') then
        return sendSnackbarMessage('error', 'nui_menu.misc.no_perms', true)
    end
    reqVehicleDelete()
end)


local function reqVehicleFix(_, cb)
    local veh = getPedVehicle()
    if veh == nil then
        return sendSnackbarMessage('error', 'nui_menu.page_main.vehicle.not_in_veh_error', true)
    end
    TriggerServerEvent('txsv:req:vehicle:fix')
    if cb then cb({}) end
end
RegisterSecureNuiCallback('fixVehicle', reqVehicleFix)
RegisterCommand('txAdmin:menu:fixVehicle', function()
    if not menuIsAccessible then return end
    if not DoesPlayerHavePerm(menuPermissions, 'menu.vehicle') then
        return sendSnackbarMessage('error', 'nui_menu.misc.no_perms', true)
    end
    reqVehicleFix()
end)


local function reqVehicleBoost(_, cb)
    local veh = getPedVehicle()
    if veh == nil then
        return sendSnackbarMessage('error', 'nui_menu.page_main.vehicle.not_in_veh_error', true)
    end
    TriggerServerEvent('txsv:req:vehicle:boost')
    if cb then cb({}) end
end
RegisterSecureNuiCallback('boostVehicle', reqVehicleBoost)
RegisterCommand('txAdmin:menu:boostVehicle', function()
    if not menuIsAccessible then return end
    if not DoesPlayerHavePerm(menuPermissions, 'menu.vehicle') then
        return sendSnackbarMessage('error', 'nui_menu.misc.no_perms', true)
    end
    reqVehicleBoost()
end)


--[[ EVENT HANDLERS + FUNCTION LOGIC ]]

local function setVehicleHandlingValue(veh, field, newValue)
    -- local currValue = GetVehicleHandlingFloat(veh, 'CHandlingData', field)
    SetVehicleHandlingField(veh, 'CHandlingData', field, newValue * 1.0)
end
local function setVehicleHandlingModifier(veh, field, multiplier)
    local currValue = GetVehicleHandlingFloat(veh, 'CHandlingData', field)
    local newValue = (multiplier * 1.0) * currValue;
    SetVehicleHandlingField(veh, 'CHandlingData', field, newValue)
end

local boostableVehicleClasses = {
    [0] = 'Compacts',
    [1] = 'Sedans',
    [2] = 'SUVs',
    [3] = 'Coupes',
    [4] = 'Muscle',
    [5] = 'Sports Classics',
    [6] = 'Sports',
    [7] = 'Super',
    -- [8]='Motorcycles',
    [9] = 'Off-road',
    -- [10]='Industrial',
    [11] = 'Utility',
    [12] = 'Vans',
    -- [13]='Cycles',
    -- [14]='Boats',
    -- [15]='Helicopters',
    -- [16]='Planes',
    [17] = 'Service',
    [18] = 'Emergency',
    [19] = 'Military',
    [20] = 'Commercial',
    -- [21]='Trains',
    [22] = 'Open Wheel'
}

local function boostVehicleFivem()
    local ped = PlayerPedId()
    local veh = GetVehiclePedIsIn(ped, false)

    --Check if in vehicle
    if not veh or veh <= 0 then
        return sendSnackbarMessage('error', 'nui_menu.page_main.vehicle.not_in_veh_error', true)
    end

    --Check if vehicle already boosted
    --NOTE: state bags were too complicated, and checking for specific float didn't work due to precision
    local boostedFlag = GetVehicleHandlingFloat(veh, 'CHandlingData', 'fInitialDriveMaxFlatVel')
    if boostedFlag == 300.401214599609375 then
        return sendSnackbarMessage('error', 'nui_menu.page_main.vehicle.boost.already_boosted', true)
    end

    --Check if vehicle is in fact a car
    local vehClass = GetVehicleClass(veh)
    if not boostableVehicleClasses[vehClass] then
        return sendSnackbarMessage('error', 'nui_menu.page_main.vehicle.boost.unsupported_class', true)
    end

    --Modify car
    setVehicleHandlingValue(veh, 'fInitialDriveMaxFlatVel', 300.40120); --the signature, don't change
    setVehicleHandlingValue(veh, 'fHandBrakeForce', 10.0);
    setVehicleHandlingValue(veh, 'fBrakeForce', 20.0);
    setVehicleHandlingModifier(veh, 'fTractionCurveMin', 2.1);
    setVehicleHandlingModifier(veh, 'fTractionCurveMax', 2.5);
    setVehicleHandlingModifier(veh, 'fInitialDriveForce', 2.0); --accelerates real fast, almost no side effects
    setVehicleHandlingModifier(veh, 'fDriveInertia', 1.25);
    setVehicleHandlingValue(veh, 'fInitialDragCoeff', 10.0);

    SetVehicleHandlingVector(veh, 'CHandlingData', 'vecInertiaMultiplier', vector3(0.1, 0.1, 0.1))
    setVehicleHandlingValue(veh, 'fAntiRollBarForce', 0.0001);   --testar, o certo é 0~1
    setVehicleHandlingValue(veh, 'fTractionLossMult', 0.00001);  --testar, o certo é >1
    setVehicleHandlingValue(veh, 'fRollCentreHeightFront', 0.5); --testar, o certo é 0~1
    setVehicleHandlingValue(veh, 'fRollCentreHeightRear', 0.5);  --testar, o certo é 0~1

    playLibrarySound('confirm')
    SetVehicleCanBreak(veh, false)         -- If this is set to false, the vehicle simply can't break
    SetVehicleEngineCanDegrade(veh, false) -- Engine strong
    SetVehicleMod(veh, 15, 3, false)       -- Max Suspension
    SetVehicleMod(veh, 11, 3, false)       -- Max Engine
    SetVehicleMod(veh, 16, 4, false)       -- Max Armor
    SetVehicleMod(veh, 12, 2, false)       -- Max Brakes
    SetVehicleMod(veh, 13, 2, false)       -- Max Transmission
    ToggleVehicleMod(veh, 18, true)        -- modTurbo
    SetVehicleMod(veh, 18, 0, false)       -- Turbo
    SetVehicleNitroEnabled(veh, true)      -- Gives the vehicle a nitro boost
    SetVehicleTurboPressure(veh, 100.0)    -- Pressure of the turbo is 100%
    EnableVehicleExhaustPops(veh, true)    -- This forces the exhaust to always "pop"
    SetVehicleCheatPowerIncrease(veh, 1.8) -- Torque multiplier

    sendSnackbarMessage('success', 'nui_menu.page_main.vehicle.boost.success', true)
end

local function boostVehicleRedm()
    local ped = PlayerPedId()
    local horse = IsPedOnMount(ped) and GetMount(ped) or false
    if not horse then
        return sendSnackbarMessage('error', 'nui_menu.page_main.vehicle.boost.redm_not_mounted', true)
    end

    local boostedFlag = Citizen.InvokeNative(0x200373A8DF081F22, horse, 0)
    if boostedFlag then
        return sendSnackbarMessage('error', 'nui_menu.page_main.vehicle.boost.already_boosted', true)
    end

    local duration = 4000.0
    -- Inner/Outter Health
    Citizen.InvokeNative(0x4AF5A4C7B9157D14, horse, 0, duration, true) --EnableAttributeCoreOverpower
    Citizen.InvokeNative(0xF6A7C08DF2E28B28, horse, 0, duration, true) --EnableAttributeOverpower
    -- Inner/Outter Stamina
    Citizen.InvokeNative(0x4AF5A4C7B9157D14, horse, 1, duration, true) --EnableAttributeCoreOverpower
    Citizen.InvokeNative(0xF6A7C08DF2E28B28, horse, 1, duration, true) --EnableAttributeOverpower

    AnimpostfxPlay('PlayerOverpower')
    sendSnackbarMessage('success', 'nui_menu.page_main.vehicle.boost.success', true)
end

local boostVehicleFunc = IS_FIVEM and boostVehicleFivem or boostVehicleRedm
RegisterNetEvent('txcl:vehicle:boost', boostVehicleFunc)

-- Fix vehicle
RegisterNetEvent('txcl:vehicle:fix', function()
    local ped = PlayerPedId()
    local veh = GetVehiclePedIsIn(ped, false)
    if veh and veh > 0 then
        SetVehicleUndriveable(veh, false)
        SetVehicleFixed(veh)
        SetVehicleEngineOn(veh, true, false)
        SetVehicleDirtLevel(veh, 0.0)
        SetVehicleOnGroundProperly(veh)
    elseif IS_REDM and IsPedOnMount(ped) then
        local horse = GetMount(ped)
        ResurrectPed(horse)
        SetEntityHealth(horse, GetEntityMaxHealth(horse))
        Citizen.InvokeNative(0xC6258F41D86676E0, horse, 0, 100) -- SetAttributeCoreValue
        Citizen.InvokeNative(0xC6258F41D86676E0, horse, 1, 100) -- SetAttributeCoreValue
        Citizen.InvokeNative(0xC6258F41D86676E0, horse, 2, 100) -- SetAttributeCoreValue
    end
    sendSnackbarMessage('success', 'nui_menu.page_main.vehicle.fix.success', true)
end)

-- Spawn vehicle - used in redm
RegisterNetEvent('txcl:vehicle:spawn:redm', function(model)
    if not IS_REDM then return end
    if type(model) ~= 'string' then return end

    -- check model
    local modelHash = GetHashKey(model)
    local isVehicle = IsModelAVehicle(model)
    local isHorse = Citizen.InvokeNative(0x772A1969F649E902, modelHash) --IsThisModelAHorse
    if not isVehicle and not isHorse then
        return debugPrint("^1Model provided is not a vehicle or horse: " .. model)
    end

    -- get player data
    local playerPed = PlayerPedId()
    local playerCoords = GetEntityCoords(playerPed)
    local playerHeading = GetEntityHeading(playerPed)
    local currentVeh = GetVehiclePedIsIn(playerPed, false)
    if IsPedOnMount(playerPed) then
        currentVeh = GetMount(playerPed)
    end
    local currentVehVelocity
    if currentVeh then
        currentVehVelocity = GetEntityVelocity(currentVeh)
        DeleteEntity(currentVeh)
    end

    -- request new model
    RequestModel(modelHash)
    while not HasModelLoaded(modelHash) do
        Wait(15)
    end

    -- spawn it
    local newVeh
    if isVehicle then
        newVeh = CreateVehicle(modelHash, playerCoords, playerHeading, true, false, false)
        SetPedIntoVehicle(playerPed, newVeh, -1)
        SetVehicleOnGroundProperly(newVeh)
    else
        newVeh = CreatePed(modelHash, playerCoords, playerHeading, true, false)
        -- Citizen.InvokeNative(0x77FF8D35EEC6BBC4, newVeh, 1, 0) --EquipMetaPedOutfitPreset
        Citizen.InvokeNative(0x283978A15512B2FE, newVeh, true)          --SetRandomOutfitVariation
        Citizen.InvokeNative(0x028F76B6E78246EB, playerPed, newVeh, -1) --SetPedOntoMount
    end

    -- preserving speed, doesn't work well for horses
    if currentVehVelocity then
        SetEntityVelocity(newVeh, currentVehVelocity)
    end
    SetModelAsNoLongerNeeded(modelHash)
    sendSnackbarMessage('success', 'nui_menu.page_main.vehicle.spawn.dialog_success', true)
end)


-- Spawn vehicles, with support for entity lockdown - used in fivem
RegisterNetEvent('txcl:seatInVehicle', function(vehNetID, seat, oldVehVelocity)
    if type(vehNetID) ~= 'number' then return end
    if type(seat) ~= 'number' then return end

    local attemptsCounter = 0
    local attemptsLimit = 400 -- 400*5 = 2s
    while not NetworkDoesEntityExistWithNetworkId(vehNetID) and attemptsCounter < attemptsLimit do
        Wait(5)
    end
    if not NetworkDoesEntityExistWithNetworkId(vehNetID) then
        return sendSnackbarMessage('error', 'Failed to seat into vehicle (net=' .. vehNetID .. ')', false)
    end

    local veh = NetToVeh(vehNetID)
    if veh and veh > 0 then
        SetPedIntoVehicle(PlayerPedId(), veh, seat)
        if seat == -1 then
            SetVehicleEngineOn(veh, true, true, false)
            SetVehicleOnGroundProperly(veh)
            if type(oldVehVelocity) ~= 'vector3' then
                SetEntityVelocity(veh, oldVehVelocity)
            end
            sendSnackbarMessage('success', 'nui_menu.page_main.vehicle.spawn.dialog_success', true)
        end
    end
end)
