-- Подключаемся к глобальной таблице
RadialVehicleSwitcher = RadialVehicleSwitcher or {}

-- Локальные функции-помощники только для интерфейса
local function getL10n(key, fallback)
    if g_i18n ~= nil and g_i18n:hasText(key) then
        return g_i18n:getText(key)
    end
    return fallback
end

local function drawTextWithShadow(x, y, size, text, r, g, b, a)
    setTextColor(0, 0, 0, a * 0.8)
    renderText(x + 0.0015, y - 0.0015 * g_screenAspectRatio, size, text)
    setTextColor(r, g, b, a)
    renderText(x, y, size, text)
end

function RadialVehicleSwitcher:playUISound(sample, volume)
    -- Мы не будем делать сложные настройки, просто проверим, загружен ли звук
    if sample ~= nil then
        -- Играем 1 раз, с заданной громкостью (обычно 0.5 достаточно)

        -- stopSample(sample, 0, 0)
        playSample(sample, 1, volume or 0.5, 0, 0, 0)
    end
end

-- Бронебойная функция получения клавиши прямо из настроек игры
local function getDynamicKey(actionId, defaultMouse, defaultPad)
    if g_inputBinding == nil or actionId == nil then
        return defaultMouse
    end
    local isGamepad = g_inputBinding:getInputHelpMode() == GS_INPUT_HELP_MODE_GAMEPAD

    local resultText = nil

    -- Метод 1: Штатный запрос к движку
    pcall(function()
        if type(g_inputBinding.getButtonNamesOfAction) == "function" then
            resultText = g_inputBinding:getButtonNamesOfAction(actionId)
        end
    end)

    -- Метод 2: Ручное сканирование настроек (если движок тупит)
    if resultText == nil or resultText == "" then
        pcall(function()
            local action = nil
            if g_inputBinding.nameToAction then
                action = g_inputBinding.nameToAction[actionId]
            end
            if not action and type(g_inputBinding.getActionByName) == "function" then
                action = g_inputBinding:getActionByName(actionId)
            end

            if action and action.bindings then
                -- Ищем бинд, который соответствует нашему устройству (клавиатура или геймпад)
                for _, binding in ipairs(action.bindings) do
                    local isBindingPad = (binding.deviceCategory == InputDevice.CATEGORY.GAMEPAD)
                    if isGamepad == isBindingPad then
                        if binding.axisNames and #binding.axisNames > 0 then
                            resultText = table.concat(binding.axisNames, " + ")
                            break
                        elseif binding.buttonNames and #binding.buttonNames > 0 then
                            resultText = table.concat(binding.buttonNames, " + ")
                            break
                        end
                    end
                end
            end
        end)
    end

    -- Наводим красоту на полученный текст
    if resultText ~= nil and resultText ~= "" then
        -- 1. Делаем все буквы заглавными
        resultText = string.upper(resultText)

        -- 2. Убираем лишний префикс KEY_
        resultText = string.gsub(resultText, "KEY_", "")

        -- 3. Переводим основные кнопки мыши на международные сокращения (Left/Right/Middle Mouse Button)
        resultText = string.gsub(resultText, "MOUSE_BUTTON_LEFT", "LMB")
        resultText = string.gsub(resultText, "MOUSE_BUTTON_RIGHT", "RMB")
        resultText = string.gsub(resultText, "MOUSE_BUTTON_MIDDLE", "MMB")
        resultText = string.gsub(resultText, "MOUSE_BUTTON_X", "MB")

        -- 4. Красивые кнопки геймпада (по стандарту XInput)
        -- Важно: двузначные цифры (10, 11) меняем ПЕРЕД однозначными (1), чтобы BUTTON_10 не превратилось в "A0"
        resultText = string.gsub(resultText, "BUTTON_10", "RS")
        resultText = string.gsub(resultText, "BUTTON_9", "LS")
        resultText = string.gsub(resultText, "BUTTON_8", "START")
        resultText = string.gsub(resultText, "BUTTON_7", "BACK")
        resultText = string.gsub(resultText, "BUTTON_6", "RB")
        resultText = string.gsub(resultText, "BUTTON_5", "LB")
        resultText = string.gsub(resultText, "BUTTON_4", "Y")
        resultText = string.gsub(resultText, "BUTTON_3", "B")
        resultText = string.gsub(resultText, "BUTTON_2", "A")
        resultText = string.gsub(resultText, "BUTTON_1", "X")

        return resultText
    end

    return isGamepad and defaultPad or defaultMouse
end

-- Главная функция отрисовки интерфейса
function RadialVehicleSwitcher:draw()
    if not self.isMenuOpen or self.currentVehicles == nil then
        return
    end

    -- === ЗАГРУЗКА ИКОНКИ КОНТРАКТА (Единожды) ===
    if self.missionIconOverlay == nil and not self.missionIconError then
        local iconPath = self.modDir .. "contract_icon.dds"
        if fileExists(iconPath) then
            self.missionIconOverlay = Overlay.new(iconPath, 0, 0, 0, 0)
        else
            self.missionIconError = true
        end
    end
    -- ============================================

    local animProgress = math.min(self.animTime / 250, 1.0)
    local popScale = 1 - math.pow(1 - animProgress, 4)
    local globalAlpha = animProgress

    drawFilledRect(0, 0, 1, 1, 0, 0, 0, 0.75 * globalAlpha)

    setTextAlignment(RenderText.ALIGN_CENTER)
    setTextBold(true)

    local centerX, centerY = 0.5, 0.5
    local baseRadiusY = 0.25 -- Раздвинули круг, чтобы 9+ элементов дышали свободно
    local radiusY = baseRadiusY * popScale
    local radiusX = radiusY / g_screenAspectRatio

    -- === ДИНАМИЧЕСКИЙ ДИЗАЙН РЕЖИМОВ ===
    -- Сначала объявляем переменные
    local titleMain = getL10n("RVS_MAIN_TITLE", "АВТОПАРК")
    local titleSub = getL10n("RVS_SUB_TITLE", "ВЫБОР ТЕХНИКИ")
    local textEmpty = getL10n("RVS_EMPTY", "ПУСТО")

    local dotR, dotG, dotB = 1, 1, 1 -- Обычный белый цвет для центра
    local titleR, titleG, titleB = 1, 0.9, 0.2 -- Классический желтый заголовок

    -- Переопределяем их, если включен режим ИИ
    if self.activeMode == "COMMAND" then
        titleMain = getL10n("RVS_AI_TITLE", "ПРИКАЗЫ ИИ")
        titleSub = getL10n("RVS_AI_SUB", "ВЫБОР КАТЕГОРИИ")
        dotR, dotG, dotB = 0.2, 0.8, 1.0 -- Голубой цвет "высоких технологий"
        titleR, titleG, titleB = 0.2, 0.8, 1.0
    end
    -- ===================================

    -- Теперь, когда dotR, dotG, dotB точно существуют, рисуем центральную точку
    drawFilledRect(centerX - 0.002, centerY - 0.002 * g_screenAspectRatio, 0.004, 0.004 * g_screenAspectRatio, dotR,
        dotG, dotB, 0.6 * globalAlpha)

    local currentLevelItems = {}
    local maxPages = 1

    -- 1. Достаем текущие кнопки (Строго через системный InputAction!)
    local keySelect = getDynamicKey(InputAction.RVS_SELECT, "LMB", "X")
    local keyFav = getDynamicKey(InputAction.RVS_FAVORITE, "MMB", "Y")
    local keyBack = getDynamicKey(InputAction.RVS_BACK, "RMB", "B")
    local keyLeft = getDynamicKey(InputAction.RVS_PAGE_LEFT, "A", "LB")
    local keyRight = getDynamicKey(InputAction.RVS_PAGE_RIGHT, "D", "RB")
    local keyToggle = getDynamicKey(InputAction.RVS_TOGGLE_MODE, "LSHIFT", "RB")

    -- 2. Загружаем локализацию с маркерами %s
    local rawHintOpen = getL10n("RVS_HINT_OPEN", "%s - Выбрать | %s - Режим")
    local rawHintBase = getL10n("RVS_HINT_BASE", "%s - Избранное | %s - Назад")
    local rawHintPage = getL10n("RVS_HINT_PAGE", " | %s/%s - Стр. ")

    -- 3. Вклеиваем кнопки прямо в текст
    local hintOpen = string.format(rawHintOpen, keySelect, keyToggle)
    local hintBase = string.format(rawHintBase, keyFav, keyBack)
    local hintPage = string.format(rawHintPage, keyLeft, keyRight)

    if self.menuLevel == 1 then
        -- НОВОЕ: Подменяем список на лету в зависимости от режима
        if self.activeMode == "COMMAND" then
            currentLevelItems = self.commandCategories or {}
        else
            currentLevelItems = {{
                name = getL10n("RVS_CAT_NEARBY", "РЯДОМ"),
                count = #self.currentVehicles.NEARBY,
                color = {0.2, 1.0, 0.2},
                isAvailable = true
            }, {
                name = getL10n("RVS_CAT_FAV", "ИЗБРАННОЕ"),
                count = #self.currentVehicles.FAVORITES,
                color = {1, 0.8, 0},
                isAvailable = true
            }, {
                name = getL10n("RVS_CAT_AI", "НАЙМИТЫ"),
                count = #self.currentVehicles.WORKERS,
                color = {0.2, 0.8, 1},
                isAvailable = true
            }, {
                name = getL10n("RVS_CAT_TRACTORS", "ТРАКТОРЫ"),
                count = #self.currentVehicles.TRACTORS,
                color = {1, 1, 1},
                isAvailable = true
            }, {
                name = getL10n("RVS_CAT_COMBINES", "КОМБАЙНЫ"),
                count = #self.currentVehicles.COMBINES,
                color = {1, 1, 1},
                isAvailable = true
            }, {
                name = getL10n("RVS_CAT_TRUCKS", "ГРУЗОВИКИ"),
                count = #self.currentVehicles.TRUCKS,
                color = {1, 1, 1},
                isAvailable = true
            }, {
                name = getL10n("RVS_CAT_LOADERS", "ПОГРУЗЧИКИ"),
                count = #self.currentVehicles.LOADERS,
                color = {1, 1, 1},
                isAvailable = true
            }, {
                name = getL10n("RVS_CAT_CARS", "ЛЕГКОВЫЕ"),
                count = #self.currentVehicles.CARS,
                color = {1, 1, 1},
                isAvailable = true
            }, {
                name = getL10n("RVS_CAT_MISC", "РАЗНОЕ"),
                count = #self.currentVehicles.MISC,
                color = {1, 1, 1},
                isAvailable = true
            }}
        end
    elseif self.menuLevel == 2 then
        local totalItems = #self.selectedCategoryVehicles
        maxPages = math.max(1, math.ceil(totalItems / self.maxItemsPerPage))
        local startIndex = (self.currentPage - 1) * self.maxItemsPerPage + 1
        local endIndex = math.min(startIndex + self.maxItemsPerPage - 1, totalItems)

        for i = startIndex, endIndex do
            table.insert(currentLevelItems, self.selectedCategoryVehicles[i])
        end
    end

    local numItems = #currentLevelItems

    local crowdFactor = 1.0
    if numItems > 7 then
        crowdFactor = math.max(0.75, 7 / numItems)
    end

    -- === БЛОК: РАСЧЕТ УГЛА ДЛЯ ГЕЙМПАДА И МЫШИ ===
    local newHoveredIndex = 0
    local mouseX, mouseY = g_lastMousePosX, g_lastMousePosY

    local dx, dy = 0, 0
    local dist = 0

    local isGamepadMode = g_inputBinding:getInputHelpMode() == GS_INPUT_HELP_MODE_GAMEPAD

    if isGamepadMode then
        dx = self.gamepadX or 0
        dy = -(self.gamepadY or 0)
        dist = math.sqrt(dx * dx + dy * dy)
    else
        if mouseX ~= nil and numItems > 0 then
            dx = (mouseX - centerX) * g_screenAspectRatio
            dy = (mouseY - centerY)
            dist = math.sqrt(dx * dx + dy * dy)
        end
    end

    local deadzone = isGamepadMode and 0.25 or 0.04

    if dist > deadzone and numItems > 0 then
        local targetAngle = math.atan2(dy, dx) + (math.pi / 2)
        if targetAngle < 0 then
            targetAngle = targetAngle + (math.pi * 2)
        end

        if self.animTime < 30 or self.currentAngle == nil then
            self.currentAngle = targetAngle
        end

        local diff = targetAngle - self.currentAngle
        while diff > math.pi do
            diff = diff - (math.pi * 2)
        end
        while diff < -math.pi do
            diff = diff + (math.pi * 2)
        end

        self.currentAngle = self.currentAngle + (diff * 0.35)

        if self.currentAngle < 0 then
            self.currentAngle = self.currentAngle + (math.pi * 2)
        end
        if self.currentAngle >= math.pi * 2 then
            self.currentAngle = self.currentAngle - (math.pi * 2)
        end

        local step = (math.pi * 2) / numItems
        local sector = self.currentAngle + (step / 2)
        if sector >= math.pi * 2 then
            sector = sector - (math.pi * 2)
        end

        newHoveredIndex = math.floor(sector / step) + 1
    else
        self.currentAngle = nil
    end

    -- === ФИКС "БАГА ОДНОГО КАДРА" ===
    if self.hoveredIndex ~= newHoveredIndex then
        -- Если курсор перешел на новый сектор, моментально глушим таймер 
        -- прямо перед отрисовкой, чтобы избежать скачков!
        self.itemHoverTime = 0
    end
    self.hoveredIndex = newHoveredIndex
    -- ====================================================

    if numItems > 1 then
        local step = (math.pi * 2) / numItems
        for i = 1, numItems do
            -- Берем прогресс удержания для текущего и следующего сектора (чтобы обе границы "росли")
            local nextIdx = (i % numItems) + 1
            local sWeight = math.max(self.selectionWeights[i] or 0, self.selectionWeights[nextIdx] or 0)

            -- Масштаб: от 1.0 (базовый) до 1.5 (максимум при зажатии)
            local currentScale = 1.0 + (sWeight * 2)

            local dividerAngle = (i - 1) * step - (math.pi / 2) + (step / 2)
            local numDots = 15
            local startRadius = 0.06 * popScale
            local endRadius = radiusY + (0.12 * crowdFactor)

            for d = 1, numDots do
                local t = d / numDots -- Дистанция от центра (0.0 - начало, 1.0 - край)
                local currentRadiusY = startRadius + (endRadius - startRadius) * t
                local currentRadiusX = currentRadiusY / g_screenAspectRatio
                local px = centerX + math.cos(dividerAngle) * currentRadiusX
                local py = centerY + math.sin(dividerAngle) * currentRadiusY

                -- 1. ЛОГИКА "ВОЛНЫ" ОТ ЦЕНТРА:
                -- Мы смещаем прогресс для каждой точки на её расстояние t.
                -- Точки у центра (t=0) начнут расти сразу, точки у края (t=1) — позже.
                local localProgress = math.max(0, math.min(1, (sWeight * 1.5) - (t * 0.5)))

                -- 2. РАЗНЫЙ МАКСИМАЛЬНЫЙ РАЗМЕР:
                -- У центра (t=0) прибавка +0.1 (итого 1.1)
                -- У края (t=1) прибавка +0.5 (итого 1.5)
                local maxBonus = 0.1 + (t * 0.4)
                local currentScale = 1.0 + (localProgress * maxBonus)

                local dotAlpha = math.sin(t * math.pi) * 0.15 * globalAlpha
                if self.hoveredIndex == i or self.hoveredIndex == nextIdx then
                    -- Яркость тоже нарастает по мере "зарядки" точки
                    dotAlpha = dotAlpha * (2.5 + localProgress * 3)
                end

                local baseSize = 0.002 * popScale
                local dotSize = baseSize * currentScale

                -- Отрисовка квадратика с учетом нового динамического размера
                drawFilledRect(px - dotSize / 2, py - (dotSize * g_screenAspectRatio) / 2, dotSize,
                    dotSize * g_screenAspectRatio, 1, 1, 1, dotAlpha)
            end
        end
    end

    if self.menuLevel == 1 then
        drawTextWithShadow(centerX, centerY + 0.02, 0.04 * popScale, titleMain, titleR, titleG, titleB, globalAlpha)
        drawTextWithShadow(centerX, centerY - 0.02, 0.015 * popScale, hintOpen, 0.7, 0.7, 0.7, globalAlpha)

        local delayStep = 20 -- Задержка в миллисекундах между появлением каждой категории
        for i, item in ipairs(currentLevelItems) do
            local angle = (i - 1) * (math.pi * 2 / #currentLevelItems) - (math.pi / 2)
            local hWeight = self.hoverWeights[i] or 0

            -- ИНДИВИДУАЛЬНАЯ АНИМАЦИЯ ЭЛЕМЕНТА
            local itemDelay = (i - 1) * delayStep
            local itemAnimTime = math.max(0, self.animTime - itemDelay)
            local itemAnimProgress = math.min(itemAnimTime / 200, 1.0) -- 200мс на появление одного элемента
            local itemPopScale = 1 - math.pow(1 - itemAnimProgress, 4)
            local itemAlpha = itemAnimProgress

            local hoverOffsetY = 0.015 * hWeight
            -- Элементы вылетают из центра за счет привязки радиуса к itemPopScale
            local currentRadiusY = (baseRadiusY * itemPopScale) + hoverOffsetY
            local currentRadiusX = currentRadiusY / g_screenAspectRatio

            local x = centerX + math.cos(angle) * currentRadiusX
            local y = centerY + math.sin(angle) * currentRadiusY

            -- Слегка сдвинули текст ближе друг к другу по вертикали
            local textY = y + 0.010
            local countY = y - 0.014

            -- Теперь размер текста умножается на crowdFactor: при 9 категориях он сам станет чуть меньше
            -- Теперь размер текста умножается на crowdFactor
            local textSize = (0.026 + (0.005 * hWeight)) * crowdFactor

            -- НОВОЕ: Логика затухания для недоступных задач (серый цвет)
            local isItemActive = (item.isAvailable ~= false)
            local alphaMult = isItemActive and 1.0 or 0.25

            local r, g, b = 0.5, 0.5, 0.5 -- Серый по умолчанию для неактивных
            if isItemActive then
                r = item.color[1] + (0.3 - item.color[1]) * hWeight
                g = item.color[2] + (1.0 - item.color[2]) * hWeight
                b = item.color[3] + (0.3 - item.color[3]) * hWeight
            end

            drawTextWithShadow(x, textY, textSize * itemPopScale, item.name, r, g, b, itemAlpha * alphaMult)
            drawTextWithShadow(x, countY, 0.015 * itemPopScale, "[" .. tostring(item.count) .. "]", 0.8, 0.8, 0.8,
                itemAlpha * alphaMult)
        end

    elseif self.menuLevel == 2 then
        local hint = hintBase
        if maxPages > 1 then
            hint = hint .. hintPage .. self.currentPage .. "/" .. maxPages
        end

        drawTextWithShadow(centerX, centerY + 0.02, 0.035 * popScale, titleSub, 1, 0.9, 0.2, globalAlpha)
        drawTextWithShadow(centerX, centerY - 0.02, 0.014 * popScale, hint, 0.7, 0.7, 0.7, globalAlpha)

        if #currentLevelItems == 0 then
            drawTextWithShadow(centerX, centerY - 0.06, 0.02 * popScale, textEmpty, 1, 1, 1, 0.5 * globalAlpha)
        end

        local delayStep = 25 -- Задержка вылета для техники (чуть больше, чтобы подчеркнуть каскад)
        for i, vInfo in ipairs(currentLevelItems) do
            local angle = (i - 1) * (math.pi * 2 / numItems) - (math.pi / 2)
            local hWeight = self.hoverWeights[i] or 0

            -- ИНДИВИДУАЛЬНАЯ АНИМАЦИЯ ЭЛЕМЕНТА
            local itemDelay = (i - 1) * delayStep
            local itemAnimTime = math.max(0, self.animTime - itemDelay)
            local itemAnimProgress = math.min(itemAnimTime / 250, 1.0)
            local itemPopScale = 1 - math.pow(1 - itemAnimProgress, 4)
            local itemAlpha = itemAnimProgress

            local hoverOffsetY = 0.02 * hWeight
            local currentRadiusY = (baseRadiusY * itemPopScale) + hoverOffsetY
            local currentRadiusX = currentRadiusY / g_screenAspectRatio

            local x = centerX + math.cos(angle) * currentRadiusX
            local y = centerY + math.sin(angle) * currentRadiusY

            local iconCenterY = y + (0.035 * crowdFactor)
            local textCenterY = y - (0.040 * crowdFactor) - (0.01 * hWeight)

            local isFav = self.favoritesMap[vInfo.id]

            -- [Код загрузки иконки остается без изменений]
            local imagePath = nil
            if vInfo.vehicle.configFileName ~= nil and g_storeManager ~= nil then
                local storeItem = g_storeManager:getItemByXMLFilename(vInfo.vehicle.configFileName)
                if storeItem ~= nil and storeItem.imageFilename ~= nil then
                    imagePath = storeItem.imageFilename
                end
            end

            if imagePath == nil and vInfo.vehicle.getImageFilename ~= nil then
                imagePath = vInfo.vehicle:getImageFilename()
            end

            if vInfo.iconOverlay == nil and not vInfo.iconError then
                if imagePath ~= nil then
                    local finalPath = imagePath
                    if not fileExists(finalPath) then
                        finalPath = Utils.getFilename(imagePath, vInfo.vehicle.customEnvironment)
                    end
                    if finalPath ~= nil and finalPath ~= "" then
                        local isBaseGame = string.sub(finalPath, 1, 5) == "data/" or string.sub(finalPath, 1, 6) ==
                                               "$data/"
                        if isBaseGame or fileExists(finalPath) then
                            vInfo.iconOverlay = Overlay.new(finalPath, 0, 0, 0, 0)
                        else
                            vInfo.iconError = true
                        end
                    else
                        vInfo.iconError = true
                    end
                else
                    vInfo.iconError = true
                end
            end

            if vInfo.iconError and vInfo.iconOverlay == nil then
                local fallbackPath = RadialVehicleSwitcher.modDir .. "default_icon.dds"
                if fileExists(fallbackPath) then
                    vInfo.iconOverlay = Overlay.new(fallbackPath, 0, 0, 0, 0)
                end
            end

            local hasImplements = (vInfo.implements ~= nil and #vInfo.implements > 0)
            local implAlpha = 0
            local shiftProgress = 0

            if hasImplements and self.hoveredIndex == i and self.itemHoverTime ~= nil and self.itemHoverTime > 300 then
                -- Делаем линейный прогресс для прозрачности (от 0 до 1)
                local linearProgress = math.min((self.itemHoverTime - 300) / 200, 1.0)
                implAlpha = linearProgress
                -- Добавляем кривую ease-out (кубическую) для сочного, упругого вылета
                shiftProgress = 1 - math.pow(1 - linearProgress, 3)
            end

            local vx = math.cos(angle)
            local vy = math.sin(angle)

            local tx = -vy
            local ty = vx

            -- === 1. СКРЫТАЯ ПРЕДЗАГРУЗКА ИКОНОК ПРИЦЕПОВ (БЕЗ ФРИЗОВ) ===
            -- Мы перенесли это сюда, чтобы игра загрузила картинки в память ДО начала анимации
            if hasImplements then
                local numImpl = #vInfo.implements
                if vInfo.implOverlays == nil then
                    vInfo.implOverlays = {}
                end

                for implIdx = 1, numImpl do
                    local implVehicle = vInfo.implements[implIdx]
                    if vInfo.implOverlays[implIdx] == nil and not vInfo.implError then
                        local imgPath = nil
                        if implVehicle.configFileName ~= nil and g_storeManager ~= nil then
                            local sItem = g_storeManager:getItemByXMLFilename(implVehicle.configFileName)
                            if sItem ~= nil then
                                imgPath = sItem.imageFilename
                            end
                        end
                        if imgPath == nil and implVehicle.getImageFilename ~= nil then
                            imgPath = implVehicle:getImageFilename()
                        end

                        if imgPath ~= nil then
                            local finalPath = imgPath
                            if not fileExists(finalPath) then
                                finalPath = Utils.getFilename(imgPath, implVehicle.customEnvironment)
                            end
                            if finalPath ~= nil and finalPath ~= "" then
                                local isBaseGame =
                                    string.sub(finalPath, 1, 5) == "data/" or string.sub(finalPath, 1, 6) == "$data/"
                                if isBaseGame or fileExists(finalPath) then
                                    vInfo.implOverlays[implIdx] = Overlay.new(finalPath, 0, 0, 0, 0)
                                else
                                    vInfo.implOverlays[implIdx] = "error"
                                end
                            else
                                vInfo.implOverlays[implIdx] = "error"
                            end
                        else
                            vInfo.implOverlays[implIdx] = "error"
                        end
                    end
                end
            end
            -- ============================================================

            -- === ДОБАВЛЯЕМ ВЫЧИСЛЕНИЕ ДРОЖАНИЯ (SHAKE) И ВСПЫШКИ ===
            local sWeight = self.selectionWeights[i] or 0
            local shakeX = 0
            local shakeY = 0

            local isFlashing = (self.isConfirming and self.confirmedIndex == i)
            local flashScale = 1.0
            local flashGlow = 0

            if isFlashing then
                -- Вычисляем прогресс вспышки (используем синусоиду для упругого прыжка)
                local fProgress = math.max(0, self.confirmTimer / 150)
                flashScale = 1.0 + (math.sin(fProgress * math.pi) * 0.4) -- Увеличится на 40%
                flashGlow = fProgress
            elseif sWeight > 0 then
                local intensity = sWeight * 0.003 * crowdFactor
                shakeX = (math.random() - 0.5) * intensity
                shakeY = (math.random() - 0.5) * intensity * g_screenAspectRatio
            end

            --- === 2. ОТРИСОВКА ОСНОВНОЙ ИКОНКИ ===
            if vInfo.iconOverlay ~= nil then
                local mainShift = 0.035 * crowdFactor * shiftProgress
                local finalIconX = x - (vx * mainShift) / g_screenAspectRatio + shakeX
                local finalIconY = iconCenterY - (vy * mainShift) + shakeY

                local baseSize = (0.11 + (0.04 * hWeight)) * itemPopScale * crowdFactor
                local sizeScale = 1.0 - (0.3 * shiftProgress)

                -- ПРИМЕНЯЕМ МАСШТАБ ВСПЫШКИ
                local size = baseSize * sizeScale * flashScale

                vInfo.iconOverlay:setPosition(finalIconX - size / 2, finalIconY - (size * g_screenAspectRatio) / 2)
                vInfo.iconOverlay:setDimension(size, size * g_screenAspectRatio)

                local iconAlpha = (0.6 + (0.4 * hWeight)) * itemAlpha
                local r, g, b = 1, 1, 1

                -- ЦВЕТОВЫЕ ЭФФЕКТЫ (Вспышка или покраснение)
                if isFlashing then
                    iconAlpha = 1.0
                    g = 1.0 - (flashGlow * 0.2)
                    b = 1.0 - (flashGlow * 0.8)
                elseif sWeight > 0.5 then
                    local colorTension = (sWeight - 0.5) * 2
                    g = 1.0 - (colorTension * 0.4)
                    b = 1.0 - (colorTension * 0.4)
                end

                vInfo.iconOverlay:setColor(r, g, b, iconAlpha)
                vInfo.iconOverlay:render()

                -- === ОТРИСОВКА ИКОНКИ КОНТРАКТА ===
                -- if vInfo.isMission then
                --     -- Размер иконки контракта (чуть меньше главной)
                --     local mSize = 0.025 * itemPopScale * crowdFactor

                --     -- Размещаем в правом верхнем углу главной иконки
                --     local mX = finalIconX + size/2 - mSize * 1.1
                --     local mY = finalIconY + (size * g_screenAspectRatio)/2 - (mSize * g_screenAspectRatio) * 1.1

                --     if self.missionIconOverlay ~= nil then
                --         -- Рисуем твою кастомную DDS иконку
                --         self.missionIconOverlay:setPosition(mX, mY)
                --         self.missionIconOverlay:setDimension(mSize, mSize * g_screenAspectRatio)
                --         self.missionIconOverlay:setColor(1, 1, 1, iconAlpha)
                --         self.missionIconOverlay:render()
                --     else
                --         -- ФОЛБЭК: Рисуем оранжевый бейдж с "$"
                --         local badgeW = mSize
                --         local badgeH = mSize * g_screenAspectRatio

                --         -- Черная обводка для контраста
                --         drawFilledRect(mX - 0.001, mY - 0.001 * g_screenAspectRatio, badgeW + 0.002, badgeH + 0.002 * g_screenAspectRatio, 0, 0, 0, iconAlpha)

                --         -- Сам оранжевый квадратик
                --         drawFilledRect(mX, mY, badgeW, badgeH, 0.9, 0.5, 0.1, iconAlpha * 0.9)

                --         setTextAlignment(RenderText.ALIGN_CENTER)
                --         setTextBold(true)
                --         drawTextWithShadow(mX + badgeW/2, mY + 0.003, mSize * 0.7, "$", 1, 1, 1, iconAlpha)

                --         -- Возвращаем настройки текста обратно
                --         setTextAlignment(RenderText.ALIGN_LEFT)
                --         setTextBold(false)
                --     end
                -- end
                -- ==================================
            end

            -- === 3. ОТРИСОВКА ПРИЦЕПОВ (ТЕПЕРЬ ПЛАВНАЯ) ===
            if implAlpha > 0 and hasImplements then
                local numImpl = #vInfo.implements
                local implSize = (0.06 + (0.02 * hWeight)) * popScale * crowdFactor

                local outDist = 0.08 * crowdFactor * shiftProgress
                local implBaseX = x + (vx * outDist) / g_screenAspectRatio
                local implBaseY = iconCenterY + (vy * outDist)

                local spreadDist = implSize * 1.3

                for implIdx = 1, numImpl do
                    local over = vInfo.implOverlays[implIdx]
                    -- Картинки уже загружены шагом 1, так что тут только рендер!
                    if over ~= nil and over ~= "error" then
                        local offset = (implIdx - 1 - (numImpl - 1) / 2) * spreadDist
                        local ix = implBaseX + (tx * offset) / g_screenAspectRatio
                        local iy = implBaseY + (ty * offset)

                        local animScale = 0.5 + 0.5 * shiftProgress
                        local finalImplSize = implSize * animScale

                        over:setPosition(ix - finalImplSize / 2, iy - (finalImplSize * g_screenAspectRatio) / 2)
                        over:setDimension(finalImplSize, finalImplSize * g_screenAspectRatio)
                        over:setColor(1, 1, 1, implAlpha * globalAlpha)
                        over:render()

                        if self.activeMode == "COMMAND" and self.hoveredIndex == i then

                            -- === ИКОНКА ТЕКУЩЕГО СЕМЕНИ СЕЯЛКИ (без кэша) ===
                            local implVehicle = vInfo.implements[implIdx]

                            -- Если у прицепа нет сеялки, ищем глубже
                            if implVehicle ~= nil and implVehicle.spec_sowingMachine == nil and
                                implVehicle.getAttachedImplements ~= nil then
                                for _, sub in ipairs(implVehicle:getAttachedImplements()) do
                                    if sub.object ~= nil and sub.object.spec_sowingMachine ~= nil then
                                        implVehicle = sub.object
                                        break
                                    end
                                end
                            end

                            if implVehicle ~= nil and implVehicle.spec_sowingMachine ~= nil then
                                local iconPath = nil
                                local spec = implVehicle.spec_sowingMachine

                                -- ПОПЫТКА 1: getSowingMachineSeedFillTypeIndex
                                if implVehicle.getSowingMachineSeedFillTypeIndex ~= nil then
                                    local fillTypeIndex = implVehicle:getSowingMachineSeedFillTypeIndex()
                                    local fillType = g_fillTypeManager:getFillTypeByIndex(fillTypeIndex)
                                    if fillType ~= nil and fillType.hudOverlayFilename ~= nil then
                                        iconPath = fillType.hudOverlayFilename
                                    end
                                end

                                -- ПОПЫТКА 2: через currentSeed слот
                                if iconPath == nil and spec.currentSeed ~= nil and spec.seeds ~= nil then
                                    local fruitTypeIndex = spec.seeds[spec.currentSeed]
                                    local fruitType = fruitTypeIndex ~= nil and
                                                          g_fruitTypeManager:getFruitTypeByIndex(fruitTypeIndex) or nil
                                    if fruitType ~= nil then
                                        if fruitType.fillType ~= nil and fruitType.fillType.hudOverlayFilename ~= nil then
                                            iconPath = fruitType.fillType.hudOverlayFilename
                                        elseif g_fillTypeManager ~= nil then
                                            local ft = g_fillTypeManager:getFillTypeByName(fruitType.name)
                                            if ft ~= nil and ft.hudOverlayFilename ~= nil then
                                                iconPath = ft.hudOverlayFilename
                                            end
                                        end
                                    end
                                end

                                -- Рисуем если нашли путь и он изменился — пересоздаём оверлей
                                if iconPath ~= nil then
                                    if implVehicle._rvsSeedOverlayPath ~= iconPath then
                                        implVehicle._rvsSeedOverlayPath = iconPath
                                        implVehicle._rvsSeedOverlay = Overlay.new(iconPath, 0, 0, 0, 0)
                                    end

                                    local seedOver = implVehicle._rvsSeedOverlay
                                    if seedOver ~= nil and seedOver.render ~= nil then
                                        local seedSize = finalImplSize * 0.45
                                        local sX = ix - seedSize / 2
                                        local sY = iy - (finalImplSize * g_screenAspectRatio) / 2 -
                                                       (seedSize * g_screenAspectRatio) * 1.3

                                        seedOver:setPosition(sX, sY)
                                        seedOver:setDimension(seedSize, seedSize * g_screenAspectRatio)
                                        local spec = implVehicle.spec_sowingMachine
                                        local fruitTypeIndex =
                                            spec.seeds ~= nil and spec.currentSeed ~= nil and
                                                spec.seeds[spec.currentSeed] or nil
                                        local isPlantable = fruitTypeIndex == nil or
                                                                RadialVehicleSwitcher:isSeedPlantableNow(fruitTypeIndex)

                                        local seedR, seedG, seedB, seedA = 1, 1, 1, implAlpha * globalAlpha
                                        if not isPlantable then
                                            seedR, seedG, seedB = 0.4, 0.4, 0.4
                                            seedA = implAlpha * globalAlpha * 0.5
                                        end

                                        seedOver:setColor(seedR, seedG, seedB, seedA)
                                        seedOver:render()
                                    end
                                end
                            end
                            -- =====================================
                        end
                    end
                end
            end

            -- Полоска наймита с учетом анимации вылета
            if vInfo.isAI then
                local tFill, tCap, dominantFillType = self:getVehicleTreeFill(vInfo.vehicle)
                if tCap > 0 then
                    local fillPct = math.min(tFill / tCap, 1.0)
                    local pctText = string.format("%d%%", math.floor(fillPct * 100))

                    local barW = 0.08 * itemPopScale * crowdFactor
                    local barH = 0.004 * g_screenAspectRatio * itemPopScale * crowdFactor
                    local barX = x - barW / 2
                    local barY = textCenterY - (0.015 * crowdFactor)

                    drawFilledRect(barX, barY, barW, barH, 0, 0, 0, 0.5 * itemAlpha)

                    local fillR, fillG, fillB = 0.2, 0.8, 0.2
                    if fillPct > 0.80 then
                        fillR, fillG, fillB = 0.9, 0.2, 0.2
                    elseif fillPct > 0.50 then
                        fillR, fillG, fillB = 0.9, 0.7, 0.1
                    end

                    if fillPct > 0 then
                        drawFilledRect(barX, barY, barW * fillPct, barH, fillR, fillG, fillB, 0.9 * itemAlpha)
                    end

                    if dominantFillType ~= nil and dominantFillType ~= FillType.UNKNOWN and g_fillTypeManager ~= nil then
                        local fillTypeDesc = g_fillTypeManager:getFillTypeByIndex(dominantFillType)
                        if fillTypeDesc ~= nil and fillTypeDesc.hudOverlayFilename ~= nil and
                            fillTypeDesc.hudOverlayFilename ~= "" then
                            -- Кэшируем оверлей иконки груза, чтобы не нагружать память
                            if vInfo.fillIconOverlay == nil or vInfo.lastFillType ~= dominantFillType then
                                vInfo.fillIconOverlay = Overlay.new(fillTypeDesc.hudOverlayFilename, 0, 0, 0, 0)
                                vInfo.lastFillType = dominantFillType
                            end

                            if vInfo.fillIconOverlay ~= nil then
                                local fIconSize = 0.014 * popScale * crowdFactor
                                local fIconX = barX - fIconSize - 0.002
                                local fIconY = barY + (barH / 2) - (fIconSize * g_screenAspectRatio / 2)

                                vInfo.fillIconOverlay:setPosition(fIconX, fIconY)
                                vInfo.fillIconOverlay:setDimension(fIconSize, fIconSize * g_screenAspectRatio)
                                vInfo.fillIconOverlay:setColor(1, 1, 1, globalAlpha)
                                vInfo.fillIconOverlay:render()
                            end
                        end
                    end

                    setTextAlignment(RenderText.ALIGN_LEFT)
                    local pctSize = 0.012 * itemPopScale * crowdFactor
                    drawTextWithShadow(barX + barW + 0.002, barY - 0.002, pctSize, pctText, 0.9, 0.9, 0.9, itemAlpha)
                    setTextAlignment(RenderText.ALIGN_CENTER)
                end
            end

            local label = vInfo.name
            if isFav then
                label = "* " .. label
            end
            if hasImplements then
                label = label .. " [+]"
            end

            local baseR, baseG, baseB = 1, 1, 1
            if vInfo.isAI then
                baseR, baseG, baseB = 0.2, 0.8, 1.0
                label = label .. " [AI]"
            end

            -- ВЫВОД МЕТРОВ НА ЭКРАН (Только если мы в категории "РЯДОМ")
            if self.currentCategoryIndex == 1 and vInfo.distance ~= nil and vInfo.distance < 999999 then
                local distText = string.format(" [%dм]", math.floor(vInfo.distance))
                label = label .. distText
            end

            local r = baseR + (0.3 - baseR) * hWeight
            local g = baseG + (1.0 - baseG) * hWeight
            local b = baseB + (0.3 - baseB) * hWeight

            -- Текст тоже подчиняется индивидуальной прозрачности
            local alpha = isFav and itemAlpha or ((0.7 + 0.3 * hWeight) * itemAlpha)
            local fontSize = (0.018 + (0.004 * hWeight)) * itemPopScale * crowdFactor

            -- === СИНХРОННОЕ ДРОЖАНИЕ И ВСПЫШКА ТЕКСТА ===
            local finalPosX = x + shakeX
            local finalPosY = textCenterY + shakeY

            if isFlashing then
                g = 1.0
                b = 1.0 - flashGlow
                fontSize = fontSize * flashScale -- Текст выпрыгивает вместе с иконкой
                alpha = 1.0
            elseif sWeight > 0.5 then
                local colorTension = (sWeight - 0.5) * 2
                g = g - (colorTension * 0.3)
                b = b - (colorTension * 0.3)
            end

            drawTextWithShadow(finalPosX, finalPosY, fontSize, label, r, g, b, alpha)
            -- ===================================
        end
    end

    setTextColor(1, 1, 1, 1)
    setTextAlignment(RenderText.ALIGN_LEFT)
    setTextBold(false)

    -- === ОТЛАДОЧНАЯ ИНФОРМАЦИЯ НА ЭКРАНЕ ===
    -- local dbgX = self.gamepadX or 0
    -- local dbgY = self.gamepadY or 0
    -- -- Переводим радианы в градусы для удобства чтения (от 0 до 360)
    -- local dbgAngle = math.deg(self.currentAngle or 0) 

    -- local dbgText = string.format("Gamepad X: %.2f | Gamepad Y: %.2f", dbgX, dbgY)
    -- local dbgText2 = string.format("Angle: %.1f deg | Hovered: %d / %d", dbgAngle, self.hoveredIndex, numItems)

    -- setTextColor(0, 1, 0, 1) -- Зеленый текст
    -- setTextAlignment(RenderText.ALIGN_LEFT)
    -- setTextBold(true)

    -- -- Рисуем текст в левом верхнем углу
    -- renderText(0.01, 0.95, 0.02, "RVS DEBUG:")
    -- renderText(0.01, 0.93, 0.02, dbgText)
    -- renderText(0.01, 0.91, 0.02, dbgText2)
    -- =======================================
end
