
TirePressureVehicleAction = {}

local TIRE_PRESSURE_DISPLAY_XML_KEY = "vehicle.tirePressureSystem"
local TIRE_PRESSURE_DISPLAY_TYPES = {
    pressure = "pressure",
    frontPressure = "frontPressure",
    rearPressure = "rearPressure",
    trailerPressure = "trailerPressure"
}

function TirePressureVehicleAction.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(Wheels, specializations)
end

function TirePressureVehicleAction.initSpecialization()
    if TirePressureSystem ~= nil and TirePressureSystem.registerSavegameXMLPaths ~= nil then
        TirePressureSystem:registerSavegameXMLPaths()
    end

    if Vehicle ~= nil and Vehicle.xmlSchema ~= nil then
        local schema = Vehicle.xmlSchema
        schema:setXMLSpecializationType("MudSystemTirePressureDisplay")

        schema:register(
            XMLValueType.INT,
            TIRE_PRESSURE_DISPLAY_XML_KEY .. "#refreshMs",
            "Pressure display refresh interval in milliseconds",
            100
        )
        schema:register(
            XMLValueType.BOOL,
            TIRE_PRESSURE_DISPLAY_XML_KEY .. "#onlyWhenControlled",
            "Update text only while this vehicle is controlled",
            false
        )

        local displayKey = TIRE_PRESSURE_DISPLAY_XML_KEY .. ".display(?)"
        schema:register(
            XMLValueType.STRING,
            displayKey .. "#type",
            "pressure|frontPressure|rearPressure|trailerPressure",
            "pressure"
        )
        schema:register(XMLValueType.NODE_INDEX, displayKey .. "#node", "Text transform node", nil)
        schema:register(XMLValueType.STRING, displayKey .. "#font", "Font material name", "DIGIT")
        schema:register(XMLValueType.STRING, displayKey .. "#alignment", "LEFT|CENTER|RIGHT", "CENTER")
        schema:register(XMLValueType.FLOAT, displayKey .. "#size", "Character size", 0.012)
        schema:register(XMLValueType.FLOAT, displayKey .. "#scaleX", "Character texture scale X", 1)
        schema:register(XMLValueType.FLOAT, displayKey .. "#scaleY", "Character texture scale Y", 1)
        schema:register(XMLValueType.STRING, displayKey .. "#mask", "Pressure mask, for example 0.0", "0.0")
        schema:register(XMLValueType.FLOAT, displayKey .. "#emissiveScale", "Text emissive scale", 1)
        schema:register(XMLValueType.COLOR, displayKey .. "#color", "Text color", "1 1 1 1")
        schema:register(XMLValueType.COLOR, displayKey .. "#hiddenColor", "Unlit text color", "")
        schema:register(XMLValueType.COLOR, displayKey .. "#offColor", "Text color while the motor is stopped", "0.15 0.15 0.15 1")
        schema:register(XMLValueType.COLOR, displayKey .. "#offHiddenColor", "Unlit segment color while the motor is stopped", "0.02 0.02 0.02 1")
        schema:register(XMLValueType.FLOAT, displayKey .. "#offEmissiveScale", "Text emissive scale while the motor is stopped", 0)

        local sliderKey = TIRE_PRESSURE_DISPLAY_XML_KEY .. ".slider(?)"
        schema:register(
            XMLValueType.STRING,
            sliderKey .. "#type",
            "pressure|frontPressure|rearPressure|trailerPressure",
            "pressure"
        )
        schema:register(XMLValueType.NODE_INDEX, sliderKey .. "#node", "Slider shape node", nil)
        schema:register(XMLValueType.FLOAT, sliderKey .. "#minValueSlider", "Pressure at empty slider position", 0.8)
        schema:register(XMLValueType.FLOAT, sliderKey .. "#maxValueSlider", "Pressure at full slider position", 2.8)
        schema:register(XMLValueType.FLOAT, sliderKey .. "#intensity", "Slider light intensity while the motor is running", 3)
        schema:register(XMLValueType.FLOAT, sliderKey .. "#offIntensity", "Slider light intensity while the motor is stopped", 0)

        schema:setXMLSpecializationType()
    end
end

function TirePressureVehicleAction.registerEventListeners(vehicleType)
    SpecializationUtil.registerEventListener(vehicleType, "onLoad", TirePressureVehicleAction)
    SpecializationUtil.registerEventListener(vehicleType, "onPostLoad", TirePressureVehicleAction)
    SpecializationUtil.registerEventListener(vehicleType, "onUpdate", TirePressureVehicleAction)
    SpecializationUtil.registerEventListener(vehicleType, "onDelete", TirePressureVehicleAction)
    SpecializationUtil.registerEventListener(vehicleType, "onRegisterActionEvents", TirePressureVehicleAction)
    SpecializationUtil.registerEventListener(vehicleType, "saveToXMLFile", TirePressureVehicleAction)
end

local function getPressureDisplayFont(vehicle, fontName)
    if g_materialManager == nil or g_materialManager.getFontMaterial == nil then
        return nil
    end
    return g_materialManager:getFontMaterial(
        string.upper(fontName or "DIGIT"),
        vehicle.customEnvironment
    )
end

local function formatPressureDisplayValue(value, mask)
    mask = tostring(mask or "0.0")
    local separatorIndex = string.find(mask, ".", 1, true)
        or string.find(mask, ",", 1, true)
    local decimals = 0
    if separatorIndex ~= nil then
        for index = separatorIndex + 1, #mask do
            if string.sub(mask, index, index) == "0" then
                decimals = decimals + 1
            end
        end
    end

    local text = string.format("%." .. tostring(decimals) .. "f", tonumber(value) or 0)
    if string.find(mask, ",", 1, true) ~= nil then
        text = string.gsub(text, "%.", ",")
    end

    local width = math.max(1, #mask)
    if #text < width then
        text = string.rep(" ", width - #text) .. text
    end
    return text
end

local function clearPressureDisplayText(display)
    if display == nil or display.characterLine == nil then return end
    local blank = string.rep(" ", math.max(1, display.characterCount or 1))
    display.characterLine:setText(blank)
end

local function getPressureDisplayMotorRunning(vehicle)
    local motorVehicle = vehicle
    if motorVehicle ~= nil and motorVehicle.spec_motorized == nil
        and motorVehicle.getRootVehicle ~= nil then
        local ok, rootVehicle = pcall(motorVehicle.getRootVehicle, motorVehicle)
        if ok and rootVehicle ~= nil then
            motorVehicle = rootVehicle
        end
    end

    if motorVehicle == nil then return false end

    local motorSpec = motorVehicle.spec_motorized
    if motorSpec ~= nil and motorSpec.isMotorStarted ~= nil then
        return motorSpec.isMotorStarted == true
    end
    if motorVehicle.getIsMotorStarted ~= nil then
        local ok, isStarted = pcall(motorVehicle.getIsMotorStarted, motorVehicle)
        return ok and isStarted == true
    end
    return false
end

local function updatePressureDisplayColors(vehicle, force)
    local spec = vehicle ~= nil and vehicle.spec_mudsysTirePressureAction or nil
    if spec == nil then return end

    local motorRunning = getPressureDisplayMotorRunning(vehicle)
    if not force and spec.pressureDisplayMotorRunning == motorRunning then return end
    spec.pressureDisplayMotorRunning = motorRunning

    for _, display in ipairs(spec.pressureDisplays) do
        if display.characterLine ~= nil then
            if motorRunning then
                display.characterLine:setColor(
                    display.color,
                    display.hiddenColor,
                    display.emissiveScale
                )
            else
                display.characterLine:setColor(
                    display.offColor,
                    display.offHiddenColor,
                    display.offEmissiveScale
                )
            end
        end
    end

    for _, slider in ipairs(spec.pressureSliders or {}) do
        local intensity = motorRunning and slider.intensity or slider.offIntensity
        if setShaderParameter ~= nil then
            pcall(
                setShaderParameter,
                slider.node,
                "lightControl",
                intensity or 0,
                0,
                0,
                0,
                false
            )
        end
    end
end

local function logPressureDisplayWarning(vehicle, message)
    local configFileName = vehicle ~= nil and vehicle.configFileName or "unknown vehicle XML"
    print(string.format(
        "[MudSystemPhysics] Tire pressure display warning in %s: %s",
        tostring(configFileName),
        tostring(message)
    ))
end

function TirePressureVehicleAction:loadPressureDisplays()
    local spec = self.spec_mudsysTirePressureAction
    if spec == nil or not self.isClient or self.xmlFile == nil then return end

    spec.pressureDisplays = {}
    spec.pressureSliders = {}
    spec.pressureDisplayTimer = 0
    spec.pressureDisplayRefreshMs = math.max(
        16,
        self.xmlFile:getValue(TIRE_PRESSURE_DISPLAY_XML_KEY .. "#refreshMs", 100)
    )
    spec.pressureDisplayOnlyWhenControlled = self.xmlFile:getValue(
        TIRE_PRESSURE_DISPLAY_XML_KEY .. "#onlyWhenControlled",
        false
    )

    if self.xmlFile.hasProperty ~= nil
        and not self.xmlFile:hasProperty(TIRE_PRESSURE_DISPLAY_XML_KEY) then
        return
    end

    local configuredDisplayCount = 0
    self.xmlFile:iterate(TIRE_PRESSURE_DISPLAY_XML_KEY .. ".display", function(_, key)
        configuredDisplayCount = configuredDisplayCount + 1
        local node = self.xmlFile:getValue(key .. "#node", nil, self.components, self.i3dMappings)
        local typeName = self.xmlFile:getValue(key .. "#type", "pressure")
        local statKey = TIRE_PRESSURE_DISPLAY_TYPES[typeName]
        if node == nil then
            logPressureDisplayWarning(self, key .. " has an invalid #node")
            return
        end
        if statKey == nil then
            logPressureDisplayWarning(self, key .. " has unsupported type '" .. tostring(typeName) .. "'")
            return
        end

        local font = getPressureDisplayFont(
            self,
            self.xmlFile:getValue(key .. "#font", "DIGIT")
        )
        if font == nil then
            logPressureDisplayWarning(self, key .. " could not load its font")
            return
        end
        if CharacterLine == nil or CharacterLine.new == nil then
            logPressureDisplayWarning(self, "CharacterLine is unavailable on this client")
            return
        end

        local mask = self.xmlFile:getValue(key .. "#mask", "0.0")
        local characterCount = math.max(1, #tostring(mask))
        local ok, characterLine = pcall(function()
            return CharacterLine.new(node, font, characterCount)
        end)
        if not ok or characterLine == nil then
            logPressureDisplayWarning(
                self,
                key .. " could not create CharacterLine: " .. tostring(characterLine)
            )
            return
        end

        local size = self.xmlFile:getValue(key .. "#size", 0.012)
        local scaleX = self.xmlFile:getValue(key .. "#scaleX", 1)
        local scaleY = self.xmlFile:getValue(key .. "#scaleY", 1)
        characterLine:setSizeAndScale(size, scaleX, scaleY)

        local alignmentName = string.upper(
            self.xmlFile:getValue(key .. "#alignment", "CENTER")
        )
        local alignment = RenderText ~= nil
            and RenderText["ALIGN_" .. alignmentName] or nil
        if alignment == nil and RenderText ~= nil then
            alignment = RenderText.ALIGN_CENTER
        end
        if alignment ~= nil then
            characterLine:setTextAlignment(alignment)
        end

        local color = self.xmlFile:getValue(key .. "#color", {1, 1, 1, 1}, true)
        local hiddenColor = self.xmlFile:getValue(key .. "#hiddenColor", nil, true)
        local emissiveScale = self.xmlFile:getValue(key .. "#emissiveScale", 1)
        local offColor = self.xmlFile:getValue(
            key .. "#offColor",
            {0.15, 0.15, 0.15, 1},
            true
        )
        local offHiddenColor = self.xmlFile:getValue(
            key .. "#offHiddenColor",
            {0.02, 0.02, 0.02, 1},
            true
        )
        local offEmissiveScale = self.xmlFile:getValue(key .. "#offEmissiveScale", 0)

        local display = {
            typeName = typeName,
            statKey = statKey,
            mask = mask,
            characterCount = characterCount,
            characterLine = characterLine,
            color = color,
            hiddenColor = hiddenColor,
            emissiveScale = emissiveScale,
            offColor = offColor,
            offHiddenColor = offHiddenColor,
            offEmissiveScale = offEmissiveScale
        }
        clearPressureDisplayText(display)
        table.insert(spec.pressureDisplays, display)
    end)

    local configuredSliderCount = 0
    self.xmlFile:iterate(TIRE_PRESSURE_DISPLAY_XML_KEY .. ".slider", function(_, key)
        configuredSliderCount = configuredSliderCount + 1
        local node = self.xmlFile:getValue(key .. "#node", nil, self.components, self.i3dMappings)
        local typeName = self.xmlFile:getValue(key .. "#type", "pressure")
        local statKey = TIRE_PRESSURE_DISPLAY_TYPES[typeName]
        if node == nil then
            logPressureDisplayWarning(self, key .. " has an invalid #node")
            return
        end
        if statKey == nil then
            logPressureDisplayWarning(self, key .. " has unsupported type '" .. tostring(typeName) .. "'")
            return
        end

        local hasSliderPos = false
        if getHasShaderParameter ~= nil then
            local ok, value = pcall(getHasShaderParameter, node, "sliderPos")
            hasSliderPos = ok and value == true
        end
        if not hasSliderPos then
            logPressureDisplayWarning(self, key .. " node has no sliderPos shader parameter")
            return
        end

        local minValue = self.xmlFile:getValue(key .. "#minValueSlider", 0.8)
        local maxValue = self.xmlFile:getValue(key .. "#maxValueSlider", 2.8)
        if maxValue <= minValue then
            logPressureDisplayWarning(self, key .. " requires maxValueSlider > minValueSlider")
            return
        end

        local slider = {
            typeName = typeName,
            statKey = statKey,
            node = node,
            minValue = minValue,
            maxValue = maxValue,
            intensity = self.xmlFile:getValue(key .. "#intensity", 3),
            offIntensity = self.xmlFile:getValue(key .. "#offIntensity", 0)
        }
        if setShaderParameter ~= nil then
            pcall(setShaderParameter, node, "sliderPos", 0, 0, 0, 0, false)
        end
        table.insert(spec.pressureSliders, slider)
    end)

    print(string.format(
        "[MudSystemPhysics] Tire pressure instruments loaded for %s: %d/%d CharacterLine(s), %d/%d slider(s)",
        tostring(self.configFileName or "vehicle"),
        #spec.pressureDisplays,
        configuredDisplayCount,
        #spec.pressureSliders,
        configuredSliderCount
    ))
    updatePressureDisplayColors(self, true)
end

function TirePressureVehicleAction:updatePressureDisplays(dt)
    local spec = self.spec_mudsysTirePressureAction
    if spec == nil then return end
    local hasDisplays = spec.pressureDisplays ~= nil and #spec.pressureDisplays > 0
    local hasSliders = spec.pressureSliders ~= nil and #spec.pressureSliders > 0
    if not hasDisplays and not hasSliders then return end

    spec.pressureDisplayTimer = (spec.pressureDisplayTimer or 0) + math.max(0, dt or 0)
    if spec.pressureDisplayTimer < (spec.pressureDisplayRefreshMs or 100) then return end
    spec.pressureDisplayTimer = 0
    updatePressureDisplayColors(self, false)

    if TirePressureSystem == nil or TirePressureSystem.enabled ~= true
        or TirePressureSystem.getPressureDisplayStats == nil then
        for _, display in ipairs(spec.pressureDisplays) do
            clearPressureDisplayText(display)
        end
        for _, slider in ipairs(spec.pressureSliders or {}) do
            if setShaderParameter ~= nil then
                pcall(setShaderParameter, slider.node, "sliderPos", 0, 0, 0, 0, false)
            end
        end
        return
    end

    if spec.pressureDisplayOnlyWhenControlled then
        local controlled = false
        if self.getIsActiveForInput ~= nil then
            controlled = self:getIsActiveForInput(true)
        elseif self.getIsEntered ~= nil then
            controlled = self:getIsEntered()
        end
        if not controlled then return end
    end

    local stats = TirePressureSystem:getPressureDisplayStats(self)
    if stats == nil then return end

    for _, display in ipairs(spec.pressureDisplays) do
        display.characterLine:setText(
            formatPressureDisplayValue(stats[display.statKey], display.mask)
        )
    end
    for _, slider in ipairs(spec.pressureSliders or {}) do
        local value = tonumber(stats[slider.statKey]) or slider.minValue
        local normalized = (value - slider.minValue)
            / math.max(0.0001, slider.maxValue - slider.minValue)
        normalized = math.max(0, math.min(1, normalized))
        if setShaderParameter ~= nil then
            pcall(
                setShaderParameter,
                slider.node,
                "sliderPos",
                normalized,
                0,
                0,
                0,
                false
            )
        end
    end
end

function TirePressureVehicleAction.registerOverwrittenFunctions(vehicleType)
    SpecializationUtil.registerOverwrittenFunction(
        vehicleType,
        "registerSelectableObjects",
        TirePressureVehicleAction.registerSelectableObjects
    )
end

-- FS25 normally adds implements to the G selection list only when one of
-- their work specializations reports getCanBeSelected() == true. A plain
-- enterable tractor has no equivalent rule, so it can be absent while an
-- attached trailer remains selectable. Keep the game's own selection object
-- and cycle logic, but make the controlled root vehicle an explicit entry.
-- We intentionally do not overwrite getCanBeSelected(): doing that
-- would make a newly attached implement lose the game's normal initial focus.
function TirePressureVehicleAction:registerSelectableObjects(superFunc, selectableObjects)
    superFunc(self, selectableObjects)

    if selectableObjects == nil
        or self ~= self.rootVehicle
        or self.spec_enterable == nil
        or self.selectionObject == nil then
        return
    end

    if self.getBlockSelection ~= nil and self:getBlockSelection() then
        return
    end

    for _, object in ipairs(selectableObjects) do
        if object == self.selectionObject then
            return
        end
    end

    -- Append instead of inserting at index 1. FS25 persists the selected
    -- object as a numeric list index; preserving all vanilla implement indices
    -- keeps existing savegames compatible with this extended chain.
    table.insert(selectableObjects, self.selectionObject)
    self.selectionObject.index = #selectableObjects

    if self.__mudsysRootSelectionLogged ~= true then
        self.__mudsysRootSelectionLogged = true
        print(
            "[MudSystemPhysics] Added controlled root vehicle to the G selection chain"
        )
    end
end

function TirePressureVehicleAction:onLoad(savegame)
    self.spec_mudsysTirePressureAction = self.spec_mudsysTirePressureAction or {}
    self.spec_mudsysTirePressureAction.actionEvents = self.spec_mudsysTirePressureAction.actionEvents or {}
    self.spec_mudsysTirePressureAction.visualRetryTimer = 0

    TirePressureVehicleAction.loadPressureDisplays(self)

    if TirePressureSystem ~= nil and TirePressureSystem.loadVehicleFromSavegame ~= nil then
        TirePressureSystem:loadVehicleFromSavegame(self, savegame)
    end
end

function TirePressureVehicleAction:onPostLoad(savegame)
    if TirePressureSystem ~= nil and TirePressureSystem.prepareVehicle ~= nil then
        TirePressureSystem:prepareVehicle(self)
    end

    if TirePressureSystem ~= nil and TirePressureSystem.applyVehicleSavegamePressure ~= nil then
        TirePressureSystem:applyVehicleSavegamePressure(self)
    end
end

function TirePressureVehicleAction:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
    if TirePressureSystem == nil or self.__mudSystemDeleting == true
        or self.isDeleting == true or self.isDeleted == true then
        return
    end

    if TirePressureSystem.enabled == true then
        TirePressureSystem:updateVehicle(self, dt)
        if (isActiveForInput == true or isActiveForInputIgnoreSelection == true)
            and TirePressureSystem.ensureSwitchImplementAction ~= nil then
            TirePressureSystem:ensureSwitchImplementAction(self)
        end
    elseif TirePressureSystem.clearVehicle ~= nil then
        TirePressureSystem:clearVehicle(self)
    end

    TirePressureVehicleAction.updatePressureDisplays(self, dt)
end

function TirePressureVehicleAction:onDelete()
    local spec = self.spec_mudsysTirePressureAction
    if spec ~= nil and spec.actionEvents ~= nil then
        self:clearActionEventsTable(spec.actionEvents)
    end

    if spec ~= nil and spec.pressureDisplays ~= nil then
        for _, display in ipairs(spec.pressureDisplays) do
            if display.characterLine ~= nil and display.characterLine.delete ~= nil then
                display.characterLine:delete()
            end
        end
        spec.pressureDisplays = {}
    end

    if TirePressureSystem ~= nil and TirePressureSystem.clearVehicle ~= nil then
        TirePressureSystem:clearVehicle(self, true)
    end
end

function TirePressureVehicleAction:saveToXMLFile(xmlFile, key, usedModNames)
    if TirePressureSystem ~= nil and TirePressureSystem.saveVehicleToXMLFile ~= nil then
        TirePressureSystem:saveVehicleToXMLFile(self, xmlFile, key)
    end
end

local function setActionText(actionEventId, textKey, active)
    if actionEventId == nil or g_inputBinding == nil then
        return
    end

    if g_inputBinding.setActionEventText ~= nil then
        g_inputBinding:setActionEventText(actionEventId, g_i18n:getText(textKey))
    end

    if g_inputBinding.setActionEventTextPriority ~= nil then
        g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_NORMAL)
    end

    if g_inputBinding.setActionEventTextVisibility ~= nil then
        g_inputBinding:setActionEventTextVisibility(actionEventId, true)
    end

    if g_inputBinding.setActionEventActive ~= nil then
        g_inputBinding:setActionEventActive(actionEventId, active ~= false)
    end
end

local function resolveActionIndex(actionName)
    if InputAction ~= nil and InputAction[actionName] ~= nil then
        return InputAction[actionName]
    end

    if g_inputBinding ~= nil and g_inputBinding.getActionIndex ~= nil then
        local ok, actionIndex = pcall(g_inputBinding.getActionIndex, g_inputBinding, actionName)
        if ok and actionIndex ~= nil then
            return actionIndex
        end
    end

    return nil
end

function TirePressureVehicleAction:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSelection)
    if not self.isClient then
        return
    end

    if self.getIsEnteredForInput ~= nil then
        if not self:getIsEnteredForInput() then
            return
        end
    elseif self.getIsEntered ~= nil then
        if not self:getIsEntered() then
            return
        end
    else
        return
    end

    if self.rootVehicle ~= nil and self.rootVehicle ~= self then
        return
    end

    if g_currentMission ~= nil and g_currentMission.controlledVehicle ~= nil and g_currentMission.controlledVehicle ~= self then
        return
    end

    local spec = self.spec_mudsysTirePressureAction
    if spec == nil then
        return
    end

    self:clearActionEventsTable(spec.actionEvents)

    local hudMouseAction = resolveActionIndex("MUDSYS_TOGGLE_MOUSE_MODE")
    if hudMouseAction ~= nil then
        local _, eventId = self:addActionEvent(spec.actionEvents, hudMouseAction, self, TirePressureVehicleAction.actionToggleHudMouse, false, true, false, true)
        if MudWetnessMiniHUD ~= nil and MudWetnessMiniHUD.getToggleMouseText ~= nil then
            setActionText(eventId, MudWetnessMiniHUD.cursorModeActive and "input_MUDSYS_TOGGLE_MOUSE_MODE_OFF" or "input_MUDSYS_TOGGLE_MOUSE_MODE", true)
        else
            setActionText(eventId, "input_MUDSYS_TOGGLE_MOUSE_MODE", true)
        end
    end

    if TirePressureSystem ~= nil and TirePressureSystem.enabled ~= true then
        return
    end

    local pressureDownAction = resolveActionIndex("MUDSYS_TIRE_PRESSURE_DOWN")
    if pressureDownAction ~= nil then
        local _, eventId = self:addActionEvent(spec.actionEvents, pressureDownAction, self, TirePressureVehicleAction.actionPressureDown, false, true, false, true)
        setActionText(eventId, "input_MUDSYS_TIRE_PRESSURE_DOWN", true)
    end

    local pressureUpAction = resolveActionIndex("MUDSYS_TIRE_PRESSURE_UP")
    if pressureUpAction ~= nil then
        local _, eventId = self:addActionEvent(spec.actionEvents, pressureUpAction, self, TirePressureVehicleAction.actionPressureUp, false, true, false, true)
        setActionText(eventId, "input_MUDSYS_TIRE_PRESSURE_UP", true)
    end

    local pressurePanelAction = resolveActionIndex("MUDSYS_TIRE_PRESSURE_PRESET")
    if pressurePanelAction ~= nil then
        local _, eventId = self:addActionEvent(spec.actionEvents, pressurePanelAction, self, TirePressureVehicleAction.actionTogglePressureHud, false, true, false, true)
        setActionText(eventId, "input_MUDSYS_TIRE_PRESSURE_PRESET", true)
    end
end

local function shouldHandlePress(callbackState)
    return InputAction == nil
        or InputAction.CALLBACK_STATE_START == nil
        or callbackState == InputAction.CALLBACK_STATE_START
end

local function debounce(vehicle, actionName)
    local spec = vehicle ~= nil and vehicle.spec_mudsysTirePressureAction or nil
    if spec == nil then
        return true
    end

    spec._lastInputTime = spec._lastInputTime or {}
    local now = g_time or 0
    local key = tostring(actionName or "unknown")
    local last = spec._lastInputTime[key] or -1000
    if now - last < 120 then
        return false
    end

    spec._lastInputTime[key] = now
    return true
end

function TirePressureVehicleAction.actionPressureDown(vehicle, actionName, inputValue, callbackState, isAnalog)
    if not shouldHandlePress(callbackState) or not debounce(vehicle, actionName) then
        return
    end

    if TirePressureSystem ~= nil then
        TirePressureSystem:onPressureDown(vehicle)
    end
end

function TirePressureVehicleAction.actionToggleHudMouse(vehicle, actionName, inputValue, callbackState, isAnalog)
    if not shouldHandlePress(callbackState) or not debounce(vehicle, actionName) then
        return
    end

    if MudWetnessMiniHUD ~= nil and MudWetnessMiniHUD.onPressToggleMouseMode ~= nil then
        MudWetnessMiniHUD:onPressToggleMouseMode()
    end
end

function TirePressureVehicleAction.actionTogglePressureHud(vehicle, actionName, inputValue, callbackState, isAnalog)
    if not shouldHandlePress(callbackState) or not debounce(vehicle, actionName) then
        return
    end

    if TirePressureSystem ~= nil then
        TirePressureSystem:onTogglePanel(vehicle)
    end
end

function TirePressureVehicleAction.actionPressureUp(vehicle, actionName, inputValue, callbackState, isAnalog)
    if not shouldHandlePress(callbackState) or not debounce(vehicle, actionName) then
        return
    end

    if TirePressureSystem ~= nil then
        TirePressureSystem:onPressureUp(vehicle)
    end
end

function TirePressureVehicleAction.actionPressurePreset(vehicle, actionName, inputValue, callbackState, isAnalog)
    if not shouldHandlePress(callbackState) or not debounce(vehicle, actionName) then
        return
    end

    if TirePressureSystem ~= nil then
        TirePressureSystem:onTogglePanel(vehicle)
    end
end
