
FieldMudInfoHUD = {}
FieldMudInfoHUD.name = g_currentModName or "FieldMudInfoHUD"
FieldMudInfoHUD.path = g_currentModDirectory or ""

addModEventListener(FieldMudInfoHUD)

FieldMudInfoHUD.debug = false

FieldMudInfoHUD.offsetXPx = 347
FieldMudInfoHUD.offsetYPx = -154
FieldMudInfoHUD.widthPx = 255
FieldMudInfoHUD.lineHeightPx = 16
FieldMudInfoHUD.bgAlpha = 0.52
FieldMudInfoHUD.textAlpha = 0.92
FieldMudInfoHUD.updateIntervalMs = 250
FieldMudInfoHUD.dragging = false
FieldMudInfoHUD.dragOffsetX = 0
FieldMudInfoHUD.dragOffsetY = 0

FieldMudInfoHUD._bgOverlay = nil
FieldMudInfoHUD._bgLeft = nil
FieldMudInfoHUD._bgMiddle = nil
FieldMudInfoHUD._bgRight = nil
FieldMudInfoHUD._cache = nil
FieldMudInfoHUD._lastUpdateMs = 0

local function fmihClamp(v, mn, mx)
    v = tonumber(v) or 0
    if v < mn then return mn end
    if v > mx then return mx end
    return v
end

local function fmihFmtPercent(v)
    return string.format("%d%%", math.floor(fmihClamp(v, 0, 1) * 100 + 0.5))
end

local function fmihGetText(key, fallback)
    if g_i18n ~= nil and key ~= nil and g_i18n.hasText ~= nil and g_i18n:hasText(key) then
        return g_i18n:getText(key)
    end
    return fallback or key or ""
end

local function fmihGetWeatherWetness()
    if FieldMudInfoHUD:getIsHardWinterFrozen() then
        return 0
    end

    local weather = nil
    if g_currentMission ~= nil
        and g_currentMission.environment ~= nil
        and g_currentMission.environment.weather ~= nil then
        weather = g_currentMission.environment.weather
    end

    if weather ~= nil and weather.getGroundWetness ~= nil then
        return fmihClamp(weather:getGroundWetness() or 0, 0, 1)
    end

    return 0
end

function FieldMudInfoHUD:getIsHardWinterFrozen()
    return FieldGroundMudPhysics ~= nil
        and FieldGroundMudPhysics.isHardWinterFrozen ~= nil
        and FieldGroundMudPhysics:isHardWinterFrozen()
end

local function fmihGetControlledVehicle()
    if g_currentMission == nil then return nil end

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

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

    return nil
end

function FieldMudInfoHUD:getIsActive()
    -- Retired: local wetness and stuck risk are now rendered by MudWetnessMiniHUD.
    return false
end

local function fmihGetVehicleXZ(vehicle)
    if vehicle == nil or getWorldTranslation == nil then
        return nil, nil
    end

    local node = vehicle.rootNode
    if (node == nil or node == 0) and vehicle.components ~= nil and vehicle.components[1] ~= nil then
        node = vehicle.components[1].node
    end

    if node == nil or node == 0 then
        return nil, nil
    end

    local x, _, z = getWorldTranslation(node)
    return x, z
end

local function fmihGetVehicleRootNode(vehicle)
    if vehicle == nil then
        return nil
    end

    if vehicle.rootNode ~= nil and vehicle.rootNode ~= 0 then
        return vehicle.rootNode
    end

    if vehicle.components ~= nil and vehicle.components[1] ~= nil then
        local node = vehicle.components[1].node
        if node ~= nil and node ~= 0 then
            return node
        end
    end

    return nil
end

local function fmihIsValidNode(node)
    if node == nil or node == 0 then
        return false
    end
    if entityExists ~= nil then
        return entityExists(node)
    end
    return true
end

local function fmihChooseWheelNode(wheel)
    if wheel == nil then return nil end

    local node = wheel.repr
    if fmihIsValidNode(node) then return node end

    node = wheel.driveNode
    if fmihIsValidNode(node) then return node end

    node = wheel.linkNode
    if fmihIsValidNode(node) then return node end

    if wheel.getFirstTireNode ~= nil then
        node = wheel:getFirstTireNode()
        if fmihIsValidNode(node) then return node end
    end

    node = wheel.wheelNode or wheel.node
    if fmihIsValidNode(node) then return node end

    return nil
end

local function fmihGetWheelContactWorldPos(wheel)
    local p = wheel ~= nil and wheel.physics or nil
    if p ~= nil and p.lastContactX ~= nil and p.lastContactY ~= nil and p.lastContactZ ~= nil then
        return p.lastContactX, p.lastContactY, p.lastContactZ
    end

    if FieldGroundMudPhysics ~= nil and FieldGroundMudPhysics.getWheelContactPos ~= nil then
        local x, y, z = FieldGroundMudPhysics:getWheelContactPos(wheel)
        if x ~= nil then
            return x, y, z
        end
    end

    local node = fmihChooseWheelNode(wheel)
    if node ~= nil and getWorldTranslation ~= nil then
        return getWorldTranslation(node)
    end

    return nil, nil, nil
end

local function fmihGetWheelProfile(wheel, x, z)
    if FieldGroundMudPhysics == nil then
        return 0, nil, nil
    end

    local p = wheel ~= nil and wheel.physics or nil
    local densityType = p ~= nil and p.densityType or nil
    if densityType ~= nil and FieldGroundMudPhysics.getProfileByFieldGroundTypeValue ~= nil then
        local prof = FieldGroundMudPhysics:getProfileByFieldGroundTypeValue(densityType)
        if prof ~= nil then
            return fmihClamp(prof.mud or 0, 0, 1), densityType, prof
        end
    end

    if FieldGroundMudPhysics.getProfileAtWorldPos ~= nil and x ~= nil and z ~= nil then
        return FieldGroundMudPhysics:getProfileAtWorldPos(x, z)
    end

    return 0, densityType, nil
end

local function fmihAddCandidate(candidates, x, z)
    if x ~= nil and z ~= nil then
        candidates[#candidates + 1] = { x = x, z = z }
    end
end

local function fmihFindBestFieldPoint(vehicle, anyX, anyZ)
    if FieldGroundMudPhysics == nil or FieldGroundMudPhysics.getProfileAtWorldPos == nil then
        return nil, nil, nil, nil, nil
    end

    local candidates = {}
    fmihAddCandidate(candidates, anyX, anyZ)

    if vehicle ~= nil and vehicle.spec_wheels ~= nil and vehicle.spec_wheels.wheels ~= nil then
        for _, wheel in ipairs(vehicle.spec_wheels.wheels) do
            local x, _, z = fmihGetWheelContactWorldPos(wheel)
            fmihAddCandidate(candidates, x, z)
        end
    end

    local root = fmihGetVehicleRootNode(vehicle)
    if root ~= nil and localToWorld ~= nil then
        local offsets = {
            { 0, 0 }, { -2.5, 0 }, { 2.5, 0 },
            { 0, -3.0 }, { 0, 3.0 },
            { -2.5, -3.0 }, { 2.5, -3.0 },
            { -2.5, 3.0 }, { 2.5, 3.0 },
            { 0, -6.0 }, { 0, 6.0 },
            { -4.5, 0 }, { 4.5, 0 }
        }

        for _, off in ipairs(offsets) do
            local x, _, z = localToWorld(root, off[1], 0, off[2])
            fmihAddCandidate(candidates, x, z)
        end
    else
        local vx, vz = fmihGetVehicleXZ(vehicle)
        fmihAddCandidate(candidates, vx, vz)
    end

    local bestX, bestZ, bestMud, bestGT, bestProf = nil, nil, 0, nil, nil
    for _, p in ipairs(candidates) do
        local mudBase, gt, prof = FieldGroundMudPhysics:getProfileAtWorldPos(p.x, p.z)
        if prof ~= nil then
            local mud = mudBase or 0
            if bestProf == nil or mud > bestMud then
                bestX, bestZ, bestMud, bestGT, bestProf = p.x, p.z, mud, gt, prof
            end
        end
    end

    return bestX, bestZ, bestMud, bestGT, bestProf
end

local function fmihGetPlayerXZ()
    if g_localPlayer == nil then return nil, nil end
    local node = g_localPlayer.rootNode or g_localPlayer.graphicsRootNode
    if node == nil or node == 0 or getWorldTranslation == nil then
        return nil, nil
    end
    local x, _, z = getWorldTranslation(node)
    return x, z
end

local function fmihGetVehicleSpeedKph(vehicle)
    if vehicle == nil then return 0 end
    local speed = (vehicle.getLastSpeed ~= nil and vehicle:getLastSpeed()) or vehicle.lastSpeedReal or 0
    return math.abs(speed or 0) * 3.6
end

local function fmihRiskText(risk)
    risk = fmihClamp(risk, 0, 1)
    if risk < 0.18 then
        return fmihGetText("mudfieldinfo_risk_low", "Low"), { 0.55, 0.95, 0.55, 1 }
    elseif risk < 0.42 then
        return fmihGetText("mudfieldinfo_risk_medium", "Medium"), { 1.00, 0.86, 0.35, 1 }
    elseif risk < 0.70 then
        return fmihGetText("mudfieldinfo_risk_high", "High"), { 1.00, 0.55, 0.20, 1 }
    end
    return fmihGetText("mudfieldinfo_risk_extreme", "Extreme"), { 1.00, 0.24, 0.18, 1 }
end

function FieldMudInfoHUD:loadMap()
    addConsoleCommand("gsFieldMudInfo", "Field mud info HUD debug/reset/status", "consoleCommand", self)
end

function FieldMudInfoHUD:deleteMap()
    removeConsoleCommand("gsFieldMudInfo")
    self:deleteOverlays()
end

function FieldMudInfoHUD:deleteOverlays()
    if self._bgOverlay ~= nil then
        self._bgOverlay:delete()
        self._bgOverlay = nil
    end
    if self._bgLeft ~= nil then self._bgLeft:delete(); self._bgLeft = nil end
    if self._bgMiddle ~= nil then self._bgMiddle:delete(); self._bgMiddle = nil end
    if self._bgRight ~= nil then self._bgRight:delete(); self._bgRight = nil end
end

function FieldMudInfoHUD:rebuildOverlays()
    self:deleteOverlays()

    if g_overlayManager ~= nil and g_overlayManager.createOverlay ~= nil then
        self._bgLeft   = g_overlayManager:createOverlay("gui.gameInfo_left",   0, 0, 0, 0)
        self._bgMiddle = g_overlayManager:createOverlay("gui.gameInfo_middle", 0, 0, 0, 0)
        self._bgRight  = g_overlayManager:createOverlay("gui.gameInfo_right",  0, 0, 0, 0)
    else
        self._bgOverlay = Overlay.new(nil, 0, 0, 0.01, 0.01)
        self._bgOverlay:setColor(0, 0, 0, self.bgAlpha or 0.52)
    end
end

function FieldMudInfoHUD:consoleCommand(arg)
    arg = tostring(arg or "")
    if arg == "debug" then
        self.debug = not self.debug
    elseif arg == "reset" then
        self:resetPosition()
    else
        local veh = fmihGetControlledVehicle()
        return string.format("FieldMudInfoHUD active=%s debug=%s vehicle=%s. HUD visibility follows FieldLocalWetness.enabled.",
            tostring(self:getIsActive()),
            tostring(self.debug),
            tostring(veh ~= nil))
    end

    return string.format("FieldMudInfoHUD active=%s debug=%s", tostring(self:getIsActive()), tostring(self.debug))
end

function FieldMudInfoHUD:onSettingsChanged()
    self.updateIntervalMs = fmihClamp(self.updateIntervalMs, 50, 2000)
    self.widthPx = fmihClamp(self.widthPx, 160, 520)
end

function FieldMudInfoHUD:resetPosition()
    self.offsetXPx = 347
    self.offsetYPx = -154
end

function FieldMudInfoHUD:collectVehicleInfo(vehicle)
    if vehicle == nil or FieldGroundMudPhysics == nil then
        return nil
    end

    if vehicle.spec_wheels == nil or vehicle.spec_wheels.wheels == nil then
        local x, z = fmihGetVehicleXZ(vehicle)
        return self:collectPointInfo(x, z)
    end

    local wetnessBase = fmihGetWeatherWetness()
    local mudSum, wetSum, slipSum, count = 0, 0, 0, 0
    local bestMud, bestProf, bestGT, bestX, bestZ = 0, nil, nil, nil, nil
    local localCell = nil
    local localRawWet = nil
    local anyX, anyZ = nil, nil

    for _, wheel in ipairs(vehicle.spec_wheels.wheels) do
        local x, _, z = fmihGetWheelContactWorldPos(wheel)
        if x ~= nil then
            anyX = anyX or x
            anyZ = anyZ or z

            local mudBase, gt, prof = fmihGetWheelProfile(wheel, x, z)
            if prof ~= nil then
                local wet = wetnessBase
                if not self:getIsHardWinterFrozen() and FieldLocalWetness ~= nil and FieldLocalWetness.getEffectiveWetnessAt ~= nil then
                    wet = FieldLocalWetness:getEffectiveWetnessAt(x, z, wetnessBase, nil, prof)
                    if FieldLocalWetness.enabled == true and FieldLocalWetness.getWetnessAt ~= nil then
                        local raw, cell = FieldLocalWetness:getWetnessAt(x, z, nil)
                        localRawWet = raw or localRawWet
                        localCell = cell or localCell
                    end
                end

                local slip = 0
                if wheel.physics ~= nil and wheel.physics.netInfo ~= nil then
                    slip = wheel.physics.netInfo.slip or 0
                elseif wheel.physics ~= nil then
                    slip = wheel.physics.slip or 0
                end

                mudSum = mudSum + (mudBase or 0)
                wetSum = wetSum + (wet or 0)
                slipSum = slipSum + (slip or 0)
                count = count + 1

                if (mudBase or 0) > bestMud then
                    bestMud = mudBase or 0
                    bestGT = gt
                    bestProf = prof
                    bestX = x
                    bestZ = z
                end
            end
        end
    end

    if count <= 0 or bestProf == nil then
        local x, z = anyX, anyZ
        local fieldX, fieldZ, fieldMud, fieldGT, prof = fmihFindBestFieldPoint(vehicle, x, z)
        if prof ~= nil then
            x, z = fieldX, fieldZ
        elseif x == nil then
            x, z = fmihGetVehicleXZ(vehicle)
        end
        return self:collectPointInfo(x, z)
    end

    local wetness = fmihClamp(wetSum / count, 0, 1)
    local mudAvg = fmihClamp(mudSum / count, 0, 1)
    local slipAvg = fmihClamp(slipSum / count, 0, 1)

    local permaMul = 1.0
    if FieldLocalWetness ~= nil and FieldLocalWetness.getPermaChanceMultiplierAt ~= nil then
        permaMul = FieldLocalWetness:getPermaChanceMultiplierAt(bestX, bestZ, wetness, nil, bestProf)
    end

    local baseChance = (FieldGroundMudPhysics.permaStuckChanceOnStruggle or 0)
    local wetRisk = fmihClamp((wetness - (FieldGroundMudPhysics.wetnessThreshold or 0.12)) / 0.65, 0, 1)
    local mudRisk = fmihClamp(mudAvg * ((bestProf.sinkMul or 1) / 7.0), 0, 1)
    local profileRisk = fmihClamp(
        mudAvg * 0.30
        + ((bestProf.wetMul or 1.0) - 1.0) * 0.20
        + ((bestProf.sinkMul or 5.4) - 5.4) * 0.08
        + ((bestProf.brakeMul or 4.0) - 4.0) * 0.04,
        0, 1)
    local lowland = (localCell ~= nil and localCell.lowland) or 0
    local slipRisk = fmihClamp((slipAvg or 0) / math.max(0.01, FieldGroundMudPhysics.permaStuckSlipMin or 0.12), 0, 1)
    local risk = fmihClamp(wetRisk * 0.40 + mudRisk * 0.24 + lowland * 0.22 + profileRisk * 0.10 + slipRisk * 0.04, 0, 1)
    local chancePerSec = 0
    if FieldGroundMudPhysics.permaStuckEnable == true and bestProf.permaStuck == true then
        chancePerSec = fmihClamp(baseChance * permaMul * fmihClamp(0.25 + wetRisk * 0.75, 0, 1), 0, 1)
    end
    if self:getIsHardWinterFrozen() then
        chancePerSec = 0
        risk = 0
    end

    return {
        wetness = wetness,
        localRawWet = localRawWet,
        lowland = lowland,
        slope = (localCell ~= nil and localCell.slope) or 0,
        risk = risk,
        chancePerSec = chancePerSec,
        slip = slipAvg,
        speedKph = fmihGetVehicleSpeedKph(vehicle),
        localMode = (FieldLocalWetness ~= nil and FieldLocalWetness.enabled == true)
    }
end

function FieldMudInfoHUD:collectPointInfo(x, z)
    if x == nil or z == nil or FieldGroundMudPhysics == nil then
        return nil
    end

    local mudBase, _, prof = FieldGroundMudPhysics:getProfileAtWorldPos(x, z)

    local wetnessBase = fmihGetWeatherWetness()
    local wetness = wetnessBase
    local localCell = nil

    if not self:getIsHardWinterFrozen() and FieldLocalWetness ~= nil and FieldLocalWetness.getEffectiveWetnessAt ~= nil then
        wetness = FieldLocalWetness:getEffectiveWetnessAt(x, z, wetnessBase, nil, prof)
        if FieldLocalWetness.enabled == true and FieldLocalWetness.getWetnessAt ~= nil then
            local _, cell = FieldLocalWetness:getWetnessAt(x, z, nil)
            localCell = cell
        end
    end

    local permaMul = 1.0
    if FieldLocalWetness ~= nil and FieldLocalWetness.getPermaChanceMultiplierAt ~= nil then
        permaMul = FieldLocalWetness:getPermaChanceMultiplierAt(x, z, wetness, nil, prof)
    end

    local baseChance = (FieldGroundMudPhysics.permaStuckChanceOnStruggle or 0)
    local wetRisk = fmihClamp((wetness - (FieldGroundMudPhysics.wetnessThreshold or 0.12)) / 0.65, 0, 1)
    local mudRisk = 0
    local profileRisk = 0
    if prof ~= nil then
        mudRisk = fmihClamp((mudBase or 0) * ((prof.sinkMul or 1) / 7.0), 0, 1)
        profileRisk = fmihClamp(
            (mudBase or 0) * 0.30
            + ((prof.wetMul or 1.0) - 1.0) * 0.20
            + ((prof.sinkMul or 5.4) - 5.4) * 0.08
            + ((prof.brakeMul or 4.0) - 4.0) * 0.04,
            0, 1)
    end
    local lowland = (localCell ~= nil and localCell.lowland) or 0
    local risk = fmihClamp(wetRisk * 0.42 + mudRisk * 0.26 + lowland * 0.22 + profileRisk * 0.10, 0, 1)
    local chancePerSec = 0
    if FieldGroundMudPhysics.permaStuckEnable == true and prof ~= nil and prof.permaStuck == true then
        chancePerSec = fmihClamp(baseChance * permaMul * fmihClamp(0.25 + wetRisk * 0.75, 0, 1), 0, 1)
    end
    if self:getIsHardWinterFrozen() then
        chancePerSec = 0
        risk = 0
    end

    return {
        wetness = fmihClamp(wetness, 0, 1),
        lowland = lowland,
        slope = (localCell ~= nil and localCell.slope) or 0,
        risk = risk,
        chancePerSec = chancePerSec,
        slip = 0,
        speedKph = 0,
        localMode = (FieldLocalWetness ~= nil and FieldLocalWetness.enabled == true)
    }
end

function FieldMudInfoHUD:update(dt)
    if not self:getIsActive() then
        self._cache = nil
        self.dragging = false
        return
    end

    if self.dragging
        and (g_inputBinding == nil or g_inputBinding.getShowMouseCursor == nil or not g_inputBinding:getShowMouseCursor()) then
        self.dragging = false
    end

    local now = g_time or 0
    if now - (self._lastUpdateMs or 0) < (self.updateIntervalMs or 250) then
        return
    end

    self._lastUpdateMs = now

    local vehicle = fmihGetControlledVehicle()
    if vehicle ~= nil then
        self._cache = self:collectVehicleInfo(vehicle)
    else
        local x, z = fmihGetPlayerXZ()
        self._cache = self:collectPointInfo(x, z)
    end
end

function FieldMudInfoHUD:getLayoutMetrics()
    if not self:getIsActive() or self._cache == nil or g_currentMission == nil or g_currentMission.hud == nil then
        return nil
    end

    local hud = g_currentMission.hud
    local gid = hud.gameInfoDisplay
    if gid == nil or gid.getPosition == nil then
        return nil
    end

    local posX, posY = gid:getPosition()
    local sx = gid.scalePixelToScreenWidth ~= nil
        and function(px) return gid:scalePixelToScreenWidth(px) end
        or function(px) return px / 1920 end
    local sy = gid.scalePixelToScreenHeight ~= nil
        and function(px) return gid:scalePixelToScreenHeight(px) end
        or function(px) return px / 1080 end

    local lineCount = 2
    local blockW = sx(self.widthPx or 255)
    local blockH = sy(14 + lineCount * (self.lineHeightPx or 16))
    local gapY = sy(6)
    local offX = sx(self.offsetXPx or 347)
    local offY = sy(self.offsetYPx or -154)
    local x0 = posX - offX - blockW
    local y0 = (posY + gapY) + offY

    return {
        gid = gid,
        posX = posX,
        posY = posY,
        sx = sx,
        sy = sy,
        sx1 = sx(1),
        sy1 = sy(1),
        gapY = gapY,
        x0 = x0,
        y0 = y0,
        blockW = blockW,
        blockH = blockH
    }
end

function FieldMudInfoHUD:getCanDragWithMouse()
    if not self:getIsActive() or g_inputBinding == nil or g_inputBinding.getShowMouseCursor == nil then
        return false
    end
    return g_inputBinding:getShowMouseCursor() == true
end

function FieldMudInfoHUD:isMouseOverHud(posX, posY, layout)
    return layout ~= nil
        and posX >= layout.x0 and posX <= layout.x0 + layout.blockW
        and posY >= layout.y0 and posY <= layout.y0 + layout.blockH
end

function FieldMudInfoHUD:applyDraggedPosition(newX0, newY0, layout, saveNow)
    if layout == nil then return end

    local pxPerUnitX = layout.sx1 ~= 0 and layout.sx1 or (1 / 1920)
    local pxPerUnitY = layout.sy1 ~= 0 and layout.sy1 or (1 / 1080)

    self.offsetXPx = fmihClamp(math.floor(((layout.posX - (newX0 + layout.blockW)) / pxPerUnitX) + 0.5), -2600, 2600)
    self.offsetYPx = fmihClamp(math.floor(((newY0 - (layout.posY + layout.gapY)) / pxPerUnitY) + 0.5), -2400, 1200)

    if saveNow and MudSystemSettings ~= nil and type(MudSystemSettings.writeSettings) == "function" then
        MudSystemSettings:writeSettings()
    end
end

function FieldMudInfoHUD:mouseEvent(posX, posY, isDown, isUp, button)
    local layout = self:getLayoutMetrics()
    if layout == nil then
        return
    end

    local canDrag = self:getCanDragWithMouse()

    if isDown then
        if button ~= Input.MOUSE_BUTTON_LEFT then
            return
        end
        if not canDrag or not self:isMouseOverHud(posX, posY, layout) then
            return
        end

        self.dragging = true
        self.dragOffsetX = posX - layout.x0
        self.dragOffsetY = posY - layout.y0
        return
    end

    if isUp then
        if self.dragging then
            self:applyDraggedPosition(posX - self.dragOffsetX, posY - self.dragOffsetY, layout, true)
        end
        self.dragging = false
        return
    end

    if self.dragging then
        if not canDrag then
            self.dragging = false
            return
        end
        self:applyDraggedPosition(posX - self.dragOffsetX, posY - self.dragOffsetY, layout, false)
    end
end

function FieldMudInfoHUD:draw()
    local layout = self:getLayoutMetrics()
    if layout == nil then
        return
    end

    local hud = g_currentMission.hud
    if hud.getIsVisible ~= nil and not hud:getIsVisible() then return end
    if hud.isVisible ~= nil and not hud.isVisible then return end

    local gid = hud.gameInfoDisplay
    if gid ~= nil and gid.getVisible ~= nil and not gid:getVisible() then
        return
    end

    local sx = layout.sx
    local sy = layout.sy

    local info = self._cache
    local riskText, riskColor = fmihRiskText(info.risk)

    local lines = {
        { fmihGetText("mudfieldinfo_wetness", "Wetness"), fmihFmtPercent(info.wetness), { 0.78, 0.90, 1.00, 1 } },
        { fmihGetText("mudfieldinfo_risk", "Stuck risk"), riskText, riskColor }
    }

    local x = layout.x0
    local y = layout.y0
    local w = layout.blockW
    local h = layout.blockH

    if self._bgLeft ~= nil and self._bgMiddle ~= nil and self._bgRight ~= nil then
        local capW = sx(10)
        local leftW = capW
        local rightW = capW
        local midW = math.max(0, w - leftW - rightW)
        local a = self.bgAlpha or 0.52

        self._bgLeft:setColor(0, 0, 0, a)
        self._bgMiddle:setColor(0, 0, 0, a)
        self._bgRight:setColor(0, 0, 0, a)

        self._bgLeft:setPosition(x, y)
        self._bgLeft:setDimension(leftW, h)
        self._bgLeft:render()

        self._bgMiddle:setPosition(x + leftW, y)
        self._bgMiddle:setDimension(midW, h)
        self._bgMiddle:render()

        self._bgRight:setPosition(x + leftW + midW, y)
        self._bgRight:setDimension(rightW, h)
        self._bgRight:render()
    elseif self._bgOverlay ~= nil then
        self._bgOverlay:setPosition(x, y)
        self._bgOverlay:setDimension(w, h)
        self._bgOverlay:setColor(0, 0, 0, self.bgAlpha or 0.52)
        self._bgOverlay:render()
    end

    local textSize = sy(13)
    local padX = sx(10)
    local topY = y + h - sy(20)

    setTextAlignment(RenderText.ALIGN_LEFT)
    setTextBold(false)
    for i, line in ipairs(lines) do
        local lineY = topY - sy((i - 1) * (self.lineHeightPx or 16))
        setTextColor(0.82, 0.82, 0.82, self.textAlpha or 0.92)
        renderText(x + padX, lineY, textSize, line[1])
        setTextAlignment(RenderText.ALIGN_RIGHT)
        local c = line[3] or { 1, 1, 1, 1 }
        setTextColor(c[1], c[2], c[3], (self.textAlpha or 0.92) * (c[4] or 1))
        renderText(x + w - padX, lineY, textSize, line[2])
        setTextAlignment(RenderText.ALIGN_LEFT)
    end

    setTextColor(1, 1, 1, 1)
    setTextBold(false)
end
