require "MS/MS_MatrixManager"
require "MS/SettingPanel/MS_MatrixStylePanel"
require "MS/SettingPanel/MS_PanelCollapseHelper"
require "MS/SettingPanel/MS_TitlePanelButtons"
require "ISUI/ISPanel"
require "ISUI/ISButton"
require "ISUI/ISLabel"
require "ISUI/ISTextEntryBox"
require "ISUI/ISContextMenu"
require "MS/MS_IndicatorOrder"

-- NIScrollView is provided by NeatUI Framework, which must load before this mod.

MS_AllIndicatorsPanel = ISPanel:derive("MS_AllIndicatorsPanel")
MS_AllIndicatorsPanel.CurrentPanel = nil
local FONT_HGT_SMALL = getTextManager():getFontHeight(UIFont.Small)
local ORIGINAL_SORT_ORDER = MS_IndicatorOrder.ORIGINAL_SORT_ORDER
local SETTING_PANEL_WIDTH_KEYS = {
    "IGUI_ModernStatus_Style",
    "IGUI_ModernStatus_Matrix",
    "IGUI_ModernStatus_ResetColor",
    "IGUI_ModernStatus_AlwaysShowValue",
    "IGUI_ModernStatus_MonoIcon",
    "IGUI_ModernStatus_Hide",
    "IGUI_ModernStatus_Unhide",
    "IGUI_ModernStatus_AutoHide",
    "IGUI_ModernStatus_Lock",
    "IGUI_ModernStatus_Unlock",
    "IGUI_ModernStatus_Columns",
    "IGUI_ModernStatus_Rows",
    "IGUI_ModernStatus_SortDirection",
    "IGUI_ModernStatus_RowWidth",
    "IGUI_ModernStatus_RowHeight",
    "IGUI_ModernStatus_Opacity",
    "IGUI_ModernStatus_UseGradient",
    "IGUI_ModernStatus_ShowIcon",
    "IGUI_ModernStatus_IconPosition",
    "IGUI_ModernStatus_IconOpacity",
    "IGUI_ModernStatus_TextSize",
    "IGUI_ModernStatus_ShowName",
    "IGUI_ModernStatus_DisplayThresholds",
    "IGUI_ModernStatus_InvertDirection",
    "IGUI_ModernStatus_EnableAlarm",
    "IGUI_ModernStatus_AnimThreshold",
    "IGUI_ModernStatus_AutoHideThreshold",
    "IGUI_ModernStatus_SettingPanelWideProbe",
}

local function calculateWidestSettingPanelLabelWidth()
    local textManager = getTextManager()
    local widestWidth = 0

    for _, key in ipairs(SETTING_PANEL_WIDTH_KEYS) do
        -- Compare localized labels at runtime because the widest option can differ by language.
        local candidateText = getText(key) .. " AAA"
        local candidateWidth = textManager:MeasureStringX(UIFont.Small, candidateText)
        if candidateWidth > widestWidth then
            widestWidth = candidateWidth
        end
    end

    return widestWidth
end


local function getOriginalSortOrder(indicatorType)
    return MS_IndicatorOrder.getOriginalSortOrder(indicatorType)
end

local function refreshButtonTooltip(button, tooltip)
    if not button then return end
    if tooltip then
        button:setTooltip(tooltip)
    end
    if button.updateTooltip then
        button:updateTooltip()
    end
end


-- ---------------------------------------------------------------------------------------- --
-- calculate Required Height
-- ---------------------------------------------------------------------------------------- --
function MS_AllIndicatorsPanel.calculateNameColumnWidth(playerIndex, buttonHeight, padding)
    local textManager = getTextManager()
    local iconWidth = math.floor((buttonHeight or math.floor(FONT_HGT_SMALL * 1.2)) * 1.2)
    local cellPadding = padding or math.floor(FONT_HGT_SMALL * 0.2)
    local widestNameWidth = 0

    -- Prefer real localized indicator names so the column is only as wide as needed.
    local indicators = StatusWidget and StatusWidget.indicators and StatusWidget.indicators[playerIndex]
    if indicators then
        for name, indicator in pairs(indicators) do
            local indicatorType = indicator and indicator.getType and indicator:getType() or nil
            local titleText = MS_MatrixManager.getIndicatorDisplayTitle(indicatorType, name)
            local titleWidth = textManager:MeasureStringX(UIFont.Small, titleText or name or "")
            if titleWidth > widestNameWidth then
                widestNameWidth = titleWidth
            end
        end
    end

    if widestNameWidth <= 0 then
        -- Fallback for early calls before indicators exist.
        widestNameWidth = textManager:MeasureStringX(UIFont.Small, getText("IGUI_ModernStatus_SettingPanelWideProbe"))
    end

    -- Icon + text + moderate margin. The 25% margin avoids truncation without wasting a full extra column.
    return math.ceil(iconWidth + cellPadding + widestNameWidth * 1.25)
end

function MS_AllIndicatorsPanel.calculateMatrixButtonWidth(padding, buttonHeight)
    local labelWidth = getTextManager():MeasureStringX(UIFont.Small, getText("IGUI_ModernStatus_Matrix"))
    return math.max(buttonHeight * 2, labelWidth + padding * 6)
end

function MS_AllIndicatorsPanel.calculateStyleButtonWidth(padding, buttonHeight)
    -- Style now uses icon-only buttons, so the column can stay compact.
    return buttonHeight
end

function MS_AllIndicatorsPanel.calculateValueButtonWidth(padding, buttonHeight)
    return buttonHeight
end

function MS_AllIndicatorsPanel.calculateMonoButtonWidth(padding, buttonHeight)
    return buttonHeight
end

function MS_AllIndicatorsPanel.calculateVisibilityButtonWidth(padding, buttonHeight)
    -- Visibility now uses icon-only buttons, so the column can stay compact.
    return buttonHeight
end

function MS_AllIndicatorsPanel.calculateAutoHideButtonWidth(padding, buttonHeight)
    return buttonHeight
end

function MS_AllIndicatorsPanel.calculateLockButtonWidth(padding, buttonHeight)
    -- Lock now uses icon-only buttons, so the column can stay compact.
    return buttonHeight
end

function MS_AllIndicatorsPanel.calculateResetButtonWidth(padding, buttonHeight)
    local buttonspacing = padding * 2
    local resetText = getText("IGUI_ModernStatus_ResetColor")
    local resetTextWidth = getTextManager():MeasureStringX(UIFont.Small, resetText)
    return math.max(resetTextWidth + buttonspacing * 2, buttonHeight * 2)
end

function MS_AllIndicatorsPanel.calculateRowHeight(buttonHeight)
    return math.floor(buttonHeight * 1.2)
end

function MS_AllIndicatorsPanel.calculateRequiredHeight(playerIndex)
    local indicators = StatusWidget.indicators[playerIndex]
    if not indicators then return 600 end

    local indicatorCount = 0
    for _ in pairs(indicators) do
        indicatorCount = indicatorCount + 1
    end

    local titleBarHeight = math.floor(FONT_HGT_SMALL * 1.2)
    local tempPadding = math.floor(FONT_HGT_SMALL * 0.2)
    local tempButtonHeight = math.floor(FONT_HGT_SMALL * 1.2)
    local rowHeight = MS_AllIndicatorsPanel.calculateRowHeight(tempButtonHeight)
    local headerRowHeight = rowHeight
    local matrixRowHeight = rowHeight

    local totalHeight = titleBarHeight + tempPadding + matrixRowHeight + tempPadding + headerRowHeight + tempPadding + (rowHeight * indicatorCount) + tempPadding

    local minHeight = 400
    return math.max(minHeight, totalHeight)
end

function MS_AllIndicatorsPanel.calculateScrollBarWidth()
    return math.floor(FONT_HGT_SMALL * 0.6)
end

function MS_AllIndicatorsPanel.calculateRequiredWidth(playerIndex)
    local tempPadding = math.floor(FONT_HGT_SMALL * 0.2)
    local tempButtonHeight = math.floor(FONT_HGT_SMALL * 1.2)
    local tempScrollBarWidth = MS_AllIndicatorsPanel.calculateScrollBarWidth()
    local tempScrollBarSpacing = tempPadding

    local nameColumnWidth = MS_AllIndicatorsPanel.calculateNameColumnWidth(playerIndex, tempButtonHeight, tempPadding)
    local matrixButtonWidth = MS_AllIndicatorsPanel.calculateMatrixButtonWidth(tempPadding, tempButtonHeight)
    local styleButtonWidth = MS_AllIndicatorsPanel.calculateStyleButtonWidth(tempPadding, tempButtonHeight)
    local iconButtonWidth = tempButtonHeight
    local resetButtonWidth = MS_AllIndicatorsPanel.calculateResetButtonWidth(tempPadding, tempButtonHeight)

    -- Columns after Matrix/Style:
    -- Gradient, Display Icon, Mono Icon, Show Name, Keep Value, Invert, Alarm, Auto Hide, Visibility, Lock, Reset.
    local iconColumnCount = 10
    local buttonsWidth = matrixButtonWidth + styleButtonWidth + (iconButtonWidth * iconColumnCount) + resetButtonWidth + tempPadding * 12
    local totalWidth = tempPadding + nameColumnWidth + tempPadding + buttonsWidth + tempScrollBarSpacing + tempScrollBarWidth + tempPadding

    local minWidth = 640
    return math.max(minWidth, totalWidth)
end

-- ---------------------------------------------------------------------------------------- --
-- initialise
-- ---------------------------------------------------------------------------------------- --
function MS_AllIndicatorsPanel:initialise()
    ISPanel.initialise(self)
    print("AllIndicatorsPanel")
    local iconTextures = {}
    local indicators = StatusWidget.indicators[self.playerIndex]
    if indicators then
        for _, indicator in pairs(indicators) do
            if indicator.baseIconName then
                iconTextures[indicator.baseIconName] = true
            end
        end
    end

    for iconName in pairs(iconTextures) do
        local path = "media/ui/ModernStatus/ColorIcon/" .. iconName .. ".png"
        local texture = getTexture(path)
        if texture then
            self.iconTextures[iconName] = texture
        end
    end
end


function MS_AllIndicatorsPanel:new(x, y, width, height, playerIndex)
    local screenHeight = getCore():getScreenHeight()
    local edgePadding = 20
    local maxHeight = math.max(120, screenHeight - edgePadding * 2)

    if not height then
        height = MS_AllIndicatorsPanel.calculateRequiredHeight(playerIndex)
    end
    height = math.min(height, maxHeight)

    if not width then
        width = MS_AllIndicatorsPanel.calculateRequiredWidth(playerIndex)
    else
        local requiredWidth = MS_AllIndicatorsPanel.calculateRequiredWidth(playerIndex)
        if width < requiredWidth then
            width = requiredWidth
        end
    end

    local o = ISPanel:new(x, y, width, height)
    setmetatable(o, self)
    self.__index = self
    o.playerIndex = playerIndex
    o.padding = math.floor(FONT_HGT_SMALL * 0.2)
    o.titleBarHeight = math.floor(FONT_HGT_SMALL * 1.2)
    o.buttonHeight = math.floor(FONT_HGT_SMALL * 1.2)
    o.headerRowHeight = MS_AllIndicatorsPanel.calculateRowHeight(o.buttonHeight)
    o.matrixRowHeight = o.headerRowHeight
    o.scrollBarWidth = MS_AllIndicatorsPanel.calculateScrollBarWidth()
    o.scrollBarSpacing = o.padding
    o.moveWithMouse = false
    o.moving = false
    o.iconTextures = {}
    o.filterText = ""
    local globalConfig = MSConfig.getGlobalConfig(playerIndex)
    local listSettings = globalConfig.allIndicatorsSettings or {}
    o.filterVisibilityMode = listSettings.visibilityMode or "all"
    o.sortMode = listSettings.sortMode or "alphabetical"
    o.customOrder = listSettings.customOrder
    o.listButtons = {}
    o.rowDragData = nil
    o.rowDragHoverIndex = nil

    -- Search and sort textures mimic the compact button style already used by the panel.
    o.searchFieldBackground = {
        L = getTexture("media/ui/ModernStatus/SettingPanel/LongBackground_L.png"),
        M = getTexture("media/ui/ModernStatus/SettingPanel/LongBackground_M.png"),
        R = getTexture("media/ui/ModernStatus/SettingPanel/LongBackground_R.png")
    }
    o.searchFieldBorder = {
        L = getTexture("media/ui/ModernStatus/SettingPanel/LongBorder_L.png"),
        M = getTexture("media/ui/ModernStatus/SettingPanel/LongBorder_M.png"),
        R = getTexture("media/ui/ModernStatus/SettingPanel/LongBorder_R.png")
    }
    o.clearIcon = getTexture("media/ui/ModernStatus/SettingPanel/Icon_Close.png")
    o.filterSearchIcon = getTexture("media/ui/ModernStatus/ICON/Icon_SearchItem.png")
    o.sortIcon = getTexture("media/ui/ModernStatus/ICON/Icon_CraftSort.png")
    o.filterModeGreyIcon = getTexture("media/ui/ModernStatus/ICON/Grey.png")
    o.filterModeOrangeIcon = getTexture("media/ui/ModernStatus/ICON/Orange.png")
    o.filterModeBlueIcon = getTexture("media/ui/ModernStatus/ICON/Blue.png")
    o.matrixIcon = getTexture("media/ui/ModernStatus/ICON/Icon_Matrix.png")

    -- Column buttons use local icon copies so the panel does not depend on other mods being enabled.
    o.circularIcon = getTexture("media/ui/ModernStatus/ICON/Icon_Circular.png")
    o.horizontalBarIcon = getTexture("media/ui/ModernStatus/ICON/Icon_Horizontal_Bar.png")
    o.verticalBarIcon = getTexture("media/ui/ModernStatus/ICON/Icon_Vertical_Bar.png")
    o.gradientIcon = getTexture("media/ui/ModernStatus/ICON/Icon_GradientColors.png")
    o.displayIcon = getTexture("media/ui/ModernStatus/ICON/Icon_DisplayIcon.png")
    o.valueIcon = getTexture("media/ui/ModernStatus/ICON/Icon_KeepValue.png")
    o.monoToggleIcon = getTexture("media/ui/ModernStatus/ICON/Icon_MonoToggle.png")
    o.showNameIcon = getTexture("media/ui/ModernStatus/ICON/Icon_ShowName.png")
    o.invertDirectionIcon = getTexture("media/ui/ModernStatus/ICON/Icon_InvertDirection.png")
    o.alarmIcon = getTexture("media/ui/ModernStatus/ICON/Icon_Warnning.png")
    o.showIcon = getTexture("media/ui/ModernStatus/ICON/Icon_Show.png")
    o.hideIcon = getTexture("media/ui/ModernStatus/ICON/Icon_Hide.png")
    o.autoHideToggleIcon = getTexture("media/ui/ModernStatus/ICON/Icon_AutoHide.png")
    o.lockIcon = getTexture("media/ui/ModernStatus/ICON/Icon_Lock.png")
    o.unlockIcon = getTexture("media/ui/ModernStatus/ICON/Icon_UnLock.png")

    o.titlebarbkg = NinePatchTexture.getSharedTexture("media/ui/ModernStatus/SettingPanel/TitleBarBG.png")
    o.contentBackground = NinePatchTexture.getSharedTexture("media/ui/ModernStatus/SettingPanel/ContentBackground.png")

    return o
end


-- Build and persist the custom order lazily so existing saves keep working.
function MS_AllIndicatorsPanel.openForPlayer(playerIndex, anchorX, anchorY)
    if MS_AllIndicatorsPanel.CurrentPanel then
        MS_AllIndicatorsPanel.CurrentPanel:close()
    end

    local screenWidth = getCore():getScreenWidth()
    local screenHeight = getCore():getScreenHeight()
    local panel = MS_AllIndicatorsPanel:new(anchorX or 100, anchorY or 100, nil, nil, playerIndex)
    panel:initialise()
    panel:addToUIManager()
    panel:setVisible(true)

    local x = anchorX or math.floor((screenWidth - panel:getWidth()) / 2)
    local y = anchorY or math.floor((screenHeight - panel:getHeight()) / 2)
    panel:setX(math.max(10, math.min(screenWidth - panel:getWidth() - 10, x)))
    panel:setY(math.max(10, math.min(screenHeight - panel:getHeight() - 10, y)))

    MS_AllIndicatorsPanel.CurrentPanel = panel
    return panel
end


function MS_AllIndicatorsPanel:getCustomOrder()
    local globalConfig = MSConfig.getGlobalConfig(self.playerIndex)
    local listSettings = globalConfig.allIndicatorsSettings or {}
    local customOrder = self.customOrder or listSettings.customOrder

    if type(customOrder) ~= "table" or #customOrder == 0 then
        customOrder = {}
        local originalItems = self:getSortedIndicators("original")
        for _, item in ipairs(originalItems) do
            table.insert(customOrder, item.indicatorType)
        end
        self.customOrder = customOrder
        listSettings.customOrder = customOrder
        MSConfig.updateGlobalConfig(self.playerIndex, "allIndicatorsSettings", listSettings)
    end

    self.customOrder = customOrder
    return customOrder
end

-- Persist the custom order used by manual row reordering.
function MS_AllIndicatorsPanel:setCustomOrder(customOrder)
    local globalConfig = MSConfig.getGlobalConfig(self.playerIndex)
    local listSettings = globalConfig.allIndicatorsSettings or {}
    self.customOrder = customOrder or {}
    listSettings.customOrder = self.customOrder
    listSettings.sortMode = self.sortMode or "custom"
    listSettings.visibilityMode = self.filterVisibilityMode or "all"
    MSConfig.updateGlobalConfig(self.playerIndex, "allIndicatorsSettings", listSettings)
end

function MS_AllIndicatorsPanel:getSortedIndicators(modeOverride)
    local indicators = StatusWidget.indicators[self.playerIndex]
    if not indicators then
        return {}
    end

    local sortedIndicators = {}
    for name, indicator in pairs(indicators) do
        local indicatorType = indicator:getType()
        local titleText = MS_MatrixManager.getIndicatorDisplayTitle(indicatorType, name)
        local titleKey = MS_IndicatorSettingsPanel and MS_IndicatorSettingsPanel.IndicatorNames and MS_IndicatorSettingsPanel.IndicatorNames[indicatorType]
        table.insert(sortedIndicators, {
            name = name,
            indicator = indicator,
            titleText = titleText,
            titleKey = titleKey,
            indicatorType = indicatorType
        })
    end

    local mode = modeOverride or self.sortMode or "alphabetical"
    local customOrderIndex = nil
    if mode == "custom" then
        customOrderIndex = {}
        for index, indicatorType in ipairs(self:getCustomOrder()) do
            if customOrderIndex[indicatorType] == nil then
                customOrderIndex[indicatorType] = index
            end
        end
    end

    table.sort(sortedIndicators, function(a, b)
        if mode == "original" then
            local orderA = getOriginalSortOrder(a.indicatorType)
            local orderB = getOriginalSortOrder(b.indicatorType)
            if orderA ~= orderB then
                return orderA < orderB
            end
        elseif mode == "custom" then
            local orderA = customOrderIndex[a.indicatorType]
            local orderB = customOrderIndex[b.indicatorType]
            if orderA and orderB and orderA ~= orderB then
                return orderA < orderB
            end
            if orderA and not orderB then
                return true
            end
            if orderB and not orderA then
                return false
            end
        end

        if a.titleText == b.titleText then
            return a.indicatorType < b.indicatorType
        end
        return a.titleText < b.titleText
    end)

    return sortedIndicators
end

function MS_AllIndicatorsPanel:getIndicatorEffectiveHidden(indicator)
    if not indicator then
        return false
    end
    return indicator.hidden == true
end

function MS_AllIndicatorsPanel:getIndicatorEffectiveLocked(indicator)
    if not indicator then
        return false
    end
    return indicator.locked == true
end

function MS_AllIndicatorsPanel:getIndicatorEffectiveAlwaysShowValue(indicator)
    if not indicator then
        return false
    end
    return indicator.alwaysShowValue == true
end

function MS_AllIndicatorsPanel:getIndicatorEffectiveUseGradient(indicator)
    if not indicator then
        return false
    end
    return indicator.useGradient == true
end

function MS_AllIndicatorsPanel:getIndicatorEffectiveShowIcon(indicator)
    if not indicator then
        return false
    end
    return indicator.showIcon == true
end

function MS_AllIndicatorsPanel:getIndicatorEffectiveShowName(indicator)
    if not indicator then
        return false
    end
    return indicator.showName == true
end

function MS_AllIndicatorsPanel:getIndicatorEffectiveInvertDirection(indicator)
    if not indicator then
        return false
    end
    return indicator.invertDirection == true
end

function MS_AllIndicatorsPanel:getIndicatorEffectiveAlarmEnabled(indicator)
    if not indicator then
        return false
    end
    return indicator.alarmEnabled == true
end

function MS_AllIndicatorsPanel:getIndicatorEffectiveUseMonoIcon(indicator)
    if not indicator then
        return false
    end
    return indicator.useMonoIcon == true
end

function MS_AllIndicatorsPanel:getIndicatorEffectiveAutoHide(indicator)
    if not indicator then
        return false
    end
    return indicator.autoHide == true
end

function MS_AllIndicatorsPanel:passesVisibilityFilter(indicator)
    local mode = self.filterVisibilityMode or "all"
    local hidden = self:getIndicatorEffectiveHidden(indicator)

    if mode == "shown" then
        return not hidden
    end
    if mode == "hidden" then
        return hidden
    end
    return true
end

function MS_AllIndicatorsPanel:getFilteredIndicators()
    local sortedIndicators = self:getSortedIndicators()
    local filterText = string.lower(self.filterText or "")
    local filteredIndicators = {}

    for _, item in ipairs(sortedIndicators) do
        if self:passesVisibilityFilter(item.indicator) then
            if filterText == "" then
                table.insert(filteredIndicators, item)
            else
                local candidateText = string.lower(item.titleText or "")
                local rawName = string.lower(item.name or "")
                local indicatorType = string.lower(item.indicatorType or "")

                if string.find(candidateText, filterText, 1, true) or
                   string.find(rawName, filterText, 1, true) or
                   string.find(indicatorType, filterText, 1, true) then
                    table.insert(filteredIndicators, item)
                end
            end
        end
    end

    return filteredIndicators
end

function MS_AllIndicatorsPanel:getAllIndicators()
    local indicators = StatusWidget.indicators[self.playerIndex]
    if not indicators then
        return {}
    end

    local allIndicators = {}
    for _, indicator in pairs(indicators) do
        table.insert(allIndicators, indicator)
    end
    return allIndicators
end

function MS_AllIndicatorsPanel:isMatrixLinked(indicator)
    return MS_MatrixManager.isIndicatorLinked(indicator)
end

function MS_AllIndicatorsPanel:getLinkedIndicators()
    return MS_MatrixManager.getLinkedIndicators(self.playerIndex, true)
end

function MS_AllIndicatorsPanel:getStandaloneIndicators()
    return MS_MatrixManager.getUnlinkedIndicators(self.playerIndex)
end

function MS_AllIndicatorsPanel:getMatrixConfig()
    return MSConfig.getMatrixConfig(self.playerIndex)
end

function MS_AllIndicatorsPanel:getMatrixDefaultConfig()
    return MSConfig.getDefaultMatrixConfig()
end

function MS_AllIndicatorsPanel:getMatrixLinkedSummary(getter, defaultValue, indicatorList)
    return MS_MatrixManager.summarizeLinkedIndicators(self.playerIndex, getter, defaultValue, indicatorList)
end

function MS_AllIndicatorsPanel:getMatrixLinkedBooleanState(key, defaultValue)
    local value, mixed = self:getMatrixLinkedSummary(function(indicator)
        return indicator[key] == true
    end, defaultValue)
    return value == true, mixed
end

function MS_AllIndicatorsPanel:getMatrixLinkedIndicatorsForSummary()
    return MS_MatrixManager.getLinkedIndicators(self.playerIndex, true)
end

function MS_AllIndicatorsPanel:getMatrixLinkedEditableIndicators()
    return MS_MatrixManager.getEditableLinkedIndicators(self.playerIndex, true)
end

function MS_AllIndicatorsPanel:getMatrixLinkedLockState()
    local indicators = self:getMatrixLinkedIndicatorsForSummary()
    if #indicators == 0 then
        return false, false, 0
    end

    local first = indicators[1].locked == true
    for i = 2, #indicators do
        if (indicators[i].locked == true) ~= first then
            return false, true, #indicators
        end
    end

    return first, false, #indicators
end

function MS_AllIndicatorsPanel:getMatrixLinkedHiddenState()
    local indicators = self:getMatrixLinkedIndicatorsForSummary()
    if #indicators == 0 then
        return false, false, 0
    end

    local first = indicators[1].hidden == true
    for i = 2, #indicators do
        if (indicators[i].hidden == true) ~= first then
            return false, true, #indicators
        end
    end

    return first, false, #indicators
end

function MS_AllIndicatorsPanel:applyMatrixLinkedBooleanValue(key, value)
    for _, indicator in ipairs(self:getMatrixLinkedEditableIndicators()) do
        local applied = value == true
        if key == "useMonoIcon" then
            indicator.useMonoIcon = applied
            if indicator.updateIconPath then
                indicator:updateIconPath()
            end
        else
            indicator[key] = applied
        end
        MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), key, applied)
    end
    MS_MatrixManager.applyMatrixLayout(self.playerIndex)
end

function MS_AllIndicatorsPanel:saveListSettings()
    local globalConfig = MSConfig.getGlobalConfig(self.playerIndex)
    local listSettings = globalConfig.allIndicatorsSettings or {}
    listSettings.sortMode = self.sortMode or "alphabetical"
    listSettings.visibilityMode = self.filterVisibilityMode or "all"
    if self.customOrder and #self.customOrder > 0 then
        listSettings.customOrder = self.customOrder
    end
    MSConfig.updateGlobalConfig(self.playerIndex, "allIndicatorsSettings", listSettings)
end

function MS_AllIndicatorsPanel:getNextMatrixMode()
    return MS_MatrixManager.getNextMode(MSConfig.getMatrixConfig(self.playerIndex).mode)
end

function MS_AllIndicatorsPanel:getMatrixButtonText(indicator)
    return self:isMatrixLinked(indicator) and getText("IGUI_ModernStatus_Matrix") or getText("IGUI_ModernStatus_Single")
end

function MS_AllIndicatorsPanel:getStyleModeForIndicator(indicator)
    if self:isMatrixLinked(indicator) then
        return MSConfig.getMatrixConfig(self.playerIndex).mode
    end
    return self:getIndicatorDisplayMode(indicator)
end

function MS_AllIndicatorsPanel:getLayoutMetrics()
    local leftInset = self.padding
    local contentWidth = self.width
    local reservedScrollWidth = self.scrollBarWidth + self.scrollBarSpacing

    local matrixButtonWidth = self.calculateMatrixButtonWidth(self.padding, self.buttonHeight)
    local styleButtonWidth = self.calculateStyleButtonWidth(self.padding, self.buttonHeight)
    local iconButtonWidth = self.buttonHeight
    local resetButtonWidth = self.calculateResetButtonWidth(self.padding, self.buttonHeight)
    local nameColumnWidth = self.calculateNameColumnWidth(self.playerIndex, self.buttonHeight, self.padding)

    -- Keep the name column compact. Do not stretch it to fill all available width.
    local matrixX = leftInset + nameColumnWidth + self.padding
    local styleX = matrixX + matrixButtonWidth + self.padding
    local gradientX = styleX + styleButtonWidth + self.padding
    local showIconX = gradientX + iconButtonWidth + self.padding
    local monoX = showIconX + iconButtonWidth + self.padding
    local showNameX = monoX + iconButtonWidth + self.padding
    local valueX = showNameX + iconButtonWidth + self.padding
    local invertX = valueX + iconButtonWidth + self.padding
    local alarmX = invertX + iconButtonWidth + self.padding
    local autoHideX = alarmX + iconButtonWidth + self.padding
    local visibilityX = autoHideX + iconButtonWidth + self.padding
    local lockX = visibilityX + iconButtonWidth + self.padding
    local resetX = lockX + iconButtonWidth + self.padding

    return {
        leftInset = leftInset,
        contentWidth = contentWidth,
        reservedScrollWidth = reservedScrollWidth,
        nameColumnWidth = nameColumnWidth,
        matrixButtonWidth = matrixButtonWidth,
        styleButtonWidth = styleButtonWidth,
        gradientButtonWidth = iconButtonWidth,
        showIconButtonWidth = iconButtonWidth,
        monoButtonWidth = iconButtonWidth,
        showNameButtonWidth = iconButtonWidth,
        valueButtonWidth = iconButtonWidth,
        invertButtonWidth = iconButtonWidth,
        alarmButtonWidth = iconButtonWidth,
        autoHideButtonWidth = iconButtonWidth,
        visibilityButtonWidth = iconButtonWidth,
        lockButtonWidth = iconButtonWidth,
        resetButtonWidth = resetButtonWidth,
        matrixX = matrixX,
        styleX = styleX,
        gradientX = gradientX,
        showIconX = showIconX,
        monoX = monoX,
        showNameX = showNameX,
        valueX = valueX,
        invertX = invertX,
        alarmX = alarmX,
        autoHideX = autoHideX,
        visibilityX = visibilityX,
        lockX = lockX,
        resetX = resetX
    }
end

function MS_AllIndicatorsPanel:getIndicatorDisplayMode(indicator)
    if not indicator then
        return "circular"
    end

    if not indicator.isBarStyle then
        return "circular"
    end

    return indicator.isVertical and "vertical" or "horizontal"
end

function MS_AllIndicatorsPanel:isEveryIndicatorInMode(mode, indicators)
    local targetIndicators = indicators or self:getAllIndicators()
    if #targetIndicators == 0 then return false end

    for _, indicator in ipairs(targetIndicators) do
        if self:getIndicatorDisplayMode(indicator) ~= mode then
            return false
        end
    end

    return true
end

function MS_AllIndicatorsPanel:areAllIndicatorsVisible(indicators)
    local targetIndicators = indicators or self:getAllIndicators()
    if #targetIndicators == 0 then return false end

    for _, indicator in ipairs(targetIndicators) do
        if indicator.hidden then
            return false
        end
    end

    return true
end

function MS_AllIndicatorsPanel:areAllIndicatorsLocked(indicators)
    local targetIndicators = indicators or self:getAllIndicators()
    if #targetIndicators == 0 then return false end

    for _, indicator in ipairs(targetIndicators) do
        if not indicator.locked then
            return false
        end
    end

    return true
end

function MS_AllIndicatorsPanel:areAllStandaloneVisible()
    return self:areAllIndicatorsVisible(self:getStandaloneIndicators())
end

function MS_AllIndicatorsPanel:areAllStandaloneLocked()
    return self:areAllIndicatorsLocked(self:getStandaloneIndicators())
end

function MS_AllIndicatorsPanel:getNextStandaloneStyleMode()
    local standaloneIndicators = self:getStandaloneIndicators()
    if #standaloneIndicators == 0 then
        return nil
    end

    if self:isEveryIndicatorInMode("circular", standaloneIndicators) then
        return "horizontal"
    end
    if self:isEveryIndicatorInMode("horizontal", standaloneIndicators) then
        return "vertical"
    end
    return "circular"
end

function MS_AllIndicatorsPanel:areAllIndicatorsMatrixLinked()
    local allIndicators = self:getAllIndicators()
    if #allIndicators == 0 then return false end

    for _, indicator in ipairs(allIndicators) do
        if not self:isMatrixLinked(indicator) then
            return false
        end
    end

    return true
end


function MS_AllIndicatorsPanel:getModeIcon(mode)
    if mode == "horizontal" then
        return self.horizontalBarIcon
    end
    if mode == "vertical" then
        return self.verticalBarIcon
    end
    return self.circularIcon
end

function MS_AllIndicatorsPanel:getGradientIcon()
    return self.gradientIcon
end

function MS_AllIndicatorsPanel:getDisplayIcon()
    return self.displayIcon or self.showIcon
end

function MS_AllIndicatorsPanel:getValueIcon()
    return self.valueIcon
end

function MS_AllIndicatorsPanel:getMonoIcon()
    return self.monoToggleIcon
end

function MS_AllIndicatorsPanel:getShowNameIcon()
    return self.showNameIcon or self.valueIcon
end

function MS_AllIndicatorsPanel:getInvertDirectionIcon()
    return self.invertDirectionIcon or self.horizontalBarIcon
end

function MS_AllIndicatorsPanel:getAlarmIcon()
    return self.alarmIcon
end

function MS_AllIndicatorsPanel:getVisibilityIcon(hidden)
    return hidden and self.hideIcon or self.showIcon
end

function MS_AllIndicatorsPanel:getAutoHideIcon()
    return self.autoHideToggleIcon
end

function MS_AllIndicatorsPanel:getLockIcon(locked)
    return locked and self.lockIcon or self.unlockIcon
end

function MS_AllIndicatorsPanel:getNextBulkStyleMode()
    return self:getNextStandaloneStyleMode()
end

function MS_AllIndicatorsPanel:areAllIndicatorsAlwaysShowValue(indicators)
    local targetIndicators = indicators or self:getAllIndicators()
    if #targetIndicators == 0 then return false end
    for _, indicator in ipairs(targetIndicators) do
        if not indicator.alwaysShowValue then
            return false
        end
    end
    return true
end

function MS_AllIndicatorsPanel:areAllIndicatorsUsingGradient(indicators)
    local targetIndicators = indicators or self:getAllIndicators()
    if #targetIndicators == 0 then return false end
    for _, indicator in ipairs(targetIndicators) do
        if not indicator.useGradient then
            return false
        end
    end
    return true
end

function MS_AllIndicatorsPanel:areAllIndicatorsShowingIcon(indicators)
    local targetIndicators = indicators or self:getAllIndicators()
    if #targetIndicators == 0 then return false end
    for _, indicator in ipairs(targetIndicators) do
        if not indicator.showIcon then
            return false
        end
    end
    return true
end

function MS_AllIndicatorsPanel:areAllIndicatorsShowingName(indicators)
    local targetIndicators = indicators or self:getAllIndicators()
    if #targetIndicators == 0 then return false end
    for _, indicator in ipairs(targetIndicators) do
        if not indicator.showName then
            return false
        end
    end
    return true
end

function MS_AllIndicatorsPanel:areAllIndicatorsInvertingDirection(indicators)
    local targetIndicators = indicators or self:getAllIndicators()
    if #targetIndicators == 0 then return false end
    for _, indicator in ipairs(targetIndicators) do
        if not indicator.invertDirection then
            return false
        end
    end
    return true
end

function MS_AllIndicatorsPanel:areAllIndicatorsAlarmEnabled(indicators)
    local targetIndicators = indicators or self:getAllIndicators()
    if #targetIndicators == 0 then return false end
    for _, indicator in ipairs(targetIndicators) do
        if not indicator.alarmEnabled then
            return false
        end
    end
    return true
end

function MS_AllIndicatorsPanel:areAllIndicatorsUsingMonoIcon(indicators)
    local targetIndicators = indicators or self:getAllIndicators()
    if #targetIndicators == 0 then return false end
    for _, indicator in ipairs(targetIndicators) do
        if not indicator.useMonoIcon then
            return false
        end
    end
    return true
end

function MS_AllIndicatorsPanel:areAllIndicatorsAutoHide(indicators)
    local targetIndicators = indicators or self:getAllIndicators()
    if #targetIndicators == 0 then return false end
    for _, indicator in ipairs(targetIndicators) do
        if not indicator.autoHide then
            return false
        end
    end
    return true
end

function MS_AllIndicatorsPanel:areAllStandaloneAlwaysShowValue()
    return self:areAllIndicatorsAlwaysShowValue(self:getStandaloneIndicators())
end

function MS_AllIndicatorsPanel:areAllStandaloneUsingGradient()
    return self:areAllIndicatorsUsingGradient(self:getStandaloneIndicators())
end

function MS_AllIndicatorsPanel:areAllStandaloneShowingIcon()
    return self:areAllIndicatorsShowingIcon(self:getStandaloneIndicators())
end

function MS_AllIndicatorsPanel:areAllStandaloneShowingName()
    return self:areAllIndicatorsShowingName(self:getStandaloneIndicators())
end

function MS_AllIndicatorsPanel:areAllStandaloneInvertingDirection()
    return self:areAllIndicatorsInvertingDirection(self:getStandaloneIndicators())
end

function MS_AllIndicatorsPanel:areAllStandaloneAlarmEnabled()
    return self:areAllIndicatorsAlarmEnabled(self:getStandaloneIndicators())
end

function MS_AllIndicatorsPanel:areAllStandaloneUsingMonoIcon()
    return self:areAllIndicatorsUsingMonoIcon(self:getStandaloneIndicators())
end

function MS_AllIndicatorsPanel:areAllStandaloneAutoHide()
    return self:areAllIndicatorsAutoHide(self:getStandaloneIndicators())
end

function MS_AllIndicatorsPanel:setIndicatorBarOrientation(indicator, wantVertical)
    if not indicator then return end

    local currentVertical = indicator.isVertical == true
    if currentVertical ~= wantVertical then
        local oldWidth = indicator:getWidth()
        local oldHeight = indicator:getHeight()
        indicator:setWidth(oldHeight)
        indicator:setHeight(oldWidth)
    end

    indicator.isVertical = wantVertical

    MSConfig.updateIndicatorConfig(
        indicator.playerIndex,
        indicator:getType(),
        "isVertical",
        wantVertical
    )

    MSConfig.updateIndicatorConfig(
        indicator.playerIndex,
        indicator:getType(),
        "barSize",
        { width = indicator:getWidth(), height = indicator:getHeight() }
    )
end

function MS_AllIndicatorsPanel:applyIndicatorDisplayMode(indicator, mode)
    if not indicator then return end

    if mode == "circular" then
        indicator:switchStyle(false)
        return
    end

    indicator:switchStyle(true)
    self:setIndicatorBarOrientation(indicator, mode == "vertical")
end

function MS_AllIndicatorsPanel:applyDefaultIndicatorConfig(indicator)

    if not indicator then return end

    local indicatorType = indicator:getType()
    local defaultConfig = MSConfig.getDefaultIndicatorConfig(indicatorType)
    local style = defaultConfig.style or "circular"

    indicator:switchStyle(style == "bar")
    indicator:setX(defaultConfig.position.x)
    indicator:setY(defaultConfig.position.y)

    if style == "bar" then
        indicator:setWidth(defaultConfig.barSize.width)
        indicator:setHeight(defaultConfig.barSize.height)
    else
        indicator:setWidth(defaultConfig.circularSize)
        indicator:setHeight(defaultConfig.circularSize)
    end

    indicator.indicatorColor = {
        r = defaultConfig.color.r,
        g = defaultConfig.color.g,
        b = defaultConfig.color.b
    }

    indicator.opacity = defaultConfig.opacity
    indicator.iconOpacity = defaultConfig.iconOpacity
    indicator.useGradient = defaultConfig.useGradient
    indicator.showIcon = defaultConfig.showIcon
    indicator.useMonoIcon = defaultConfig.useMonoIcon

    if style == "bar" then
        indicator.isVertical = defaultConfig.isVertical
    end

    indicator.locked = defaultConfig.locked
    indicator.hidden = defaultConfig.hidden
    indicator:setVisible(not indicator.hidden)
    indicator.moveWithMouse = not indicator.locked

    for key, value in pairs(defaultConfig) do
        MSConfig.updateIndicatorConfig(
            indicator.playerIndex,
            indicator:getType(),
            key,
            value
        )
    end
end


function MS_AllIndicatorsPanel:clearIndicatorList()
    if not self.scrollView then return end

    for _, item in ipairs(self.listButtons) do
        if item.indicator then
            if item.indicator.matrixButton == item.matrixButton then item.indicator.matrixButton = nil end
            if item.indicator.styleButton == item.styleButton then item.indicator.styleButton = nil end
            if item.indicator.gradientButton == item.gradientButton then item.indicator.gradientButton = nil end
            if item.indicator.showIconButton == item.showIconButton then item.indicator.showIconButton = nil end
            if item.indicator.monoButton == item.monoButton then item.indicator.monoButton = nil end
            if item.indicator.showNameButton == item.showNameButton then item.indicator.showNameButton = nil end
            if item.indicator.valueButton == item.valueButton then item.indicator.valueButton = nil end
            if item.indicator.invertButton == item.invertButton then item.indicator.invertButton = nil end
            if item.indicator.alarmButton == item.alarmButton then item.indicator.alarmButton = nil end
            if item.indicator.autoHideButton == item.autoHideButton then item.indicator.autoHideButton = nil end
            if item.indicator.visibilityButton == item.visibilityButton then item.indicator.visibilityButton = nil end
            if item.indicator.lockButton == item.lockButton then item.indicator.lockButton = nil end
            if item.indicator.resetButton == item.resetButton then item.indicator.resetButton = nil end
        end
    end

    for i = #self.scrollView.scrollChildren, 1, -1 do
        local child = self.scrollView.scrollChildren[i]
        self.scrollView:removeScrollChild(child)
    end

    self.listButtons = {}
end

function MS_AllIndicatorsPanel:createChildren()
    self:createTitleBar()
    self:createMatrixRow()
    self:createHeaderRow()
    self:createScrollView()
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:createTitleBar()
    local titleButtonSize = FONT_HGT_SMALL
    MS_PanelCollapseHelper.addTitleBarButton(self, titleButtonSize, "AllIndicatorsPanel")

    self.titleLabel = ISLabel:new(MS_PanelCollapseHelper.getTitleLabelX(self, titleButtonSize), (self.titleBarHeight - FONT_HGT_SMALL) / 2, self.titleBarHeight, getText("IGUI_ModernStatus_AllIndicatorsTitle"), 1, 1, 1, 1, UIFont.Small, true)
    self.titleLabel:initialise()
    self:addChild(self.titleLabel)

    local closeSize = FONT_HGT_SMALL
    self.closeButton = MS_SquareButton:new(self.width - closeSize - self.padding, (self.titleBarHeight - FONT_HGT_SMALL) / 2, closeSize, getTexture("media/ui/ModernStatus/SettingPanel/Icon_Close.png"), self, MS_AllIndicatorsPanel.close)
    self.closeButton:setActive(true)
    self.closeButton:setActiveColor(0.8, 0.2, 0.2)
    self.closeButton:initialise()

    self:addChild(self.closeButton)

    MS_TitlePanelButtons.addButtons(self, { "matrix", "unlinked" }, closeSize)
end


function MS_AllIndicatorsPanel:createMatrixRow()
    local metrics = self:getLayoutMetrics()
    local rowY = self.titleBarHeight + self.padding
    local buttonY = rowY + (self.matrixRowHeight - self.buttonHeight) / 2
    local iconSize = self.buttonHeight

    self.matrixNameButton = ISButton:new(metrics.leftInset, rowY, metrics.nameColumnWidth, self.matrixRowHeight, "", self, MS_AllIndicatorsPanel.onOpenMatrixPanel)
    self.matrixNameButton:initialise()
    self.matrixNameButton:setTooltip(getText("IGUI_ModernStatus_Matrix"))
    self.matrixNameButton.displayBackground = false
    self.matrixNameButton.prerender = function(button)
        if button:isMouseOver() then
            button:drawRect(0, 0, button:getWidth(), button:getHeight(), 0.3, 0.5, 0.5, 0.5)
        end
        if self.matrixIcon then
            button:drawTextureScaled(self.matrixIcon, 0, (self.matrixRowHeight - iconSize) / 2, iconSize, iconSize, 1, 1, 1, 1)
        end
        local textX = iconSize + self.padding
        local textY = (self.matrixRowHeight - FONT_HGT_SMALL) / 2
        button:drawText(getText("IGUI_ModernStatus_Matrix"), textX, textY, 1, 1, 1, 1, UIFont.Small)
    end
    self:addChild(self.matrixNameButton)

    self.matrixStyleButton = MS_SquareButton:new(metrics.styleX, buttonY, self.buttonHeight, self:getModeIcon(self:getMatrixConfig().mode), self, MS_AllIndicatorsPanel.onMatrixStyleClick)
    self.matrixStyleButton:initialise()
    self.matrixStyleButton:setIconSizeRatio(0.7)
    self.matrixStyleButton.prerender = function(button)
        local matrixConfig = self:getMatrixConfig()
        button:setIcon(self:getModeIcon(matrixConfig.mode))
        button:setActive(matrixConfig.mode ~= "circular")
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_Style"))
    end
    self:addChild(self.matrixStyleButton)

    self.matrixGradientButton = MS_SquareButton:new(metrics.gradientX, buttonY, self.buttonHeight, self:getGradientIcon(), self, MS_AllIndicatorsPanel.onMatrixUseGradientClick)
    self.matrixGradientButton:initialise()
    self.matrixGradientButton:setIconSizeRatio(0.7)
    self.matrixGradientButton.prerender = function(button)
        local value, mixed = self:getMatrixLinkedBooleanState("useGradient", self:getMatrixDefaultConfig().useGradient)
        button:setIcon(self:getGradientIcon())
        button:setActive(not mixed and value == true)
        button.activeColor = mixed and { r = 0.45, g = 0.45, b = 0.45 } or { r = 0.95, g = 0.5, b = 0.1 }
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_UseGradient"))
    end
    self:addChild(self.matrixGradientButton)

    self.matrixShowIconButton = MS_SquareButton:new(metrics.showIconX, buttonY, self.buttonHeight, self:getDisplayIcon(), self, MS_AllIndicatorsPanel.onMatrixShowIconClick)
    self.matrixShowIconButton:initialise()
    self.matrixShowIconButton:setIconSizeRatio(0.7)
    self.matrixShowIconButton.prerender = function(button)
        local value, mixed = self:getMatrixLinkedBooleanState("showIcon", self:getMatrixDefaultConfig().showIcon)
        button:setIcon(self:getDisplayIcon())
        button:setActive(not mixed and value == true)
        button.activeColor = mixed and { r = 0.45, g = 0.45, b = 0.45 } or { r = 0.95, g = 0.5, b = 0.1 }
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_ShowIcon"))
    end
    self:addChild(self.matrixShowIconButton)

    self.matrixMonoButton = MS_SquareButton:new(metrics.monoX, buttonY, self.buttonHeight, self:getMonoIcon(), self, MS_AllIndicatorsPanel.onMatrixMonoIconClick)
    self.matrixMonoButton:initialise()
    self.matrixMonoButton:setIconSizeRatio(0.7)
    self.matrixMonoButton.prerender = function(button)
        local value, mixed = self:getMatrixLinkedBooleanState("useMonoIcon", self:getMatrixDefaultConfig().useMonoIcon)
        button:setIcon(self:getMonoIcon())
        button:setActive(not mixed and value == true)
        button.activeColor = mixed and { r = 0.45, g = 0.45, b = 0.45 } or { r = 0.95, g = 0.5, b = 0.1 }
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_MonoIcon"))
    end
    self:addChild(self.matrixMonoButton)

    self.matrixShowNameButton = MS_SquareButton:new(metrics.showNameX, buttonY, self.buttonHeight, self:getShowNameIcon(), self, MS_AllIndicatorsPanel.onMatrixShowNameClick)
    self.matrixShowNameButton:initialise()
    self.matrixShowNameButton:setIconSizeRatio(0.7)
    self.matrixShowNameButton.prerender = function(button)
        local value, mixed = self:getMatrixLinkedBooleanState("showName", self:getMatrixDefaultConfig().showName)
        button:setIcon(self:getShowNameIcon())
        button:setActive(not mixed and value == true)
        button.activeColor = mixed and { r = 0.45, g = 0.45, b = 0.45 } or { r = 0.95, g = 0.5, b = 0.1 }
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_ShowName"))
    end
    self:addChild(self.matrixShowNameButton)

    self.matrixValueButton = MS_SquareButton:new(metrics.valueX, buttonY, self.buttonHeight, self:getValueIcon(), self, MS_AllIndicatorsPanel.onMatrixAlwaysShowValueClick)
    self.matrixValueButton:initialise()
    self.matrixValueButton:setIconSizeRatio(0.7)
    self.matrixValueButton.prerender = function(button)
        local value, mixed = self:getMatrixLinkedBooleanState("alwaysShowValue", self:getMatrixDefaultConfig().alwaysShowValue)
        button:setIcon(self:getValueIcon())
        button:setActive(not mixed and value == true)
        button.activeColor = mixed and { r = 0.45, g = 0.45, b = 0.45 } or { r = 0.95, g = 0.5, b = 0.1 }
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_AlwaysShowValue"))
    end
    self:addChild(self.matrixValueButton)

    self.matrixInvertButton = MS_SquareButton:new(metrics.invertX, buttonY, self.buttonHeight, self:getInvertDirectionIcon(), self, MS_AllIndicatorsPanel.onMatrixInvertDirectionClick)
    self.matrixInvertButton:initialise()
    self.matrixInvertButton:setIconSizeRatio(0.7)
    self.matrixInvertButton.prerender = function(button)
        local value, mixed = self:getMatrixLinkedBooleanState("invertDirection", self:getMatrixDefaultConfig().invertDirection)
        button:setIcon(self:getInvertDirectionIcon())
        button:setActive(not mixed and value == true)
        button.activeColor = mixed and { r = 0.45, g = 0.45, b = 0.45 } or { r = 0.95, g = 0.5, b = 0.1 }
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_InvertDirection"))
    end
    self:addChild(self.matrixInvertButton)

    self.matrixAlarmButton = MS_SquareButton:new(metrics.alarmX, buttonY, self.buttonHeight, self:getAlarmIcon(), self, MS_AllIndicatorsPanel.onMatrixAlarmEnabledClick)
    self.matrixAlarmButton:initialise()
    self.matrixAlarmButton:setIconSizeRatio(0.7)
    self.matrixAlarmButton.prerender = function(button)
        local value, mixed = self:getMatrixLinkedBooleanState("alarmEnabled", self:getMatrixDefaultConfig().alarmEnabled)
        button:setIcon(self:getAlarmIcon())
        button:setActive(not mixed and value == true)
        button.activeColor = mixed and { r = 0.45, g = 0.45, b = 0.45 } or { r = 0.95, g = 0.5, b = 0.1 }
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_EnableAlarm"))
    end
    self:addChild(self.matrixAlarmButton)

    self.matrixAutoHideButton = MS_SquareButton:new(metrics.autoHideX, buttonY, self.buttonHeight, self:getAutoHideIcon(), self, MS_AllIndicatorsPanel.onMatrixAutoHideClick)
    self.matrixAutoHideButton:initialise()
    self.matrixAutoHideButton:setIconSizeRatio(0.7)
    self.matrixAutoHideButton.prerender = function(button)
        local value, mixed = self:getMatrixLinkedBooleanState("autoHide", self:getMatrixDefaultConfig().autoHide)
        button:setIcon(self:getAutoHideIcon())
        button:setActive(not mixed and value == true)
        button.activeColor = mixed and { r = 0.45, g = 0.45, b = 0.45 } or { r = 0.95, g = 0.5, b = 0.1 }
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_AutoHide"))
    end
    self:addChild(self.matrixAutoHideButton)

    self.matrixVisibilityButton = MS_SquareButton:new(metrics.visibilityX, buttonY, self.buttonHeight, self:getVisibilityIcon(self:getMatrixConfig().hidden), self, MS_AllIndicatorsPanel.onMatrixVisibilityClick)
    self.matrixVisibilityButton:initialise()
    self.matrixVisibilityButton:setIconSizeRatio(0.7)
    self.matrixVisibilityButton.prerender = function(button)
        local hidden, mixed = self:getMatrixLinkedHiddenState()
        button:setIcon(self:getVisibilityIcon(hidden))
        button:setActive(hidden or mixed)
        button.activeColor = mixed and { r = 0.45, g = 0.45, b = 0.45 } or { r = 0.95, g = 0.5, b = 0.1 }
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_Visibility"))
    end
    self:addChild(self.matrixVisibilityButton)

    self.matrixLockButton = MS_SquareButton:new(metrics.lockX, buttonY, self.buttonHeight, self:getLockIcon(self:getMatrixConfig().locked), self, MS_AllIndicatorsPanel.onMatrixLockClick)
    self.matrixLockButton:initialise()
    self.matrixLockButton:setIconSizeRatio(0.7)
    self.matrixLockButton.prerender = function(button)
        local locked, mixed = self:getMatrixLinkedLockState()
        button:setIcon(self:getLockIcon(locked))
        button:setActive(locked or mixed)
        button.activeColor = mixed and { r = 0.45, g = 0.45, b = 0.45 } or { r = 0.95, g = 0.5, b = 0.1 }
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_LockUnlock"))
    end
    self:addChild(self.matrixLockButton)

    self.matrixResetButton = MS_LongButton:new(metrics.resetX, buttonY, metrics.resetButtonWidth, self.buttonHeight, getText("IGUI_ModernStatus_ResetColor"), self, MS_AllIndicatorsPanel.onMatrixResetClick)
    self.matrixResetButton:initialise()
    self:addChild(self.matrixResetButton)
end


function MS_AllIndicatorsPanel:createHeaderRow()
    local metrics = self:getLayoutMetrics()
    local headerY = self.titleBarHeight + self.padding + self.matrixRowHeight + self.padding
    local searchBoxHeight = self.buttonHeight
    local buttonY = headerY + (self.headerRowHeight - self.buttonHeight) / 2
    local squareButtonSize = self.buttonHeight
    local sortButtonX = self.padding
    local filterButtonX = sortButtonX + squareButtonSize + self.padding
    local searchBoxX = filterButtonX + squareButtonSize + self.padding
    local searchBoxWidth = math.max(40, metrics.nameColumnWidth - squareButtonSize * 2 - self.padding * 2)

    self.sortButton = MS_SquareButton:new(sortButtonX, buttonY, squareButtonSize, self.sortIcon, self, MS_AllIndicatorsPanel.onSortButtonClick)
    self.sortButton:initialise()
    self.sortButton:setIconSizeRatio(0.7)
    self:addChild(self.sortButton)
    self:updateSortButtonState()

    self.filterButton = MS_SquareButton:new(filterButtonX, buttonY, squareButtonSize, self.filterSearchIcon, self, MS_AllIndicatorsPanel.onFilterButtonClick)
    self.filterButton:initialise()
    self.filterButton:setIconSizeRatio(0.7)
    self:addChild(self.filterButton)
    self:updateFilterButtonState()

    self.filterEntry = ISTextEntryBox:new("", searchBoxX, buttonY, searchBoxWidth, searchBoxHeight)
    self.filterEntry:initialise()
    self.filterEntry.font = UIFont.Small
    self.filterEntry.backgroundColor.a = 0
    self.filterEntry.borderColor.a = 0
    self.filterEntry.onTextChange = function()
        self:onFilterTextChanged()
    end
    self.filterEntry.prerender = function(searchBox)
        NeatTool.ThreePatch.drawHorizontal(searchBox, -self.padding / 2, 0, searchBox.width + self.padding, searchBox.height, self.searchFieldBackground.L, self.searchFieldBackground.M, self.searchFieldBackground.R, 0.8, 0.1, 0.1, 0.1)
        NeatTool.ThreePatch.drawHorizontal(searchBox, -self.padding / 2, 0, searchBox.width + self.padding, searchBox.height, self.searchFieldBorder.L, self.searchFieldBorder.M, self.searchFieldBorder.R, 1, 0.4, 0.4, 0.4)
    end
    self:addChild(self.filterEntry)

    self.filterClearButton = ISButton:new(searchBoxX + searchBoxWidth - searchBoxHeight, buttonY, searchBoxHeight, searchBoxHeight, "", self, MS_AllIndicatorsPanel.onClearFilter)
    self.filterClearButton:initialise()
    self.filterClearButton.borderColor.a = 0
    self.filterClearButton.backgroundColor.a = 0
    self.filterClearButton.backgroundColorMouseOver.a = 0
    self.filterClearButton:setVisible(false)
    self.filterClearButton.prerender = function(button)
        if button:isVisible() and self.clearIcon then
            local iconSize = math.floor(button.height * 0.55)
            local iconX = (button.width - iconSize) / 2
            local iconY = (button.height - iconSize) / 2
            local alpha = button:isMouseOver() and 1.0 or 0.7
            button:drawTextureScaled(self.clearIcon, iconX, iconY, iconSize, iconSize, alpha, 0.8, 0.8, 0.8)
        end
    end
    self:addChild(self.filterClearButton)

    self.allMatrixButton = MS_LongButton:new(metrics.matrixX, buttonY, metrics.matrixButtonWidth, self.buttonHeight, getText("IGUI_ModernStatus_Matrix"), self, MS_AllIndicatorsPanel.onApplyMatrixToAll)
    self.allMatrixButton:initialise()
    self.allMatrixButton:setTooltip(getText("IGUI_ModernStatus_LinkUnlinkAllToMatrix"))
    self.allMatrixButton.prerender = function(button)
        button:setTitle(getText("IGUI_ModernStatus_Matrix"))
        button:setActive(self:areAllIndicatorsMatrixLinked())
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_LinkUnlinkAllToMatrix"))
    end
    self:addChild(self.allMatrixButton)

    self.allStyleButton = MS_SquareButton:new(metrics.styleX, buttonY, self.buttonHeight, self:getModeIcon(self:getNextBulkStyleMode()), self, MS_AllIndicatorsPanel.onApplyStyleToAll)
    self.allStyleButton:initialise()
    self.allStyleButton:setTooltip(getText("IGUI_ModernStatus_Style"))
    self.allStyleButton:setIconSizeRatio(0.7)
    self.allStyleButton.prerender = function(button)
        local targetMode = self:getNextBulkStyleMode() or "circular"
        button:setIcon(self:getModeIcon(targetMode))
        button:setActive(self:getNextBulkStyleMode() ~= nil and targetMode ~= "circular")
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_Style"))
    end
    self:addChild(self.allStyleButton)

    self.allGradientButton = MS_SquareButton:new(metrics.gradientX, buttonY, self.buttonHeight, self:getGradientIcon(), self, MS_AllIndicatorsPanel.onApplyUseGradientToAll)
    self.allGradientButton:initialise()
    self.allGradientButton:setTooltip(getText("IGUI_ModernStatus_UseGradient"))
    self.allGradientButton:setIconSizeRatio(0.7)
    self.allGradientButton.prerender = function(button)
        button:setIcon(self:getGradientIcon())
        button:setActive(self:areAllStandaloneUsingGradient())
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_UseGradient"))
    end
    self:addChild(self.allGradientButton)

    self.allShowIconButton = MS_SquareButton:new(metrics.showIconX, buttonY, self.buttonHeight, self:getDisplayIcon(), self, MS_AllIndicatorsPanel.onApplyShowIconToAll)
    self.allShowIconButton:initialise()
    self.allShowIconButton:setTooltip(getText("IGUI_ModernStatus_ShowIcon"))
    self.allShowIconButton:setIconSizeRatio(0.7)
    self.allShowIconButton.prerender = function(button)
        button:setIcon(self:getDisplayIcon())
        button:setActive(self:areAllStandaloneShowingIcon())
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_ShowIcon"))
    end
    self:addChild(self.allShowIconButton)

    self.allMonoButton = MS_SquareButton:new(metrics.monoX, buttonY, self.buttonHeight, self:getMonoIcon(), self, MS_AllIndicatorsPanel.onApplyMonoIconToAll)
    self.allMonoButton:initialise()
    self.allMonoButton:setTooltip(getText("IGUI_ModernStatus_MonoIcon"))
    self.allMonoButton:setIconSizeRatio(0.7)
    self.allMonoButton.prerender = function(button)
        button:setIcon(self:getMonoIcon())
        button:setActive(self:areAllStandaloneUsingMonoIcon())
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_MonoIcon"))
    end
    self:addChild(self.allMonoButton)

    self.allShowNameButton = MS_SquareButton:new(metrics.showNameX, buttonY, self.buttonHeight, self:getShowNameIcon(), self, MS_AllIndicatorsPanel.onApplyShowNameToAll)
    self.allShowNameButton:initialise()
    self.allShowNameButton:setTooltip(getText("IGUI_ModernStatus_ShowName"))
    self.allShowNameButton:setIconSizeRatio(0.7)
    self.allShowNameButton.prerender = function(button)
        button:setIcon(self:getShowNameIcon())
        button:setActive(self:areAllStandaloneShowingName())
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_ShowName"))
    end
    self:addChild(self.allShowNameButton)

    self.allValueButton = MS_SquareButton:new(metrics.valueX, buttonY, self.buttonHeight, self:getValueIcon(), self, MS_AllIndicatorsPanel.onApplyAlwaysShowValueToAll)
    self.allValueButton:initialise()
    self.allValueButton:setTooltip(getText("IGUI_ModernStatus_AlwaysShowValue"))
    self.allValueButton:setIconSizeRatio(0.7)
    self.allValueButton.prerender = function(button)
        button:setIcon(self:getValueIcon())
        button:setActive(self:areAllStandaloneAlwaysShowValue())
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_AlwaysShowValue"))
    end
    self:addChild(self.allValueButton)

    self.allInvertButton = MS_SquareButton:new(metrics.invertX, buttonY, self.buttonHeight, self:getInvertDirectionIcon(), self, MS_AllIndicatorsPanel.onApplyInvertDirectionToAll)
    self.allInvertButton:initialise()
    self.allInvertButton:setTooltip(getText("IGUI_ModernStatus_InvertDirection"))
    self.allInvertButton:setIconSizeRatio(0.7)
    self.allInvertButton.prerender = function(button)
        button:setIcon(self:getInvertDirectionIcon())
        button:setActive(self:areAllStandaloneInvertingDirection())
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_InvertDirection"))
    end
    self:addChild(self.allInvertButton)

    self.allAlarmButton = MS_SquareButton:new(metrics.alarmX, buttonY, self.buttonHeight, self:getAlarmIcon(), self, MS_AllIndicatorsPanel.onApplyAlarmEnabledToAll)
    self.allAlarmButton:initialise()
    self.allAlarmButton:setTooltip(getText("IGUI_ModernStatus_EnableAlarm"))
    self.allAlarmButton:setIconSizeRatio(0.7)
    self.allAlarmButton.prerender = function(button)
        button:setIcon(self:getAlarmIcon())
        button:setActive(self:areAllStandaloneAlarmEnabled())
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_EnableAlarm"))
    end
    self:addChild(self.allAlarmButton)

    self.allAutoHideButton = MS_SquareButton:new(metrics.autoHideX, buttonY, self.buttonHeight, self:getAutoHideIcon(), self, MS_AllIndicatorsPanel.onApplyAutoHideToAll)
    self.allAutoHideButton:initialise()
    self.allAutoHideButton:setTooltip(getText("IGUI_ModernStatus_AutoHide"))
    self.allAutoHideButton:setIconSizeRatio(0.7)
    self.allAutoHideButton.prerender = function(button)
        button:setIcon(self:getAutoHideIcon())
        button:setActive(self:areAllStandaloneAutoHide())
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_AutoHide"))
    end
    self:addChild(self.allAutoHideButton)

    self.allVisibilityButton = MS_SquareButton:new(metrics.visibilityX, buttonY, self.buttonHeight, self:getVisibilityIcon(self:areAllStandaloneVisible()), self, MS_AllIndicatorsPanel.onApplyVisibilityToAll)
    self.allVisibilityButton:initialise()
    self.allVisibilityButton:setTooltip(getText("IGUI_ModernStatus_Visibility"))
    self.allVisibilityButton:setIconSizeRatio(0.7)
    self.allVisibilityButton.prerender = function(button)
        local shouldHide = self:areAllStandaloneVisible()
        button:setIcon(self:getVisibilityIcon(shouldHide))
        button:setActive(shouldHide)
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_Visibility"))
    end
    self:addChild(self.allVisibilityButton)

    self.allLockButton = MS_SquareButton:new(metrics.lockX, buttonY, self.buttonHeight, self:getLockIcon(not self:areAllStandaloneLocked()), self, MS_AllIndicatorsPanel.onApplyLockToAll)
    self.allLockButton:initialise()
    self.allLockButton:setTooltip(getText("IGUI_ModernStatus_LockUnlock"))
    self.allLockButton:setIconSizeRatio(0.7)
    self.allLockButton.prerender = function(button)
        local newLockedState = not self:areAllStandaloneLocked()
        button:setIcon(self:getLockIcon(newLockedState))
        button:setActive(newLockedState)
        refreshButtonTooltip(button, getText("IGUI_ModernStatus_LockUnlock"))
    end
    self:addChild(self.allLockButton)

    self.allResetButton = MS_LongButton:new(metrics.resetX, buttonY, metrics.resetButtonWidth, self.buttonHeight, getText("IGUI_ModernStatus_ResetColor"), self, MS_AllIndicatorsPanel.onResetAllIndicators)
    self.allResetButton:initialise()
    self.allResetButton:setTooltip(getText("IGUI_ModernStatus_ResetAllMayLag"))
    self:addChild(self.allResetButton)
end

function MS_AllIndicatorsPanel:createScrollView()
    local contentY = self.titleBarHeight + self.padding + self.matrixRowHeight + self.padding + self.headerRowHeight + self.padding
    local contentHeight = math.max(self.buttonHeight, self.height - contentY - self.padding)

    self.scrollView = NIScrollView:new(0, contentY, self.width, contentHeight)
    self.scrollView:initialise()
    self.scrollView:setScrollDirection("vertical")
    self.scrollView:setScrollSensitivity(self.calculateRowHeight(self.buttonHeight))
    self:addChild(self.scrollView)

    -- Keep the cached width aligned with the actual scrollbar width created by NIScrollView.
    if self.scrollView.vscroll then
        self.scrollBarWidth = self.scrollView.vscroll.width
    end
end


function MS_AllIndicatorsPanel:rebuildIndicatorList()
    self:clearIndicatorList()
    self:updateFilterButtonState()
    self:updateSortButtonState()

    if self.scrollView then
        self.scrollView:resetScroll()
    end

    local metrics = self:getLayoutMetrics()
    local rowHeight = self.calculateRowHeight(self.buttonHeight)
    local startY = self.padding
    local filteredIndicators = self:getFilteredIndicators()

    if #filteredIndicators == 0 then
        local emptyLabel = ISLabel:new(metrics.leftInset, startY + (rowHeight - FONT_HGT_SMALL) / 2, rowHeight, getText("IGUI_ModernStatus_NoMatches"), 0.7, 0.7, 0.7, 1, UIFont.Small, true)
        emptyLabel:initialise()
        self.scrollView:addScrollChild(emptyLabel)
        self.scrollView:setScrollHeight(self.scrollView:getHeight())
        return
    end

    for index, item in ipairs(filteredIndicators) do
        local indicator = item.indicator
        local rowY = startY + (index - 1) * rowHeight
        local iconTexture = indicator.iconTexture
        local iconSize = rowHeight * 1.2

        local nameButton = ISButton:new(metrics.leftInset, rowY, metrics.nameColumnWidth, rowHeight, "", self, MS_AllIndicatorsPanel.onNameButtonClick)
        nameButton:initialise()
        nameButton.displayBackground = false
        nameButton.indicator = indicator
        nameButton.indicatorTitle = item.titleText
        nameButton:setTooltip(item.titleText)
        -- The icon + name cell works as a drag handle for custom ordering.
        nameButton.onMouseDown = function(button, x, y)
            return self:onIndicatorRowMouseDown(button, x, y)
        end
        nameButton.prerender = function(button)
            local isDraggingRow = self.rowDragData and self.rowDragData.button == button and self.rowDragData.started == true
            if button:isMouseOver() or isDraggingRow then
                local alpha = isDraggingRow and 0.45 or 0.3
                button:drawRect(0, 0, button:getWidth(), button:getHeight(), alpha, 0.5, 0.5, 0.5)
            end
            if iconTexture then
                button:drawTextureScaled(iconTexture, 0, (rowHeight - iconSize) / 2, iconSize, iconSize, 1, 1, 1, 1)
            end
            local textX = iconSize + self.padding
            local textY = (rowHeight - FONT_HGT_SMALL) / 2
            button:drawText(button.indicatorTitle, textX, textY, 1, 1, 1, 1, UIFont.Small)
        end
        self.scrollView:addScrollChild(nameButton)

        local buttonY = rowY + (rowHeight - self.buttonHeight) / 2

        local matrixButton = MS_LongButton:new(metrics.matrixX, buttonY, metrics.matrixButtonWidth, self.buttonHeight, self:getMatrixButtonText(indicator), self, MS_AllIndicatorsPanel.onToggleMatrixLink)
        matrixButton.indicator = indicator
        matrixButton:initialise()
        matrixButton:setTooltip(getText("IGUI_ModernStatus_Matrix"))
        matrixButton.prerender = function(button)
            local linked = self:isMatrixLinked(button.indicator)
            button:setTitle(self:getMatrixButtonText(button.indicator))
            button:setActive(linked)
        end
        self.scrollView:addScrollChild(matrixButton)

        local styleButton = MS_SquareButton:new(metrics.styleX, buttonY, self.buttonHeight, self:getModeIcon(self:getStyleModeForIndicator(indicator)), self, MS_AllIndicatorsPanel.onToggleStyle)
        styleButton.indicator = indicator
        styleButton:initialise()
        styleButton:setIconSizeRatio(0.7)
        styleButton.prerender = function(button)
            local mode = self:getStyleModeForIndicator(button.indicator)
            local linked = self:isMatrixLinked(button.indicator)
            button:setIcon(self:getModeIcon(mode))
            button:setActive(linked or mode ~= "circular")
            refreshButtonTooltip(button, getText("IGUI_ModernStatus_Style"))
        end
        self.scrollView:addScrollChild(styleButton)
        indicator.styleButton = styleButton

        local gradientButton = MS_SquareButton:new(metrics.gradientX, buttonY, self.buttonHeight, self:getGradientIcon(), self, MS_AllIndicatorsPanel.onToggleUseGradient)
        gradientButton.indicator = indicator
        gradientButton:initialise()
        gradientButton:setIconSizeRatio(0.7)
        gradientButton.prerender = function(button)
            local enabled = self:getIndicatorEffectiveUseGradient(button.indicator)
            button:setIcon(self:getGradientIcon())
            button:setActive(enabled)
            refreshButtonTooltip(button, getText("IGUI_ModernStatus_UseGradient"))
        end
        self.scrollView:addScrollChild(gradientButton)
        indicator.gradientButton = gradientButton

        local showIconButton = MS_SquareButton:new(metrics.showIconX, buttonY, self.buttonHeight, self:getDisplayIcon(), self, MS_AllIndicatorsPanel.onToggleShowIcon)
        showIconButton.indicator = indicator
        showIconButton:initialise()
        showIconButton:setIconSizeRatio(0.7)
        showIconButton.prerender = function(button)
            local enabled = self:getIndicatorEffectiveShowIcon(button.indicator)
            button:setIcon(self:getDisplayIcon())
            button:setActive(enabled)
            refreshButtonTooltip(button, getText("IGUI_ModernStatus_ShowIcon"))
        end
        self.scrollView:addScrollChild(showIconButton)
        indicator.showIconButton = showIconButton

        local monoButton = MS_SquareButton:new(metrics.monoX, buttonY, self.buttonHeight, self:getMonoIcon(), self, MS_AllIndicatorsPanel.onToggleMonoIcon)
        monoButton.indicator = indicator
        monoButton:initialise()
        monoButton:setIconSizeRatio(0.7)
        monoButton.prerender = function(button)
            local enabled = self:getIndicatorEffectiveUseMonoIcon(button.indicator)
            button:setIcon(self:getMonoIcon())
            button:setActive(enabled)
            refreshButtonTooltip(button, getText("IGUI_ModernStatus_MonoIcon"))
        end
        self.scrollView:addScrollChild(monoButton)
        indicator.monoButton = monoButton

        local showNameButton = MS_SquareButton:new(metrics.showNameX, buttonY, self.buttonHeight, self:getShowNameIcon(), self, MS_AllIndicatorsPanel.onToggleShowName)
        showNameButton.indicator = indicator
        showNameButton:initialise()
        showNameButton:setIconSizeRatio(0.7)
        showNameButton.prerender = function(button)
            local enabled = self:getIndicatorEffectiveShowName(button.indicator)
            button:setIcon(self:getShowNameIcon())
            button:setActive(enabled)
            refreshButtonTooltip(button, getText("IGUI_ModernStatus_ShowName"))
        end
        self.scrollView:addScrollChild(showNameButton)
        indicator.showNameButton = showNameButton

        local valueButton = MS_SquareButton:new(metrics.valueX, buttonY, self.buttonHeight, self:getValueIcon(), self, MS_AllIndicatorsPanel.onToggleAlwaysShowValue)
        valueButton.indicator = indicator
        valueButton:initialise()
        valueButton:setIconSizeRatio(0.7)
        valueButton.prerender = function(button)
            local enabled = self:getIndicatorEffectiveAlwaysShowValue(button.indicator)
            button:setIcon(self:getValueIcon())
            button:setActive(enabled)
            refreshButtonTooltip(button, getText("IGUI_ModernStatus_AlwaysShowValue"))
        end
        self.scrollView:addScrollChild(valueButton)
        indicator.valueButton = valueButton

        local invertButton = MS_SquareButton:new(metrics.invertX, buttonY, self.buttonHeight, self:getInvertDirectionIcon(), self, MS_AllIndicatorsPanel.onToggleInvertDirection)
        invertButton.indicator = indicator
        invertButton:initialise()
        invertButton:setIconSizeRatio(0.7)
        invertButton.prerender = function(button)
            local enabled = self:getIndicatorEffectiveInvertDirection(button.indicator)
            button:setIcon(self:getInvertDirectionIcon())
            button:setActive(enabled)
            refreshButtonTooltip(button, getText("IGUI_ModernStatus_InvertDirection"))
        end
        self.scrollView:addScrollChild(invertButton)
        indicator.invertButton = invertButton

        local alarmButton = MS_SquareButton:new(metrics.alarmX, buttonY, self.buttonHeight, self:getAlarmIcon(), self, MS_AllIndicatorsPanel.onToggleAlarmEnabled)
        alarmButton.indicator = indicator
        alarmButton:initialise()
        alarmButton:setIconSizeRatio(0.7)
        alarmButton.prerender = function(button)
            local enabled = self:getIndicatorEffectiveAlarmEnabled(button.indicator)
            button:setIcon(self:getAlarmIcon())
            button:setActive(enabled)
            refreshButtonTooltip(button, getText("IGUI_ModernStatus_EnableAlarm"))
        end
        self.scrollView:addScrollChild(alarmButton)
        indicator.alarmButton = alarmButton

        local autoHideButton = MS_SquareButton:new(metrics.autoHideX, buttonY, self.buttonHeight, self:getAutoHideIcon(), self, MS_AllIndicatorsPanel.onToggleAutoHide)
        autoHideButton.indicator = indicator
        autoHideButton:initialise()
        autoHideButton:setIconSizeRatio(0.7)
        autoHideButton.prerender = function(button)
            local enabled = self:getIndicatorEffectiveAutoHide(button.indicator)
            button:setIcon(self:getAutoHideIcon())
            button:setActive(enabled)
            refreshButtonTooltip(button, getText("IGUI_ModernStatus_AutoHide"))
        end
        self.scrollView:addScrollChild(autoHideButton)
        indicator.autoHideButton = autoHideButton

        local visibilityButton = MS_SquareButton:new(metrics.visibilityX, buttonY, self.buttonHeight, self:getVisibilityIcon(self:getIndicatorEffectiveHidden(indicator)), self, MS_AllIndicatorsPanel.onToggleVisibility)
        visibilityButton.indicator = indicator
        visibilityButton:initialise()
        visibilityButton:setIconSizeRatio(0.7)
        visibilityButton.prerender = function(button)
            local hidden = self:getIndicatorEffectiveHidden(button.indicator)
            button:setIcon(self:getVisibilityIcon(hidden))
            button:setActive(not hidden)
            refreshButtonTooltip(button, getText("IGUI_ModernStatus_Visibility"))
        end
        self.scrollView:addScrollChild(visibilityButton)
        indicator.visibilityButton = visibilityButton

        local lockButton = MS_SquareButton:new(metrics.lockX, buttonY, self.buttonHeight, self:getLockIcon(self:getIndicatorEffectiveLocked(indicator)), self, MS_AllIndicatorsPanel.onToggleLock)
        lockButton.indicator = indicator
        lockButton:initialise()
        lockButton:setIconSizeRatio(0.7)
        lockButton.prerender = function(button)
            local locked = self:getIndicatorEffectiveLocked(button.indicator)
            button:setIcon(self:getLockIcon(locked))
            button:setActive(locked)
            refreshButtonTooltip(button, getText("IGUI_ModernStatus_LockUnlock"))
        end
        self.scrollView:addScrollChild(lockButton)
        indicator.lockButton = lockButton

        local resetButton = MS_LongButton:new(metrics.resetX, buttonY, metrics.resetButtonWidth, self.buttonHeight, getText("IGUI_ModernStatus_ResetColor"), self, MS_AllIndicatorsPanel.onResetIndicator)
        resetButton.indicator = indicator
        resetButton:initialise()
        resetButton:setTooltip(getText("IGUI_ModernStatus_ResetColor"))
        self.scrollView:addScrollChild(resetButton)
        indicator.resetButton = resetButton

        table.insert(self.listButtons, {
            nameButton = nameButton,
            matrixButton = matrixButton,
            styleButton = styleButton,
            gradientButton = gradientButton,
            showIconButton = showIconButton,
            monoButton = monoButton,
            showNameButton = showNameButton,
            valueButton = valueButton,
            invertButton = invertButton,
            alarmButton = alarmButton,
            autoHideButton = autoHideButton,
            visibilityButton = visibilityButton,
            lockButton = lockButton,
            resetButton = resetButton,
            indicator = indicator
        })
    end

    local totalContentHeight = startY + (#filteredIndicators * rowHeight) + self.padding
    self.scrollView:setScrollHeight(math.max(totalContentHeight, self.scrollView:getHeight()))
end


function MS_AllIndicatorsPanel:updateFilterButtonState()
    if not self.filterButton then return end

    self.filterButton:setIcon(self.filterSearchIcon)
    local mode = self.filterVisibilityMode or "all"
    if mode == "shown" then
        self.filterButton:setActiveColor(0.8, 0.5, 0.2)
        self.filterButton:setActive(true)
        self.filterButton:setTooltip(getText("IGUI_ModernStatus_Shown"))
    elseif mode == "hidden" then
        self.filterButton:setActiveColor(0.2, 0.4, 0.8)
        self.filterButton:setActive(true)
        self.filterButton:setTooltip(getText("IGUI_ModernStatus_Hidden"))
    else
        self.filterButton:setActive(false)
        self.filterButton:setTooltip(getText("IGUI_ModernStatus_ShowAll"))
    end
end

function MS_AllIndicatorsPanel:updateSortButtonState()
    if not self.sortButton then return end

    self.sortButton:setIcon(self.sortIcon)
    local mode = self.sortMode or "alphabetical"
    if mode == "custom" then
        self.sortButton:setActiveColor(0.8, 0.5, 0.2)
        self.sortButton:setActive(true)
        self.sortButton:setTooltip(getText("IGUI_ModernStatus_Custom"))
    elseif mode == "original" then
        self.sortButton:setActiveColor(0.2, 0.4, 0.8)
        self.sortButton:setActive(true)
        self.sortButton:setTooltip(getText("IGUI_ModernStatus_SortOriginal"))
    else
        self.sortButton:setActive(false)
        self.sortButton:setTooltip(getText("IGUI_ModernStatus_SortAlphabeticalAZ"))
    end
end

function MS_AllIndicatorsPanel:openMatrixSettingsPanel()
    local panelX = self:getAbsoluteX() + self:getWidth() + 10
    local panelY = self:getAbsoluteY()

    if panelX + MS_MatrixStylePanel.calculateRequiredSize().width > getCore():getScreenWidth() then
        panelX = self:getAbsoluteX() - MS_MatrixStylePanel.calculateRequiredSize().width - 10
    end

    if panelX < 10 then
        panelX = nil
        panelY = nil
    end

    MS_MatrixStylePanel.openForPlayer(self.playerIndex, panelX, panelY)
end

function MS_AllIndicatorsPanel:onOpenMatrixPanel()
    self:openMatrixSettingsPanel()
    return true
end

function MS_AllIndicatorsPanel:onMatrixStyleClick()
    MS_MatrixManager.cycleMatrixMode(self.playerIndex)
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onMatrixUseGradientClick()
    local defaultConfig = self:getMatrixDefaultConfig()
    local enabled, mixed = self:getMatrixLinkedBooleanState("useGradient", defaultConfig.useGradient)
    local nextValue = mixed and true or not enabled
    self:applyMatrixLinkedBooleanValue("useGradient", nextValue)
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onMatrixShowIconClick()
    local defaultConfig = self:getMatrixDefaultConfig()
    local enabled, mixed = self:getMatrixLinkedBooleanState("showIcon", defaultConfig.showIcon)
    local nextValue = mixed and true or not enabled
    self:applyMatrixLinkedBooleanValue("showIcon", nextValue)
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onMatrixShowNameClick()
    local defaultConfig = self:getMatrixDefaultConfig()
    local enabled, mixed = self:getMatrixLinkedBooleanState("showName", defaultConfig.showName)
    local nextValue = mixed and true or not enabled
    self:applyMatrixLinkedBooleanValue("showName", nextValue)
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onMatrixInvertDirectionClick()
    local defaultConfig = self:getMatrixDefaultConfig()
    local enabled, mixed = self:getMatrixLinkedBooleanState("invertDirection", defaultConfig.invertDirection)
    local nextValue = mixed and true or not enabled
    self:applyMatrixLinkedBooleanValue("invertDirection", nextValue)
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onMatrixAlarmEnabledClick()
    local defaultConfig = self:getMatrixDefaultConfig()
    local enabled, mixed = self:getMatrixLinkedBooleanState("alarmEnabled", defaultConfig.alarmEnabled)
    local nextValue = mixed and true or not enabled
    self:applyMatrixLinkedBooleanValue("alarmEnabled", nextValue)
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onMatrixAlwaysShowValueClick()
    local defaultConfig = self:getMatrixDefaultConfig()
    local enabled, mixed = self:getMatrixLinkedBooleanState("alwaysShowValue", defaultConfig.alwaysShowValue)
    local nextValue = mixed and true or not enabled
    self:applyMatrixLinkedBooleanValue("alwaysShowValue", nextValue)
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onMatrixMonoIconClick()
    local defaultConfig = self:getMatrixDefaultConfig()
    local enabled, mixed = self:getMatrixLinkedBooleanState("useMonoIcon", defaultConfig.useMonoIcon)
    local nextValue = mixed and true or not enabled
    self:applyMatrixLinkedBooleanValue("useMonoIcon", nextValue)
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onMatrixVisibilityClick()
    local shouldHide = not MS_MatrixManager.areAllLinkedIndicatorsHidden(self.playerIndex)
    MS_MatrixManager.setLinkedIndicatorsHidden(self.playerIndex, shouldHide)
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onMatrixAutoHideClick()
    local defaultConfig = self:getMatrixDefaultConfig()
    local enabled, mixed = self:getMatrixLinkedBooleanState("autoHide", defaultConfig.autoHide)
    local nextValue = mixed and true or not enabled
    self:applyMatrixLinkedBooleanValue("autoHide", nextValue)
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onMatrixLockClick()
    local locked, mixed = self:getMatrixLinkedLockState()
    local shouldLock = mixed or not locked
    MS_MatrixManager.setLinkedIndicatorsLocked(self.playerIndex, shouldLock)
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onMatrixResetClick()
    MS_MatrixManager.resetMatrixConfig(self.playerIndex)
    self:rebuildIndicatorList()
end


function MS_AllIndicatorsPanel:onSortButtonClick()
    local context = ISContextMenu.get(0, self.sortButton:getAbsoluteX(), self.sortButton:getAbsoluteY() + self.sortButton:getHeight())

    local alphabeticalOption = context:addOption(getText("IGUI_ModernStatus_SortAlphabeticalAZ"), self, MS_AllIndicatorsPanel.onSetSortMode, "alphabetical")
    alphabeticalOption.iconTexture = self.filterModeGreyIcon

    local originalOption = context:addOption(getText("IGUI_ModernStatus_Original"), self, MS_AllIndicatorsPanel.onSetSortMode, "original")
    originalOption.iconTexture = self.filterModeBlueIcon

    local customOption = context:addOption(getText("IGUI_ModernStatus_Custom"), self, MS_AllIndicatorsPanel.onSetSortMode, "custom")
    customOption.iconTexture = self.filterModeOrangeIcon

    return true
end

function MS_AllIndicatorsPanel:onSetSortMode(mode)
    self.sortMode = mode or "alphabetical"
    self:saveListSettings()
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onFilterButtonClick()
    local context = ISContextMenu.get(0, self.filterButton:getAbsoluteX(), self.filterButton:getAbsoluteY() + self.filterButton:getHeight())

    local showAllOption = context:addOption(getText("IGUI_ModernStatus_ShowAll"), self, MS_AllIndicatorsPanel.onSetVisibilityFilter, "all")
    showAllOption.iconTexture = self.filterModeGreyIcon

    local shownOption = context:addOption(getText("IGUI_ModernStatus_Shown"), self, MS_AllIndicatorsPanel.onSetVisibilityFilter, "shown")
    shownOption.iconTexture = self.filterModeOrangeIcon

    local hiddenOption = context:addOption(getText("IGUI_ModernStatus_Hidden"), self, MS_AllIndicatorsPanel.onSetVisibilityFilter, "hidden")
    hiddenOption.iconTexture = self.filterModeBlueIcon

    return true
end

function MS_AllIndicatorsPanel:onSetVisibilityFilter(mode)
    self.filterVisibilityMode = mode or "all"
    self:saveListSettings()
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onFilterTextChanged()
    if self.filterEntry then
        self.filterText = self.filterEntry:getInternalText() or ""
    else
        self.filterText = ""
    end

    if self.filterClearButton then
        self.filterClearButton:setVisible(self.filterText ~= "")
    end

    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onClearFilter()
    if not self.filterEntry then return end
    self.filterEntry:setText("")
    self.filterText = ""
    self.filterClearButton:setVisible(false)
    self:rebuildIndicatorList()
end

function MS_AllIndicatorsPanel:onNameButtonClick(button)
    local indicator = button.indicator
    if not indicator then return end

    if MS_IndicatorSettingsPanel.CurrentPanel then
        MS_IndicatorSettingsPanel.CurrentPanel:close()
    end

    local panel = MS_IndicatorSettingsPanel:new(0, 0, nil, nil, indicator)
    panel:initialise()
    panel:addToUIManager()
    MS_IndicatorSettingsPanel.CurrentPanel = panel

    local panelX = self:getAbsoluteX() - panel:getWidth() - 10
    local panelY = self:getAbsoluteY() + (self:getHeight() - panel:getHeight()) / 2

    if panelX < 0 then
        panelX = self:getAbsoluteX() + self:getWidth() + 10
    end

    if panelY < 0 then
        panelY = 10
    end

    if panelY + panel:getHeight() > getCore():getScreenHeight() then
        panelY = getCore():getScreenHeight() - panel:getHeight() - 10
    end

    panel:setX(panelX)
    panel:setY(panelY)
    panel:setVisible(true)

    if indicator.isBarStyle then
        panel:switchToTab(MS_IndicatorSettingsPanel.TAB_Bar)
    else
        panel:switchToTab(MS_IndicatorSettingsPanel.TAB_CIRCULAR)
    end
end

function MS_AllIndicatorsPanel:onToggleMatrixLink(button)
    local indicator = button.indicator
    if not indicator then return end

    MS_MatrixManager.setIndicatorLinked(self.playerIndex, indicator, not self:isMatrixLinked(indicator))
    self:updateFilterButtonState()
end

function MS_AllIndicatorsPanel:onToggleStyle(button)
    local indicator = button.indicator
    if not indicator then return end

    if self:isMatrixLinked(indicator) then
        self:onMatrixStyleClick()
        return
    end

    local currentMode = self:getIndicatorDisplayMode(indicator)
    local nextMode = "circular"

    if currentMode == "circular" then
        nextMode = "horizontal"
    elseif currentMode == "horizontal" then
        nextMode = "vertical"
    end

    self:applyIndicatorDisplayMode(indicator, nextMode)
end


function MS_AllIndicatorsPanel:onToggleUseGradient(button)
    local indicator = button.indicator
    if not indicator then return end

    indicator.useGradient = not (indicator.useGradient == true)
    MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "useGradient", indicator.useGradient)
end

function MS_AllIndicatorsPanel:onToggleShowIcon(button)
    local indicator = button.indicator
    if not indicator then return end

    indicator.showIcon = not (indicator.showIcon == true)
    MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "showIcon", indicator.showIcon)
end

function MS_AllIndicatorsPanel:onToggleShowName(button)
    local indicator = button.indicator
    if not indicator then return end

    indicator.showName = not (indicator.showName == true)
    MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "showName", indicator.showName)
end

function MS_AllIndicatorsPanel:onToggleInvertDirection(button)
    local indicator = button.indicator
    if not indicator then return end

    indicator.invertDirection = not (indicator.invertDirection == true)
    MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "invertDirection", indicator.invertDirection)
end

function MS_AllIndicatorsPanel:onToggleAlarmEnabled(button)
    local indicator = button.indicator
    if not indicator then return end

    indicator.alarmEnabled = not (indicator.alarmEnabled == true)
    MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "alarmEnabled", indicator.alarmEnabled)
end

function MS_AllIndicatorsPanel:onToggleAlwaysShowValue(button)
    local indicator = button.indicator
    if not indicator then return end

    indicator.alwaysShowValue = not (indicator.alwaysShowValue == true)
    MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "alwaysShowValue", indicator.alwaysShowValue)
end

function MS_AllIndicatorsPanel:onToggleMonoIcon(button)
    local indicator = button.indicator
    if not indicator then return end

    indicator.useMonoIcon = not (indicator.useMonoIcon == true)
    indicator:updateIconPath()
    MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "useMonoIcon", indicator.useMonoIcon)
end

function MS_AllIndicatorsPanel:onToggleVisibility(button)
    local indicator = button.indicator
    if not indicator then return end

    indicator.hidden = not indicator.hidden
    indicator:setVisible(not indicator.hidden)

    MSConfig.updateIndicatorConfig(
        indicator.playerIndex,
        indicator:getType(),
        "hidden",
        indicator.hidden
    )

    MS_MatrixManager.applyMatrixLayout(self.playerIndex)
end


function MS_AllIndicatorsPanel:onToggleAutoHide(button)
    local indicator = button.indicator
    if not indicator then return end

    indicator.autoHide = not (indicator.autoHide == true)
    MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "autoHide", indicator.autoHide)
end

function MS_AllIndicatorsPanel:onToggleLock(button)
    local indicator = button.indicator
    if not indicator then return end

    indicator.locked = not indicator.locked
    indicator.moveWithMouse = not indicator.locked

    MSConfig.updateIndicatorConfig(
        indicator.playerIndex,
        indicator:getType(),
        "locked",
        indicator.locked
    )
end

function MS_AllIndicatorsPanel:onResetIndicator(button)
    local indicator = button.indicator
    if not indicator then return end

    if self:isMatrixLinked(indicator) then
        self:onMatrixResetClick()
        return
    end

    self:applyDefaultIndicatorConfig(indicator)
end

function MS_AllIndicatorsPanel:onApplyMatrixToAll()
    local shouldLinkAll = not self:areAllIndicatorsMatrixLinked()

    if shouldLinkAll then
        for _, item in ipairs(self:getSortedIndicators()) do
            local indicator = item.indicator
            if not self:isMatrixLinked(indicator) then
                MS_MatrixManager.setIndicatorLinked(self.playerIndex, indicator, true, true)
            end
        end
    else
        for _, indicator in ipairs(self:getAllIndicators()) do
            if self:isMatrixLinked(indicator) then
                MS_MatrixManager.setIndicatorLinked(self.playerIndex, indicator, false, true)
            end
        end
    end

    MS_MatrixManager.applyMatrixLayout(self.playerIndex)
    self:updateFilterButtonState()
end

function MS_AllIndicatorsPanel:onApplyStyleToAll()
    local targetMode = self:getNextBulkStyleMode()
    if not targetMode then return end

    for _, indicator in ipairs(self:getStandaloneIndicators()) do
        self:applyIndicatorDisplayMode(indicator, targetMode)
    end

    MS_MatrixManager.refreshPlayer(self.playerIndex)
end


function MS_AllIndicatorsPanel:onApplyUseGradientToAll()
    local standaloneIndicators = self:getStandaloneIndicators()
    if #standaloneIndicators == 0 then return end

    local enabled = not self:areAllStandaloneUsingGradient()
    for _, indicator in ipairs(standaloneIndicators) do
        indicator.useGradient = enabled
        MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "useGradient", enabled)
    end
end

function MS_AllIndicatorsPanel:onApplyShowIconToAll()
    local standaloneIndicators = self:getStandaloneIndicators()
    if #standaloneIndicators == 0 then return end

    local enabled = not self:areAllStandaloneShowingIcon()
    for _, indicator in ipairs(standaloneIndicators) do
        indicator.showIcon = enabled
        MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "showIcon", enabled)
    end
end

function MS_AllIndicatorsPanel:onApplyShowNameToAll()
    local standaloneIndicators = self:getStandaloneIndicators()
    if #standaloneIndicators == 0 then return end

    local enabled = not self:areAllStandaloneShowingName()
    for _, indicator in ipairs(standaloneIndicators) do
        indicator.showName = enabled
        MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "showName", enabled)
    end
end

function MS_AllIndicatorsPanel:onApplyInvertDirectionToAll()
    local standaloneIndicators = self:getStandaloneIndicators()
    if #standaloneIndicators == 0 then return end

    local enabled = not self:areAllStandaloneInvertingDirection()
    for _, indicator in ipairs(standaloneIndicators) do
        indicator.invertDirection = enabled
        MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "invertDirection", enabled)
    end
end

function MS_AllIndicatorsPanel:onApplyAlarmEnabledToAll()
    local standaloneIndicators = self:getStandaloneIndicators()
    if #standaloneIndicators == 0 then return end

    local enabled = not self:areAllStandaloneAlarmEnabled()
    for _, indicator in ipairs(standaloneIndicators) do
        indicator.alarmEnabled = enabled
        MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "alarmEnabled", enabled)
    end
end

function MS_AllIndicatorsPanel:onApplyAlwaysShowValueToAll()
    local standaloneIndicators = self:getStandaloneIndicators()
    if #standaloneIndicators == 0 then return end

    local enabled = not self:areAllStandaloneAlwaysShowValue()
    for _, indicator in ipairs(standaloneIndicators) do
        indicator.alwaysShowValue = enabled
        MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "alwaysShowValue", enabled)
    end
end

function MS_AllIndicatorsPanel:onApplyMonoIconToAll()
    local standaloneIndicators = self:getStandaloneIndicators()
    if #standaloneIndicators == 0 then return end

    local enabled = not self:areAllStandaloneUsingMonoIcon()
    for _, indicator in ipairs(standaloneIndicators) do
        indicator.useMonoIcon = enabled
        indicator:updateIconPath()
        MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "useMonoIcon", enabled)
    end
end

function MS_AllIndicatorsPanel:onApplyVisibilityToAll()
    local standaloneIndicators = self:getStandaloneIndicators()
    if #standaloneIndicators == 0 then return end

    local shouldHide = self:areAllStandaloneVisible()
    for _, indicator in ipairs(standaloneIndicators) do
        indicator.hidden = shouldHide
        indicator:setVisible(not shouldHide)
        MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "hidden", shouldHide)
    end
    MS_MatrixManager.applyMatrixLayout(self.playerIndex)
end


function MS_AllIndicatorsPanel:onApplyAutoHideToAll()
    local standaloneIndicators = self:getStandaloneIndicators()
    if #standaloneIndicators == 0 then return end

    local enabled = not self:areAllStandaloneAutoHide()
    for _, indicator in ipairs(standaloneIndicators) do
        indicator.autoHide = enabled
        MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "autoHide", enabled)
    end
end

function MS_AllIndicatorsPanel:onApplyLockToAll()
    local standaloneIndicators = self:getStandaloneIndicators()
    if #standaloneIndicators == 0 then return end

    local newLockedState = not self:areAllStandaloneLocked()
    for _, indicator in ipairs(standaloneIndicators) do
        indicator.locked = newLockedState
        indicator.moveWithMouse = not newLockedState
        MSConfig.updateIndicatorConfig(indicator.playerIndex, indicator:getType(), "locked", newLockedState)
    end
end

function MS_AllIndicatorsPanel:onResetAllIndicators()
    for _, indicator in ipairs(self:getStandaloneIndicators()) do
        self:applyDefaultIndicatorConfig(indicator)
    end
    MS_MatrixManager.applyMatrixLayout(self.playerIndex)
end

-- Convert the mouse Y position into an insertion slot between visible rows.
function MS_AllIndicatorsPanel:getDropRowIndexForAbsoluteY(absoluteY, visibleCount)
    if not self.scrollView or visibleCount <= 0 then
        return 1
    end

    local rowHeight = self.calculateRowHeight(self.buttonHeight)
    local logicalY = absoluteY - self.scrollView:getAbsoluteY() - self.scrollView:getYScroll()
    local relativeY = logicalY - self.padding
    local rowIndex = math.floor(relativeY / rowHeight) + 1
    local offsetInRow = relativeY - ((rowIndex - 1) * rowHeight)
    local targetIndex = rowIndex

    if offsetInRow > rowHeight / 2 then
        targetIndex = targetIndex + 1
    end

    if targetIndex < 1 then
        targetIndex = 1
    elseif targetIndex > (visibleCount + 1) then
        targetIndex = visibleCount + 1
    end

    return targetIndex
end

-- Rebuild the custom order from the currently visible rows after a drag-and-drop move.
function MS_AllIndicatorsPanel:applyCustomOrderFromRowDrag(draggedIndicator, absoluteY)
    if not draggedIndicator then return false end

    local filteredIndicators = self:getFilteredIndicators()
    if #filteredIndicators <= 1 then
        return false
    end

    local draggedIndex = nil
    for index, item in ipairs(filteredIndicators) do
        if item.indicator == draggedIndicator then
            draggedIndex = index
            break
        end
    end

    if not draggedIndex then
        return false
    end

    local targetIndex = self:getDropRowIndexForAbsoluteY(absoluteY, #filteredIndicators)

    local reorderedVisibleTypes = {}
    for _, item in ipairs(filteredIndicators) do
        table.insert(reorderedVisibleTypes, item.indicatorType)
    end

    local movedType = table.remove(reorderedVisibleTypes, draggedIndex)
    if targetIndex > draggedIndex then
        targetIndex = targetIndex - 1
    end
    if targetIndex == draggedIndex then
        return false
    end
    table.insert(reorderedVisibleTypes, targetIndex, movedType)

    local visibleLookup = {}
    for _, indicatorType in ipairs(reorderedVisibleTypes) do
        visibleLookup[indicatorType] = true
    end

    local fullCurrentOrder = self:getSortedIndicators()
    local customOrder = {}
    for _, indicatorType in ipairs(reorderedVisibleTypes) do
        table.insert(customOrder, indicatorType)
    end
    for _, item in ipairs(fullCurrentOrder) do
        if not visibleLookup[item.indicatorType] then
            table.insert(customOrder, item.indicatorType)
        end
    end

    self.sortMode = "custom"
    self:setCustomOrder(customOrder)
    self:rebuildIndicatorList()
    return true
end

-- Distinguish a click from a drag so clicking still opens the indicator settings.
function MS_AllIndicatorsPanel:onIndicatorRowMouseDown(button, x, y)
    if not button or not button.indicator then
        return false
    end

    -- Capture drag input on the panel itself so the gesture keeps working inside the scroll view.
    self.rowDragData = {
        button = button,
        indicator = button.indicator,
        startMouseX = getMouseX(),
        startMouseY = getMouseY(),
        currentMouseY = getMouseY(),
        started = false
    }
    self.rowDragHoverIndex = nil
    self:setCapture(true)
    return true
end

function MS_AllIndicatorsPanel:updateIndicatorRowDragState(mouseX, mouseY)
    local dragData = self.rowDragData
    if not dragData then
        return false
    end

    dragData.currentMouseY = mouseY

    if not dragData.started then
        local totalDX = math.abs(mouseX - dragData.startMouseX)
        local totalDY = math.abs(mouseY - dragData.startMouseY)
        if totalDX >= 4 or totalDY >= 4 then
            dragData.started = true
        end
    end

    if dragData.started then
        local filteredIndicators = self:getFilteredIndicators()
        self.rowDragHoverIndex = self:getDropRowIndexForAbsoluteY(mouseY, #filteredIndicators)
    end

    return true
end

function MS_AllIndicatorsPanel:finishIndicatorRowDrag(mouseY)
    local dragData = self.rowDragData
    self.rowDragData = nil
    self.rowDragHoverIndex = nil
    self:setCapture(false)

    if not dragData then
        return false
    end

    if dragData.started then
        self:applyCustomOrderFromRowDrag(dragData.indicator, mouseY)
        return true
    end

    self:onNameButtonClick(dragData.button)
    return true
end

function MS_AllIndicatorsPanel:onMouseWheel(del)
    if self.scrollView and self.scrollView:isVisible() then
        return self.scrollView:onMouseWheel(del)
    end
    return false
end

-- ---------------------------------------------------------------------------------------- --
-- Mouse Function
-- ---------------------------------------------------------------------------------------- --
function MS_AllIndicatorsPanel:onMouseDown(x, y)
    if y <= self.titleBarHeight then
        self.moving = true
        self:setCapture(true)
        return true
    end
    return ISPanel.onMouseDown(self, x, y)
end

function MS_AllIndicatorsPanel:onMouseUp(x, y)
    if self.rowDragData then
        return self:finishIndicatorRowDrag(getMouseY())
    end

    if self.moving then
        self.moving = false
        self:setCapture(false)
    end
    return ISPanel.onMouseUp(self, x, y)
end

function MS_AllIndicatorsPanel:onMouseMove(dx, dy)
    if self.rowDragData then
        return self:updateIndicatorRowDragState(getMouseX(), getMouseY())
    end

    if self.moving then
        self:setX(self.x + dx)
        self:setY(self.y + dy)
        return true
    end
    return ISPanel.onMouseMove(self, dx, dy)
end

function MS_AllIndicatorsPanel:onMouseMoveOutside(dx, dy)
    if self.rowDragData then
        return self:updateIndicatorRowDragState(getMouseX(), getMouseY())
    end

    if self.moving then
        self:setX(self.x + dx)
        self:setY(self.y + dy)
        return true
    end
    return ISPanel.onMouseMoveOutside(self, dx, dy)
end

function MS_AllIndicatorsPanel:onMouseUpOutside(x, y)
    if self.rowDragData then
        return self:finishIndicatorRowDrag(getMouseY())
    end

    if self.moving then
        self.moving = false
        self:setCapture(false)
    end
    if MS_PanelCollapseHelper.onMouseUpOutside(self) then
        return true
    end
    return ISPanel.onMouseUpOutside(self, x, y)
end

-- ---------------------------------------------------------------------------------------- --
-- Panel Control
-- ---------------------------------------------------------------------------------------- --
function MS_AllIndicatorsPanel:close()
    if MS_AllIndicatorsPanel.CurrentPanel == self then
        MS_AllIndicatorsPanel.CurrentPanel = nil
    end
    self:setVisible(false)
    self:removeFromUIManager()
end

-- ---------------------------------------------------------------------------------------- --
-- render
-- ---------------------------------------------------------------------------------------- --
function MS_AllIndicatorsPanel:prerender()
    MS_PanelCollapseHelper.updateCollapsedPreviewFromMouse(self)

    local titlebarbkg = NinePatchTexture.getSharedTexture("media/ui/ModernStatus/SettingPanel/TitleBarBG.png")
    titlebarbkg:render(self:getAbsoluteX(), self:getAbsoluteY(), self:getWidth(), self.titleBarHeight, 0.5, 0.5, 0.5, 0.95)

    if MS_PanelCollapseHelper.isContentHidden(self) then
        return
    end

    local contentbkg = NinePatchTexture.getSharedTexture("media/ui/ModernStatus/SettingPanel/ContentBackground.png")
    contentbkg:render(self:getAbsoluteX(), self:getAbsoluteY() + self.titleBarHeight, self:getWidth(), self:getHeight() - self.titleBarHeight, 0.1, 0.1, 0.1, 0.8)

    -- Separate the dedicated matrix row from the bulk-actions header row.
    local dividerY = self.titleBarHeight + self.padding + self.matrixRowHeight + math.floor(self.padding / 2)
    self:drawRect(self.padding, dividerY, self.width - self.padding * 2, 1, 0.8, 0.35, 0.35, 0.35)
end

function MS_AllIndicatorsPanel:render()
    ISPanel.render(self)

    if MS_PanelCollapseHelper.isContentHidden(self) then
        return
    end

    if not self.rowDragData or not self.rowDragData.started or not self.scrollView then
        return
    end

    local filteredIndicators = self:getFilteredIndicators()
    if #filteredIndicators == 0 then
        return
    end

    local targetIndex = self.rowDragHoverIndex or self:getDropRowIndexForAbsoluteY(self.rowDragData.currentMouseY or getMouseY(), #filteredIndicators)
    local rowHeight = self.calculateRowHeight(self.buttonHeight)
    local lineY = self.scrollView.y + self.padding + ((targetIndex - 1) * rowHeight) + self.scrollView:getYScroll()
    local lineHeight = math.max(2, math.floor(rowHeight / 6))
    local lineWidth = self.scrollView.width - self.scrollBarWidth - self.padding * 2

    -- Draw a drop marker similar to the vanilla mod-order list to show where the row will be inserted.
    if lineY >= self.scrollView.y - rowHeight and lineY <= self.scrollView.y + self.scrollView.height then
        self:drawRect(self.padding, lineY - math.floor(lineHeight / 2), lineWidth, lineHeight, 0.6, 1, 1, 1)
    end
end
