if not TowBarMod then TowBarMod = {} end
if not TowBarMod.Hook then TowBarMod.Hook = {} end

---------------------------------------------------------------------------
--- Tow bar functions
---------------------------------------------------------------------------

local TowBarTowMass = 200
local CannotDriveWhileTowedFallbackText = "Cannot drive while being towed"
local CannotDriveMessageCooldownHours = 1 / 1800 -- 2 seconds
local ForcedTowBarReapplyCooldownHours = 1 / 3600 -- 1 second
TowBarMod.Hook.reappliedVehicleIds = TowBarMod.Hook.reappliedVehicleIds or {}
TowBarMod.Hook.pendingReapplyVehicleIds = TowBarMod.Hook.pendingReapplyVehicleIds or {}
TowBarMod.Hook.reapplyTickCounter = TowBarMod.Hook.reapplyTickCounter or 0
TowBarMod.Hook.lastCannotDriveMessageAtByPlayer = TowBarMod.Hook.lastCannotDriveMessageAtByPlayer or {}
TowBarMod.Hook.lastForcedReapplyAtByVehicle = TowBarMod.Hook.lastForcedReapplyAtByVehicle or {}
TowBarMod.Hook.lastTowBarVehicleIdByPlayer = TowBarMod.Hook.lastTowBarVehicleIdByPlayer or {}

function TowBarMod.Hook.isTowedByTowBar(vehicle)
    if not vehicle then return false end

    local modData = vehicle:getModData()
    if not modData or not modData["isTowingByTowBar"] or not modData["towed"] then
        return false
    end

    local towingVehicle = vehicle:getVehicleTowedBy()
    if not towingVehicle then return false end

    local towingModData = towingVehicle:getModData()
    return towingModData and towingModData["isTowingByTowBar"] == true
end

function TowBarMod.Hook.getCannotDriveWhileTowedText()
    local msg = getText("UI_Text_Towing_cannotDriveWhileTowed")
    if not msg or msg == "UI_Text_Towing_cannotDriveWhileTowed" then
        return CannotDriveWhileTowedFallbackText
    end
    return msg
end

function TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
    if not playerObj then return end
    local playerNum = playerObj:getPlayerNum()
    local nowHours = getGameTime() and getGameTime():getWorldAgeHours() or nil
    local lastHours = TowBarMod.Hook.lastCannotDriveMessageAtByPlayer[playerNum]
    if nowHours and lastHours and (nowHours - lastHours) < CannotDriveMessageCooldownHours then
        return
    end
    TowBarMod.Hook.lastCannotDriveMessageAtByPlayer[playerNum] = nowHours or 0

    -- Match the overhead-style skill/feedback text.
    HaloTextHelper.addBadText(playerObj, TowBarMod.Hook.getCannotDriveWhileTowedText())
end

function TowBarMod.Hook.installStartEngineBlock()
    if not ISVehicleMenu or not ISVehicleMenu.onStartEngine then
        return
    end

    if TowBarMod.Hook._startEngineBlockInstalled then
        return
    end

    TowBarMod.Hook.defaultOnStartEngine = ISVehicleMenu.onStartEngine
    ISVehicleMenu.onStartEngine = function(playerObj)
        local vehicle = playerObj and playerObj:getVehicle() or nil
        if TowBarMod.Hook.isTowedByTowBar(vehicle) then
            TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
            return
        end

        TowBarMod.Hook.defaultOnStartEngine(playerObj)
    end

    TowBarMod.Hook._startEngineBlockInstalled = true
end

function TowBarMod.Hook.getVehicleByIdSafe(vehicleId)
    if getVehicleById then
        return getVehicleById(vehicleId)
    end

    local cell = getCell()
    if not cell then return nil end
    local vehicles = cell:getVehicles()
    if not vehicles then return nil end

    for i = 0, vehicles:size() - 1 do
        local vehicle = vehicles:get(i)
        if vehicle and vehicle:getId() == vehicleId then
            return vehicle
        end
    end
    return nil
end

function TowBarMod.Hook.shouldBlockDriverSeatForTowBar(playerObj, vehicle, seat)
    if not playerObj or not vehicle then return false end
    if seat ~= 0 then return false end
    return TowBarMod.Hook.isTowedByTowBar(vehicle)
end

function TowBarMod.Hook.hasPendingSeatSafetyAction(playerObj)
    if not playerObj or not ISTimedActionQueue then return false end

    return ISTimedActionQueue.hasActionType(playerObj, "ISSwitchVehicleSeat")
        or ISTimedActionQueue.hasActionType(playerObj, "ISExitVehicle")
        or ISTimedActionQueue.hasActionType(playerObj, "ISStopVehicle")
end

function TowBarMod.Hook.getBestSeatForDriverKick(playerObj, vehicle)
    if not playerObj or not vehicle then return nil end

    if ISVehicleMenu and ISVehicleMenu.getBestSwitchSeatExit then
        local best = ISVehicleMenu.getBestSwitchSeatExit(playerObj, vehicle, 0)
        if best and best > 0 and not vehicle:isSeatOccupied(best) and vehicle:canSwitchSeat(0, best) then
            return best
        end
    end

    for seat = 1, vehicle:getMaxPassengers() - 1 do
        if not vehicle:isSeatOccupied(seat) and vehicle:canSwitchSeat(0, seat) then
            return seat
        end
    end
    return nil
end

function TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
    if not playerObj or not vehicle then return false end
    if not TowBarMod.Hook.isTowedByTowBar(vehicle) then return false end
    if not vehicle:isDriver(playerObj) and vehicle:getSeat(playerObj) ~= 0 then return false end
    if TowBarMod.Hook.hasPendingSeatSafetyAction(playerObj) then return false end

    local seatTo = TowBarMod.Hook.getBestSeatForDriverKick(playerObj, vehicle)
    if seatTo then
        if ISVehicleMenu and ISVehicleMenu.onSwitchSeat then
            ISVehicleMenu.onSwitchSeat(playerObj, seatTo)
        elseif ISSwitchVehicleSeat then
            ISTimedActionQueue.add(ISSwitchVehicleSeat:new(playerObj, seatTo))
        end
        return true
    end

    if not vehicle:isStopped() then
        return false
    end

    if ISVehicleMenu and ISVehicleMenu.onExit then
        ISVehicleMenu.onExit(playerObj, 0)
        return true
    end

    if ISExitVehicle then
        ISTimedActionQueue.add(ISExitVehicle:new(playerObj))
        return true
    end

    return false
end

function TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
    if not TowBarMod.Hook.hasTowBarTowData(vehicle) then return false end

    local vehicleId = vehicle:getId()
    local nowHours = getGameTime() and getGameTime():getWorldAgeHours() or nil
    local lastHours = TowBarMod.Hook.lastForcedReapplyAtByVehicle[vehicleId]
    if nowHours and lastHours and (nowHours - lastHours) < ForcedTowBarReapplyCooldownHours then
        return false
    end
    TowBarMod.Hook.lastForcedReapplyAtByVehicle[vehicleId] = nowHours or 0

    TowBarMod.Hook.clearReapplied(vehicle)
    TowBarMod.Hook.queueTowBarReapply(vehicle)
    return TowBarMod.Hook.tryTowBarReapply(vehicle, playerObj)
end

function TowBarMod.Hook.installSeatEntryBlock()
    if not ISVehicleMenu or not ISVehicleMenu.onEnter or not ISVehicleMenu.onSwitchSeat then
        return
    end

    if TowBarMod.Hook._seatEntryBlockInstalled then
        return
    end

    TowBarMod.Hook.defaultOnEnter = ISVehicleMenu.onEnter
    TowBarMod.Hook.defaultOnEnter2 = ISVehicleMenu.onEnter2
    TowBarMod.Hook.defaultOnSwitchSeat = ISVehicleMenu.onSwitchSeat

    ISVehicleMenu.onEnter = function(playerObj, vehicle, seat)
        if TowBarMod.Hook.shouldBlockDriverSeatForTowBar(playerObj, vehicle, seat) then
            TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
            TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
            TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
            return
        end
        TowBarMod.Hook.defaultOnEnter(playerObj, vehicle, seat)
    end

    if TowBarMod.Hook.defaultOnEnter2 then
        ISVehicleMenu.onEnter2 = function(playerObj, vehicle, seat)
            if TowBarMod.Hook.shouldBlockDriverSeatForTowBar(playerObj, vehicle, seat) then
                TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
                TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
                TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
                return
            end
            TowBarMod.Hook.defaultOnEnter2(playerObj, vehicle, seat)
        end
    end

    ISVehicleMenu.onSwitchSeat = function(playerObj, seatTo)
        local vehicle = playerObj and playerObj:getVehicle() or nil
        if TowBarMod.Hook.shouldBlockDriverSeatForTowBar(playerObj, vehicle, seatTo) then
            TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
            TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
            TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
            return
        end
        TowBarMod.Hook.defaultOnSwitchSeat(playerObj, seatTo)
    end

    TowBarMod.Hook._seatEntryBlockInstalled = true
end

function TowBarMod.Hook.enforceTowedVehicleEngineOff(playerObj)
    if not playerObj or not instanceof(playerObj, "IsoPlayer") or not playerObj:isLocalPlayer() then return end

    local vehicle = playerObj:getVehicle()
    if not TowBarMod.Hook.isTowedByTowBar(vehicle) then return end

    if vehicle:isEngineRunning() or vehicle:isEngineStarted() or vehicle:isStarting() then
        vehicle:shutOff()
    end
end

function TowBarMod.Hook.enforceTowedVehicleSeatSafety(playerObj)
    if not playerObj or not instanceof(playerObj, "IsoPlayer") or not playerObj:isLocalPlayer() then return end

    local vehicle = playerObj:getVehicle()
    if not TowBarMod.Hook.isTowedByTowBar(vehicle) then return end

    TowBarMod.Hook.lastTowBarVehicleIdByPlayer[playerObj:getPlayerNum()] = vehicle:getId()

    if vehicle:isDriver(playerObj) or vehicle:getSeat(playerObj) == 0 then
        TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
        TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
        TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
    end
end

function TowBarMod.Hook.handleTowBarSeatEvent(character)
    if not character or not instanceof(character, "IsoPlayer") or not character:isLocalPlayer() then return end

    local playerObj = character
    local vehicle = playerObj:getVehicle()
    if not TowBarMod.Hook.isTowedByTowBar(vehicle) then return end

    TowBarMod.Hook.lastTowBarVehicleIdByPlayer[playerObj:getPlayerNum()] = vehicle:getId()
    TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
    TowBarMod.Hook.enforceTowedVehicleSeatSafety(playerObj)
end

function TowBarMod.Hook.OnEnterVehicle(character)
    TowBarMod.Hook.handleTowBarSeatEvent(character)
end

function TowBarMod.Hook.OnSwitchVehicleSeat(character)
    TowBarMod.Hook.handleTowBarSeatEvent(character)
end

function TowBarMod.Hook.OnExitVehicle(character)
    if not character or not instanceof(character, "IsoPlayer") or not character:isLocalPlayer() then return end

    local playerNum = character:getPlayerNum()
    local vehicleId = TowBarMod.Hook.lastTowBarVehicleIdByPlayer[playerNum]
    if not vehicleId then return end

    local vehicle = TowBarMod.Hook.getVehicleByIdSafe(vehicleId)
    if vehicle and TowBarMod.Hook.hasTowBarTowData(vehicle) then
        TowBarMod.Hook.forceTowBarReapply(vehicle, character)
    end
end

function TowBarMod.Hook.markReapplied(vehicle)
    if not vehicle then return end
    local vehicleId = vehicle:getId()
    TowBarMod.Hook.reappliedVehicleIds[vehicleId] = true
    TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = nil
end

function TowBarMod.Hook.clearReapplied(vehicle)
    if not vehicle then return end
    local vehicleId = vehicle:getId()
    TowBarMod.Hook.reappliedVehicleIds[vehicleId] = nil
    TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = nil
end

function TowBarMod.Hook.hasTowBarTowData(vehicle)
    if not vehicle then return false end
    local modData = vehicle:getModData()
    return modData and modData["isTowingByTowBar"] and modData["towed"]
end

function TowBarMod.Hook.isTowBarLinkActive(towingVehicle, towedVehicle)
    if not towingVehicle or not towedVehicle then return false end
    return towingVehicle:getVehicleTowing() == towedVehicle
        and towedVehicle:getVehicleTowedBy() == towingVehicle
end

function TowBarMod.Hook.queueTowBarReapply(vehicle)
    if not TowBarMod.Hook.hasTowBarTowData(vehicle) then return end
    local vehicleId = vehicle:getId()
    TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = true
end

function TowBarMod.Hook.tryTowBarReapply(vehicle, playerObj)
    if not TowBarMod.Hook.hasTowBarTowData(vehicle) then
        if vehicle then
            TowBarMod.Hook.pendingReapplyVehicleIds[vehicle:getId()] = nil
        end
        return false
    end

    local vehicleId = vehicle:getId()
    if TowBarMod.Hook.reappliedVehicleIds[vehicleId] then
        TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = nil
        return true
    end

    local towingVehicle = vehicle:getVehicleTowedBy()
    if not towingVehicle then return false end
    local towingModData = towingVehicle:getModData()
    if not towingModData or towingModData["isTowingByTowBar"] ~= true then return false end

    local modData = vehicle:getModData()
    if not modData.towBarOriginalScriptName then
        modData.towBarOriginalScriptName = vehicle:getScriptName()
        vehicle:transmitModData()
    end

    -- Save/load already restores a valid tow link. Do not stack another attach on top.
    if TowBarMod.Hook.isTowBarLinkActive(towingVehicle, vehicle) then
        TowBarMod.Hook.setVehiclePostAttach(playerObj, vehicle)
        TowBarMod.Hook.markReapplied(vehicle)
        return true
    end

    playerObj = playerObj or getPlayer()
    if not playerObj then return false end

    local attachmentA = towingVehicle:getTowAttachmentSelf() or "trailer"
    local attachmentB = vehicle:getTowAttachmentSelf() or "trailerfront"

    -- Re-run the same rigid tow offset + fake-trailer flow used during normal attach.
    TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, vehicle, attachmentA, attachmentB)
    vehicle:setScriptName("notTowingA_Trailer")

    local args = { vehicleA = towingVehicle:getId(), vehicleB = vehicle:getId(), attachmentA = attachmentA, attachmentB = attachmentB }
    sendClientCommand(playerObj, "vehicle", "attachTrailer", args)
    ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, vehicle))

    TowBarMod.Hook.markReapplied(vehicle)
    return true
end

function TowBarMod.Hook.reapplyTowBarPostLoad(vehicle)
    TowBarMod.Hook.queueTowBarReapply(vehicle)
    TowBarMod.Hook.tryTowBarReapply(vehicle, getPlayer())
end

function TowBarMod.Hook.processPendingReapplies()
    TowBarMod.Hook.reapplyTickCounter = TowBarMod.Hook.reapplyTickCounter + 1
    if TowBarMod.Hook.reapplyTickCounter < 15 then
        return
    end
    TowBarMod.Hook.reapplyTickCounter = 0

    local playerObj = getPlayer()
    if not playerObj then return end

    local resolvedVehicleIds = {}
    for vehicleId, _ in pairs(TowBarMod.Hook.pendingReapplyVehicleIds) do
        local vehicle = TowBarMod.Hook.getVehicleByIdSafe(vehicleId)
        if vehicle and (TowBarMod.Hook.tryTowBarReapply(vehicle, playerObj) or not TowBarMod.Hook.hasTowBarTowData(vehicle)) then
            table.insert(resolvedVehicleIds, vehicleId)
        end
    end

    for _, vehicleId in ipairs(resolvedVehicleIds) do
        TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = nil
    end
end

function TowBarMod.Hook.OnSpawnVehicle(vehicle)
    TowBarMod.Hook.reapplyTowBarPostLoad(vehicle)
end

function TowBarMod.Hook.performAttachTowBar(playerObj, towingVehicle, towedVehicle, attachmentA, attachmentB)
    if #(TowBarMod.Utils.getHookTypeVariants(towingVehicle, towedVehicle, true)) == 0 then return end

    local towBarItem = playerObj:getInventory():getItemFromType("TowBar.TowBar")
    if towBarItem ~= nil then
        sendClientCommand(playerObj, "towbar", "consumeTowBar", { itemId = towBarItem:getID() })
    end
    playerObj:setPrimaryHandItem(nil)

    TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)

    local towingModData = towingVehicle:getModData()
    local towedModData = towedVehicle:getModData()
    towedModData.towBarOriginalScriptName = towedVehicle:getScriptName()
    towedModData.towBarOriginalMass = towedVehicle:getMass()
    towedModData.towBarOriginalBrakingForce = towedVehicle:getBrakingForce()

    towingModData["isTowingByTowBar"] = true
    towedModData["isTowingByTowBar"] = true
    towedModData["towed"] = true
    towingVehicle:transmitModData()
    towedVehicle:transmitModData()

    local part = towedVehicle:getPartById("towbar")
    if part ~= nil then
        if towedVehicle:getScript():getModelScale() >= 1.5 and towedVehicle:getScript():getModelScale() <= 2 then
            local z = towedVehicle:getScript():getPhysicsChassisShape():z()/2 - 0.1
            part:setModelVisible("towbar" .. math.floor((z*2/3-1)*10), true)
        end
    end

    towedVehicle:doDamageOverlay()

    -- Fake a trailer script so the base "attachTrailer" command creates rigid point-constraint towing.
    towedVehicle:setScriptName("notTowingA_Trailer")
    local args = { vehicleA = towingVehicle:getId(), vehicleB = towedVehicle:getId(), attachmentA = attachmentA, attachmentB = attachmentB }
    sendClientCommand(playerObj, "vehicle", "attachTrailer", args)
    ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle))
    TowBarMod.Hook.markReapplied(towedVehicle)
end

function TowBarMod.Hook.setVehiclePostAttach(playerObj, towedVehicle)
    if not towedVehicle then return end

    local towedModData = towedVehicle:getModData()
    if towedModData.towBarOriginalScriptName then
        towedVehicle:setScriptName(towedModData.towBarOriginalScriptName)
    end

    towedVehicle:setMass(TowBarTowMass)
    towedVehicle:setBrakingForce(0)
    towedVehicle:constraintChanged()
    towedVehicle:updateTotalMass()
end

function TowBarMod.Hook.attachByTowBarAction(playerObj, towingVehicle, towedVehicle)
    if playerObj == nil or towingVehicle == nil or towedVehicle == nil then return end

    local item = playerObj:getInventory():getItemFromTypeRecurse("TowBar.TowBar")
    if item == nil then return end

    if #(TowBarMod.Utils.getHookTypeVariants(towingVehicle, towedVehicle, true)) == 0 then return end

    local hookPoint = towedVehicle:getAttachmentWorldPos("trailerfront", TowBarMod.Utils.tempVector1)
    if hookPoint == nil then return end
    ISTimedActionQueue.add(TowBarCustomPathFind:pathToLocationF(playerObj, hookPoint:x(), hookPoint:y(), hookPoint:z()))

    if not playerObj:getInventory():contains("TowBar.TowBar") then
        ISTimedActionQueue.add(ISInventoryTransferAction:new(playerObj, item, item:getContainer(), playerObj:getInventory(), nil))
    end

    local storePrim = playerObj:getPrimaryHandItem()
    if storePrim == nil or storePrim ~= item then
        ISTimedActionQueue.add(ISEquipWeaponAction:new(playerObj, item, 12, true))
    end

    ISTimedActionQueue.add(TowBarHookVehicle:new(playerObj, 300, TowBarMod.Config.lowLevelAnimation))

    hookPoint = towingVehicle:getAttachmentWorldPos("trailer", TowBarMod.Utils.tempVector1)
    if hookPoint == nil then return end
    ISTimedActionQueue.add(TowBarCustomPathFind:pathToLocationF(playerObj, hookPoint:x(), hookPoint:y(), hookPoint:z()))

    ISTimedActionQueue.add(TowBarHookVehicle:new(
        playerObj,
        100,
        TowBarMod.Config.lowLevelAnimation,
        TowBarMod.Hook.performAttachTowBar,
        towingVehicle,
        towedVehicle,
        "trailer",
        "trailerfront"
    ))
end

function TowBarMod.Hook.performDeattachTowBar(playerObj, towingVehicle, towedVehicle)
    TowBarMod.Utils.updateAttachmentsOnDefaultValues(towingVehicle, towedVehicle)

    -- Detach from the towing side to mirror vanilla trailer detach behavior.
    local args = { vehicle = towingVehicle:getId() }
    sendClientCommand(playerObj, "vehicle", "detachTrailer", args)

    local towedModData = towedVehicle:getModData()
    if towedModData.towBarOriginalScriptName then
        towedVehicle:setScriptName(towedModData.towBarOriginalScriptName)
    end
    if towedModData.towBarOriginalMass ~= nil then
        towedVehicle:setMass(towedModData.towBarOriginalMass)
    end
    if towedModData.towBarOriginalBrakingForce ~= nil then
        towedVehicle:setBrakingForce(towedModData.towBarOriginalBrakingForce)
    end
    towedVehicle:constraintChanged()
    towedVehicle:updateTotalMass()

    sendClientCommand(playerObj, "towbar", "giveTowBar", { equipPrimary = true })

    towingVehicle:getModData()["isTowingByTowBar"] = false
    towedModData["isTowingByTowBar"] = false
    towedModData["towed"] = false
    towedModData.towBarOriginalScriptName = nil
    towedModData.towBarOriginalMass = nil
    towedModData.towBarOriginalBrakingForce = nil
    towingVehicle:transmitModData()
    towedVehicle:transmitModData()
    TowBarMod.Hook.clearReapplied(towedVehicle)
    TowBarMod.Hook.lastForcedReapplyAtByVehicle[towedVehicle:getId()] = nil

    local part = towedVehicle:getPartById("towbar")
    if part ~= nil then
        for j=0, 23 do
            part:setModelVisible("towbar" .. j, false)
        end
    end
    towedVehicle:doDamageOverlay()
end

function TowBarMod.Hook.deattachTowBarAction(playerObj, vehicle)
    local towingVehicle = vehicle
    local towedVehicle = vehicle:getVehicleTowing()
    if vehicle:getVehicleTowedBy() then
        towingVehicle = vehicle:getVehicleTowedBy()
        towedVehicle = vehicle
    end
    if towingVehicle == nil or towedVehicle == nil then return end

    local localPoint = towingVehicle:getAttachmentLocalPos(towingVehicle:getTowAttachmentSelf(), TowBarMod.Utils.tempVector1)
    local shift = 0
    if towingVehicle:getModData()["isChangedTowedAttachment"] then
        shift = localPoint:z() > 0 and -1 or 1
    end
    local hookPoint = towingVehicle:getWorldPos(localPoint:x(), localPoint:y(), localPoint:z() + shift, TowBarMod.Utils.tempVector2)
    if hookPoint == nil then return end
    ISTimedActionQueue.add(TowBarCustomPathFind:pathToLocationF(playerObj, hookPoint:x(), hookPoint:y(), hookPoint:z()))

    local storePrim = playerObj:getPrimaryHandItem()
    if storePrim ~= nil then
        ISTimedActionQueue.add(ISUnequipAction:new(playerObj, storePrim, 12))
    end

    ISTimedActionQueue.add(TowBarHookVehicle:new(playerObj, 100, TowBarMod.Config.lowLevelAnimation))

    localPoint = towedVehicle:getAttachmentLocalPos(towedVehicle:getTowAttachmentSelf(), TowBarMod.Utils.tempVector1)
    shift = 0
    if towedVehicle:getModData()["isChangedTowedAttachment"] then
        shift = localPoint:z() > 0 and -1 or 1
    end
    hookPoint = towedVehicle:getWorldPos(localPoint:x(), localPoint:y(), localPoint:z() + shift, TowBarMod.Utils.tempVector2)
    if hookPoint == nil then return end
    ISTimedActionQueue.add(TowBarCustomPathFind:pathToLocationF(playerObj, hookPoint:x(), hookPoint:y(), hookPoint:z()))

    ISTimedActionQueue.add(TowBarHookVehicle:new(
        playerObj,
        300,
        TowBarMod.Config.lowLevelAnimation,
        TowBarMod.Hook.performDeattachTowBar,
        towingVehicle,
        towedVehicle
    ))
end

TowBarMod.Hook.installStartEngineBlock()
TowBarMod.Hook.installSeatEntryBlock()
Events.OnGameStart.Add(TowBarMod.Hook.installStartEngineBlock)
Events.OnGameStart.Add(TowBarMod.Hook.installSeatEntryBlock)
Events.OnPlayerUpdate.Add(TowBarMod.Hook.enforceTowedVehicleEngineOff)
Events.OnPlayerUpdate.Add(TowBarMod.Hook.enforceTowedVehicleSeatSafety)
Events.OnSpawnVehicleEnd.Add(TowBarMod.Hook.OnSpawnVehicle)
Events.OnEnterVehicle.Add(TowBarMod.Hook.OnEnterVehicle)
Events.OnSwitchVehicleSeat.Add(TowBarMod.Hook.OnSwitchVehicleSeat)
Events.OnExitVehicle.Add(TowBarMod.Hook.OnExitVehicle)
Events.OnTick.Add(TowBarMod.Hook.processPendingReapplies)


